├── 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 |
--------------------------------------------------------------------------------