├── LICENSE ├── README.md ├── cookiecutter.json ├── hooks ├── post_gen_project.py └── pre_gen_project.py └── {{cookiecutter.package_name}} ├── .gitignore ├── MANIFEST.in ├── README.rst ├── setup.cfg ├── setup.py └── {{cookiecutter.package_name}} ├── __init__.py ├── __main__.py ├── app.py ├── model.py ├── path.py ├── templates ├── greeting.pt └── index.pt ├── tests └── test_app.py └── view.py /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # A Cookiecutter Template for Morepath 2 | 3 | A cookiecutter template to get started with [Morepath](https://morepath.readthedocs.org), 4 | based on the [organizing your project](http://morepath.readthedocs.org/en/latest/organizing_your_project.html) 5 | chapter from the Morepath documentation. 6 | 7 | This template either generates a RESTful application or a tradiational web 8 | application (HTML). The wizard will ask you to choose between those two 9 | options. 10 | 11 | If you go with the traditional route, the [Chameleon templating language](https://chameleon.readthedocs.org/en/latest/) 12 | will be used. Feel free to switch to another templating language. We've chosen 13 | one for you because adding support for multiple templating languages would make 14 | this cookiecutter template quite messy. 15 | 16 | # Requirements 17 | 18 | To use this template, install the latest version of cookiecutter: 19 | 20 | pip install --upgrade cookiecutter 21 | 22 | You will also need Python 2.7 or 3.3+ and [virtualenv](http://docs.python-guide.org/en/latest/dev/virtualenvs/). 23 | 24 | # Usage 25 | 26 | Create a new package with the wizard: 27 | 28 | cookiecutter https://github.com/morepath/morepath-cookiecutter 29 | 30 | (The result will be stored in a new directory named after the package) 31 | 32 | Inside the so created package create a virtualenv: 33 | 34 | cd mypackage 35 | virtualenv env 36 | source env/bin/activate 37 | 38 | And install the package (including tests): 39 | 40 | env/bin/pip install -e '.[test]' 41 | 42 | You can now run your application using either of the following commands: 43 | 44 | env/bin/run-app 45 | env/bin/python -m mypackage 46 | 47 | Once the application is running you can open it in your browser under 48 | [http://localhost:5000](http://localhost:5000). 49 | 50 | There is also one example test included which you can run using: 51 | 52 | env/bin/py.test 53 | 54 | # Features 55 | 56 | This template is pretty bare-bones. We do not offer support for various 57 | integrations like travis-ci or tox, because we mean to help out beginners 58 | with Morepath. 59 | 60 | If you require more advanced features, feel free to fork! 61 | 62 | # Structure 63 | 64 | In the resulting directory you find the following files: 65 | 66 | * `./app.py`: Contains the Morepath application. 67 | * `./model.py`: Contains the models you are working with. 68 | * `./path.py`: Contains the paths pointing to the models. 69 | * `./view.py`: Contains the views associated with the models. 70 | * `./__main__.py`: Contains the run script for the test server. 71 | * `./tests/test_app.py`: Contains an example test. 72 | 73 | # License 74 | 75 | The Cookiecutter Template for Morepath is released into the public domain. 76 | 77 | # More Information 78 | 79 | * https://morepath.readthedocs.org 80 | * https://github.com/cookiecutter/cookiecutter 81 | -------------------------------------------------------------------------------- /cookiecutter.json: -------------------------------------------------------------------------------- 1 | { 2 | "package_name": "helloworld", 3 | "description": "A morepath helloworld application", 4 | "goal": ["A RESTful application", "A traditional web application"], 5 | "author": "", 6 | "author_email": "" 7 | } 8 | -------------------------------------------------------------------------------- /hooks/post_gen_project.py: -------------------------------------------------------------------------------- 1 | #!/bin/python 2 | import os 3 | import shutil 4 | 5 | from contextlib import contextmanager 6 | 7 | 8 | @contextmanager 9 | def switch_directory(path): 10 | previous_path = os.getcwd() 11 | 12 | os.chdir(path) 13 | yield 14 | os.chdir(previous_path) 15 | 16 | 17 | def get_parent_directory(path): 18 | return os.path.split(path) 19 | 20 | 21 | def get_directory_name(path): 22 | return os.path.split(path)[-1] 23 | 24 | 25 | def create_nested_folders(package_name, target): 26 | 27 | declare_ns = "__import__('pkg_resources').declare_namespace(__name__)\n" 28 | 29 | # make sure to get back to the original cwd 30 | with switch_directory(os.getcwd()): 31 | parts = package_name.split('.') 32 | 33 | for ix, part in enumerate(parts): 34 | if (ix + 1) == len(parts): 35 | shutil.move(target, part) 36 | else: 37 | os.mkdir(part) 38 | os.chdir(part) 39 | 40 | with open('__init__.py', 'w') as f: 41 | f.write(declare_ns) 42 | 43 | 44 | # create nested package folders (my.package becomes my/package) 45 | package_name = get_directory_name(os.getcwd()) 46 | 47 | if '.' in package_name: 48 | create_nested_folders( 49 | package_name=package_name, 50 | target=os.path.join(os.getcwd(), package_name) 51 | ) 52 | 53 | # remove templates dir if REST application was requested 54 | goal = '{{ cookiecutter.goal }}' 55 | 56 | if 'REST' in goal: 57 | parts = [os.getcwd()] 58 | parts.extend(package_name.split('.')) 59 | parts.append('templates') 60 | 61 | shutil.rmtree(os.path.join(*parts)) 62 | -------------------------------------------------------------------------------- /hooks/pre_gen_project.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | 3 | import re 4 | import sys 5 | 6 | 7 | MODULE_REGEX = re.compile(r'^[_a-zA-Z][_a-zA-Z0-9]*$') 8 | 9 | package_name = '{{ cookiecutter.package_name }}' 10 | 11 | 12 | for part in package_name.split('.'): 13 | if not MODULE_REGEX.match(part): 14 | print('ERROR: %s is not a valid Python module name!' % package_name) 15 | sys.exit(1) 16 | -------------------------------------------------------------------------------- /{{cookiecutter.package_name}}/.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized 2 | __pycache__/ 3 | *.py[co] 4 | 5 | # Distribution / packaging 6 | env/ 7 | build/ 8 | dist/ 9 | *.egg-info/ 10 | 11 | # Unit test / coverage reports 12 | htmlcov/ 13 | .tox/ 14 | .coverage 15 | .cache 16 | nosetests.xml 17 | coverage.xml 18 | 19 | # Sphinx documentation 20 | docs/_build/ 21 | -------------------------------------------------------------------------------- /{{cookiecutter.package_name}}/MANIFEST.in: -------------------------------------------------------------------------------- 1 | recursive-include {{ cookiecutter.package_name.split('.')[0] }} * 2 | recursive-include docs * 3 | include *.txt 4 | include *.rst 5 | global-exclude *.mo 6 | global-exclude *.pyc 7 | global-exclude *.pyo 8 | global-exclude __pycache__/* 9 | -------------------------------------------------------------------------------- /{{cookiecutter.package_name}}/README.rst: -------------------------------------------------------------------------------- 1 | {{ cookiecutter.package_name.split('.') | map('capitalize') | join(' ') }} 2 | {{ "=" * cookiecutter.package_name.__len__() }} 3 | 4 | {{ cookiecutter.description }} 5 | -------------------------------------------------------------------------------- /{{cookiecutter.package_name}}/setup.cfg: -------------------------------------------------------------------------------- 1 | [tool:pytest] 2 | testpaths = {{cookiecutter.package_name.split('.')[0]}} 3 | -------------------------------------------------------------------------------- /{{cookiecutter.package_name}}/setup.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from setuptools import setup, find_packages 4 | 5 | name = '{{ cookiecutter.package_name }}' 6 | description = ( 7 | '{{ cookiecutter.description }}' 8 | ) 9 | version = '0.0.0' 10 | 11 | 12 | setup( 13 | name=name, 14 | version=version, 15 | description=description, 16 | author='{{ cookiecutter.author }}', 17 | author_email='{{ cookiecutter.author_email }}', 18 | packages=find_packages(), 19 | namespace_packages=name.split('.')[:-1], 20 | include_package_data=True, 21 | zip_safe=False, 22 | platforms='any', 23 | install_requires=[ 24 | {% if 'REST' in cookiecutter.goal %}'morepath' 25 | {%- endif -%} 26 | {% if 'traditional' in cookiecutter.goal -%} 27 | 'morepath', 28 | 'more.chameleon', 29 | {%- endif %} 30 | ], 31 | extras_require=dict( 32 | test=[ 33 | 'pytest', 34 | 'webtest', 35 | ], 36 | ), 37 | entry_points=dict( 38 | console_scripts=[ 39 | 'run-app = {{ cookiecutter.package_name }}.__main__:run', 40 | ], 41 | ), 42 | classifiers=[ 43 | 'Intended Audience :: Developers', 44 | 'Environment :: Web Environment', 45 | 'Topic :: Internet :: WWW/HTTP :: WSGI', 46 | 'Programming Language :: Python :: 2.7', 47 | 'Programming Language :: Python :: 3.4', 48 | ] 49 | ) 50 | -------------------------------------------------------------------------------- /{{cookiecutter.package_name}}/{{cookiecutter.package_name}}/__init__.py: -------------------------------------------------------------------------------- 1 | from {{ cookiecutter.package_name }}.app import App 2 | 3 | __all__ = ['App'] 4 | -------------------------------------------------------------------------------- /{{cookiecutter.package_name}}/{{cookiecutter.package_name}}/__main__.py: -------------------------------------------------------------------------------- 1 | import morepath 2 | from .app import App 3 | 4 | 5 | def run(): 6 | morepath.autoscan() 7 | morepath.run(App()) 8 | 9 | 10 | if __name__ == '__main__': 11 | run() 12 | -------------------------------------------------------------------------------- /{{cookiecutter.package_name}}/{{cookiecutter.package_name}}/app.py: -------------------------------------------------------------------------------- 1 | {% if 'REST' in cookiecutter.goal -%}import morepath{%- endif -%} 2 | {% if 'traditional' in cookiecutter.goal -%}from more.chameleon import ChameleonApp{%- endif -%} 3 | 4 | {% if 'REST' in cookiecutter.goal %} 5 | 6 | 7 | class App(morepath.App): 8 | pass 9 | {% else %} 10 | 11 | 12 | class App(ChameleonApp): 13 | pass 14 | 15 | 16 | @App.template_directory() 17 | def get_template_directory(): 18 | return 'templates' 19 | 20 | 21 | @App.setting_section(section='chameleon') 22 | def get_chameleon_settings(): 23 | return {'debug': True} 24 | {% endif %} -------------------------------------------------------------------------------- /{{cookiecutter.package_name}}/{{cookiecutter.package_name}}/model.py: -------------------------------------------------------------------------------- 1 | class Root(object): 2 | @property 3 | def greetings(self): 4 | return [ 5 | Greeting('mundo'), 6 | Greeting('world') 7 | ] 8 | 9 | 10 | class Greeting(object): 11 | def __init__(self, person): 12 | self.person = person 13 | -------------------------------------------------------------------------------- /{{cookiecutter.package_name}}/{{cookiecutter.package_name}}/path.py: -------------------------------------------------------------------------------- 1 | from .app import App 2 | from . import model 3 | 4 | 5 | @App.path(model=model.Root, path='/') 6 | def get_root(): 7 | return model.Root() 8 | 9 | 10 | @App.path(model=model.Greeting, path='/greeting/{person}') 11 | def get_greeting(person): 12 | return model.Greeting(person) 13 | -------------------------------------------------------------------------------- /{{cookiecutter.package_name}}/{{cookiecutter.package_name}}/templates/greeting.pt: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {{ cookiecutter.package_name }} 6 | 7 | 8 |

${greeting}

9 | 10 | 11 | -------------------------------------------------------------------------------- /{{cookiecutter.package_name}}/{{cookiecutter.package_name}}/templates/index.pt: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {{ cookiecutter.package_name }} 6 | 7 | 8 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /{{cookiecutter.package_name}}/{{cookiecutter.package_name}}/tests/test_app.py: -------------------------------------------------------------------------------- 1 | import morepath 2 | import {{ cookiecutter.package_name }} 3 | 4 | from webtest import TestApp as Client 5 | 6 | 7 | def test_root(): 8 | morepath.scan({{ cookiecutter.package_name }}) 9 | morepath.commit({{ cookiecutter.package_name }}.App) 10 | 11 | client = Client({{ cookiecutter.package_name }}.App()) 12 | root = client.get('/') 13 | 14 | assert root.status_code == 200 15 | {%- if 'traditional' in cookiecutter.goal %} 16 | assert '/greeting/world' in root 17 | assert '/greeting/mundo' in root 18 | {%- else %} 19 | assert len(root.json['greetings']) == 2 20 | {%- endif %} 21 | -------------------------------------------------------------------------------- /{{cookiecutter.package_name}}/{{cookiecutter.package_name}}/view.py: -------------------------------------------------------------------------------- 1 | from .app import App 2 | from . import model 3 | 4 | 5 | {%- if 'REST' in cookiecutter.goal %} 6 | 7 | 8 | @App.json(model=model.Root) 9 | def view_root(self, request): 10 | return { 11 | 'greetings': [ 12 | { 13 | 'name': greeting.person, 14 | '@id': request.link(greeting) 15 | } 16 | for greeting in self.greetings 17 | ] 18 | } 19 | 20 | 21 | @App.json(model=model.Greeting) 22 | def view_greeting(self, request): 23 | return { 24 | 'greeting': 'hello ' + self.person 25 | } 26 | {%- else %} 27 | 28 | 29 | @App.html(model=model.Root, template='index.pt') 30 | def view_root(self, request): 31 | return { 32 | 'greetings': self.greetings, 33 | 'request': request, 34 | } 35 | 36 | 37 | @App.html(model=model.Greeting, template='greeting.pt') 38 | def view_greeting(self, request): 39 | return { 40 | 'greeting': 'hello ' + self.person 41 | } 42 | {%- endif %} 43 | --------------------------------------------------------------------------------