summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRyan Petrello <lists@ryanpetrello.com>2014-03-06 18:46:23 +0000
committerRyan Petrello <lists@ryanpetrello.com>2014-03-06 20:46:47 +0000
commitb046c1e965e2a37758c5092402a92b46217d58b4 (patch)
tree9852d38032471e1ca347d3911edfafbd23648883
parent32712c50926ef27c58e1f16e14a4389d1a201f4b (diff)
downloadpecan-b046c1e965e2a37758c5092402a92b46217d58b4.tar.gz
Add a pecan scaffold for generating a simple REST API.
Change-Id: Iae346dc2d9a9cc52f3c8b4b546793c8d9670f56d Fixes bug 1248822
-rw-r--r--MANIFEST.in2
-rw-r--r--pecan/scaffolds/__init__.py4
-rw-r--r--pecan/scaffolds/base/+package+/model/__init__.py2
-rw-r--r--pecan/scaffolds/rest-api/+package+/__init__.py0
-rw-r--r--pecan/scaffolds/rest-api/+package+/app.py_tmpl16
-rw-r--r--pecan/scaffolds/rest-api/+package+/controllers/__init__.py0
-rw-r--r--pecan/scaffolds/rest-api/+package+/controllers/root.py44
-rw-r--r--pecan/scaffolds/rest-api/+package+/errors.py18
-rw-r--r--pecan/scaffolds/rest-api/+package+/model/__init__.py15
-rw-r--r--pecan/scaffolds/rest-api/+package+/tests/__init__.py_tmpl22
-rw-r--r--pecan/scaffolds/rest-api/+package+/tests/config.py_tmpl19
-rw-r--r--pecan/scaffolds/rest-api/+package+/tests/test_functional.py_tmpl37
-rw-r--r--pecan/scaffolds/rest-api/+package+/tests/test_units.py7
-rw-r--r--pecan/scaffolds/rest-api/config.py_tmpl41
-rw-r--r--pecan/scaffolds/rest-api/setup.cfg_tmpl6
-rw-r--r--pecan/scaffolds/rest-api/setup.py_tmpl22
-rw-r--r--pecan/tests/scaffold_builder.py10
-rw-r--r--setup.py1
-rw-r--r--tox.ini59
19 files changed, 317 insertions, 8 deletions
diff --git a/MANIFEST.in b/MANIFEST.in
index d708b6d..79e6393 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,4 +1,6 @@
recursive-include pecan/scaffolds/base *
include pecan/scaffolds/base/*
+recursive-include pecan/scaffolds/rest-api *
+include pecan/scaffolds/rest-api/*
include pecan/middleware/resources/*
include LICENSE README.rst requirements.txt
diff --git a/pecan/scaffolds/__init__.py b/pecan/scaffolds/__init__.py
index ac22464..2dbe46f 100644
--- a/pecan/scaffolds/__init__.py
+++ b/pecan/scaffolds/__init__.py
@@ -42,6 +42,10 @@ class BaseScaffold(PecanScaffold):
_scaffold_dir = ('pecan', os.path.join('scaffolds', 'base'))
+class RestAPIScaffold(PecanScaffold):
+ _scaffold_dir = ('pecan', os.path.join('scaffolds', 'rest-api'))
+
+
def copy_dir(source, dest, variables, out_=sys.stdout, i=0):
"""
Copies the ``source`` directory to the ``dest`` directory, where
diff --git a/pecan/scaffolds/base/+package+/model/__init__.py b/pecan/scaffolds/base/+package+/model/__init__.py
index 2f1740f..d983f7b 100644
--- a/pecan/scaffolds/base/+package+/model/__init__.py
+++ b/pecan/scaffolds/base/+package+/model/__init__.py
@@ -5,7 +5,7 @@ def init_model():
"""
This is a stub method which is called at application startup time.
- If you need to bind to a parse database configuration, set up tables or
+ If you need to bind to a parsed database configuration, set up tables or
ORM classes, or perform any database initialization, this is the
recommended place to do it.
diff --git a/pecan/scaffolds/rest-api/+package+/__init__.py b/pecan/scaffolds/rest-api/+package+/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/pecan/scaffolds/rest-api/+package+/__init__.py
diff --git a/pecan/scaffolds/rest-api/+package+/app.py_tmpl b/pecan/scaffolds/rest-api/+package+/app.py_tmpl
new file mode 100644
index 0000000..3eb5edf
--- /dev/null
+++ b/pecan/scaffolds/rest-api/+package+/app.py_tmpl
@@ -0,0 +1,16 @@
+from pecan import make_app
+from ${package} import model
+from ${package}.errors import JSONErrorHook
+
+
+def setup_app(config):
+
+ model.init_model()
+ app_conf = dict(config.app)
+
+ return make_app(
+ app_conf.pop('root'),
+ logging=getattr(config, 'logging', {}),
+ hooks=[JSONErrorHook()],
+ **app_conf
+ )
diff --git a/pecan/scaffolds/rest-api/+package+/controllers/__init__.py b/pecan/scaffolds/rest-api/+package+/controllers/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/pecan/scaffolds/rest-api/+package+/controllers/__init__.py
diff --git a/pecan/scaffolds/rest-api/+package+/controllers/root.py b/pecan/scaffolds/rest-api/+package+/controllers/root.py
new file mode 100644
index 0000000..18ea5dc
--- /dev/null
+++ b/pecan/scaffolds/rest-api/+package+/controllers/root.py
@@ -0,0 +1,44 @@
+from pecan import expose, response, abort
+from pecan.rest import RestController
+
+people = {
+ 1: 'Luke',
+ 2: 'Leia',
+ 3: 'Han',
+ 4: 'Anakin'
+}
+
+
+class PeopleController(RestController):
+
+ @expose('json')
+ def get_all(self):
+ return people
+
+ @expose()
+ def get_one(self, person_id):
+ return people.get(int(person_id)) or abort(404)
+
+ @expose()
+ def post(self):
+ # TODO: Create a new person
+ response.status = 201
+
+ @expose()
+ def put(self, person_id):
+ # TODO: Idempotent PUT (returns 200 or 204)
+ response.status = 204
+
+ @expose()
+ def delete(self, person_id):
+ # TODO: Idempotent DELETE
+ response.status = 200
+
+
+class RootController(object):
+
+ people = PeopleController()
+
+ @expose()
+ def index(self):
+ return "Hello, World!"
diff --git a/pecan/scaffolds/rest-api/+package+/errors.py b/pecan/scaffolds/rest-api/+package+/errors.py
new file mode 100644
index 0000000..4d4d06c
--- /dev/null
+++ b/pecan/scaffolds/rest-api/+package+/errors.py
@@ -0,0 +1,18 @@
+import json
+import webob
+from pecan.hooks import PecanHook
+
+
+class JSONErrorHook(PecanHook):
+ """
+ A pecan hook that translates webob HTTP errors into a JSON format.
+ """
+
+ def on_error(self, state, exc):
+ if isinstance(exc, webob.exc.HTTPError):
+ return webob.Response(
+ body=json.dumps({'reason': str(exc)}),
+ status=exc.status,
+ headerlist=exc.headerlist,
+ content_type='application/json'
+ )
diff --git a/pecan/scaffolds/rest-api/+package+/model/__init__.py b/pecan/scaffolds/rest-api/+package+/model/__init__.py
new file mode 100644
index 0000000..d983f7b
--- /dev/null
+++ b/pecan/scaffolds/rest-api/+package+/model/__init__.py
@@ -0,0 +1,15 @@
+from pecan import conf # noqa
+
+
+def init_model():
+ """
+ This is a stub method which is called at application startup time.
+
+ If you need to bind to a parsed database configuration, set up tables or
+ ORM classes, or perform any database initialization, this is the
+ recommended place to do it.
+
+ For more information working with databases, and some common recipes,
+ see http://pecan.readthedocs.org/en/latest/databases.html
+ """
+ pass
diff --git a/pecan/scaffolds/rest-api/+package+/tests/__init__.py_tmpl b/pecan/scaffolds/rest-api/+package+/tests/__init__.py_tmpl
new file mode 100644
index 0000000..78ea527
--- /dev/null
+++ b/pecan/scaffolds/rest-api/+package+/tests/__init__.py_tmpl
@@ -0,0 +1,22 @@
+import os
+from unittest import TestCase
+from pecan import set_config
+from pecan.testing import load_test_app
+
+__all__ = ['FunctionalTest']
+
+
+class FunctionalTest(TestCase):
+ """
+ Used for functional tests where you need to test your
+ literal application and its integration with the framework.
+ """
+
+ def setUp(self):
+ self.app = load_test_app(os.path.join(
+ os.path.dirname(__file__),
+ 'config.py'
+ ))
+
+ def tearDown(self):
+ set_config({}, overwrite=True)
diff --git a/pecan/scaffolds/rest-api/+package+/tests/config.py_tmpl b/pecan/scaffolds/rest-api/+package+/tests/config.py_tmpl
new file mode 100644
index 0000000..09efcb7
--- /dev/null
+++ b/pecan/scaffolds/rest-api/+package+/tests/config.py_tmpl
@@ -0,0 +1,19 @@
+# Server Specific Configurations
+server = {
+ 'port': '8080',
+ 'host': '0.0.0.0'
+}
+
+# Pecan Application Configurations
+app = {
+ 'root': '${package}.controllers.root.RootController',
+ 'modules': ['${package}'],
+ 'debug': True
+}
+
+# Custom Configurations must be in Python dictionary format::
+#
+# foo = {'bar':'baz'}
+#
+# All configurations are accessible at::
+# pecan.conf
diff --git a/pecan/scaffolds/rest-api/+package+/tests/test_functional.py_tmpl b/pecan/scaffolds/rest-api/+package+/tests/test_functional.py_tmpl
new file mode 100644
index 0000000..f1ac12c
--- /dev/null
+++ b/pecan/scaffolds/rest-api/+package+/tests/test_functional.py_tmpl
@@ -0,0 +1,37 @@
+import json
+from ${package}.tests import FunctionalTest
+
+
+class TestRootController(FunctionalTest):
+
+ def test_get_all(self):
+ response = self.app.get('/people/')
+ assert response.status_int == 200
+ assert response.namespace[1] == 'Luke'
+ assert response.namespace[2] == 'Leia'
+ assert response.namespace[3] == 'Han'
+ assert response.namespace[4] == 'Anakin'
+
+ def test_get_one(self):
+ response = self.app.get('/people/1/')
+ assert response.status_int == 200
+ assert response.body.decode() == 'Luke'
+
+ def test_post(self):
+ response = self.app.post('/people/')
+ assert response.status_int == 201
+
+ def test_put(self):
+ response = self.app.put('/people/1')
+ assert response.status_int == 204
+
+ def test_delete(self):
+ response = self.app.delete('/people/1')
+ assert response.status_int == 200
+
+ def test_not_found(self):
+ response = self.app.get('/missing/', expect_errors=True)
+ assert response.status_int == 404
+ assert json.loads(response.body.decode()) == {
+ 'reason': 'The resource could not be found.'
+ }
diff --git a/pecan/scaffolds/rest-api/+package+/tests/test_units.py b/pecan/scaffolds/rest-api/+package+/tests/test_units.py
new file mode 100644
index 0000000..573fb68
--- /dev/null
+++ b/pecan/scaffolds/rest-api/+package+/tests/test_units.py
@@ -0,0 +1,7 @@
+from unittest import TestCase
+
+
+class TestUnits(TestCase):
+
+ def test_units(self):
+ assert 5 * 5 == 25
diff --git a/pecan/scaffolds/rest-api/config.py_tmpl b/pecan/scaffolds/rest-api/config.py_tmpl
new file mode 100644
index 0000000..bd4d29d
--- /dev/null
+++ b/pecan/scaffolds/rest-api/config.py_tmpl
@@ -0,0 +1,41 @@
+# Server Specific Configurations
+server = {
+ 'port': '8080',
+ 'host': '0.0.0.0'
+}
+
+# Pecan Application Configurations
+app = {
+ 'root': '${package}.controllers.root.RootController',
+ 'modules': ['${package}'],
+ 'debug': True
+}
+
+logging = {
+ 'loggers': {
+ 'root': {'level': 'INFO', 'handlers': ['console']},
+ '${package}': {'level': 'DEBUG', 'handlers': ['console']},
+ 'py.warnings': {'handlers': ['console']},
+ '__force_dict__': True
+ },
+ 'handlers': {
+ 'console': {
+ 'level': 'DEBUG',
+ 'class': 'logging.StreamHandler',
+ 'formatter': 'simple'
+ }
+ },
+ 'formatters': {
+ 'simple': {
+ 'format': ('%(asctime)s %(levelname)-5.5s [%(name)s]'
+ '[%(threadName)s] %(message)s')
+ }
+ }
+}
+
+# Custom Configurations must be in Python dictionary format::
+#
+# foo = {'bar':'baz'}
+#
+# All configurations are accessible at::
+# pecan.conf
diff --git a/pecan/scaffolds/rest-api/setup.cfg_tmpl b/pecan/scaffolds/rest-api/setup.cfg_tmpl
new file mode 100644
index 0000000..111f7cc
--- /dev/null
+++ b/pecan/scaffolds/rest-api/setup.cfg_tmpl
@@ -0,0 +1,6 @@
+[nosetests]
+match=^test
+where=${package}
+nocapture=1
+cover-package=${package}
+cover-erase=1
diff --git a/pecan/scaffolds/rest-api/setup.py_tmpl b/pecan/scaffolds/rest-api/setup.py_tmpl
new file mode 100644
index 0000000..ec47896
--- /dev/null
+++ b/pecan/scaffolds/rest-api/setup.py_tmpl
@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+try:
+ from setuptools import setup, find_packages
+except ImportError:
+ from ez_setup import use_setuptools
+ use_setuptools()
+ from setuptools import setup, find_packages
+
+setup(
+ name='${package}',
+ version='0.1',
+ description='',
+ author='',
+ author_email='',
+ install_requires=[
+ "pecan",
+ ],
+ test_suite='${package}',
+ zip_safe=False,
+ include_package_data=True,
+ packages=find_packages(exclude=['ez_setup'])
+)
diff --git a/pecan/tests/scaffold_builder.py b/pecan/tests/scaffold_builder.py
index 563e762..2c403ae 100644
--- a/pecan/tests/scaffold_builder.py
+++ b/pecan/tests/scaffold_builder.py
@@ -58,9 +58,8 @@ if __name__ == '__main__':
try:
# ...and that it's serving (valid) content...
resp = urlopen('http://localhost:8080/')
- assert resp.getcode() == 200
- assert 'This is a sample Pecan project.' in \
- resp.read().decode()
+ assert resp.getcode()
+ assert len(resp.read().decode())
except URLError:
pass
else:
@@ -112,9 +111,8 @@ if __name__ == '__main__':
try:
# ...and that it's serving (valid) content...
resp = urlopen('http://localhost:%d/' % port)
- assert resp.getcode() == 200
- assert 'This is a sample Pecan project.' in \
- resp.read().decode()
+ assert resp.getcode()
+ assert len(resp.read().decode())
except URLError:
pass
else:
diff --git a/setup.py b/setup.py
index 0f8f982..fa7c87d 100644
--- a/setup.py
+++ b/setup.py
@@ -118,6 +118,7 @@ setup(
create = pecan.commands:CreateCommand
[pecan.scaffold]
base = pecan.scaffolds:BaseScaffold
+ rest-api = pecan.scaffolds:RestAPIScaffold
[console_scripts]
pecan = pecan.commands:CommandRunner.handle_command_line
gunicorn_pecan = pecan.commands.serve:gunicorn_run
diff --git a/tox.ini b/tox.ini
index efa65fb..cbd59ab 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,5 +1,5 @@
[tox]
-envlist = py26,py27,py32,py33,py34,scaffolds-26,scaffolds-27,scaffolds-32,scaffolds-33,scaffolds-34,pep8
+envlist = py26,py27,py32,py33,py34,scaffolds-26,scaffolds-27,scaffolds-32,scaffolds-33,scaffolds-34,scaffolds-26-rest-api,scaffolds-27-rest-api,scaffolds-32-rest-api,scaffolds-33-rest-api,scaffolds-34-rest-api,pep8
[testenv]
commands={envpython} setup.py test -v
@@ -20,6 +20,17 @@ commands=pecan create testing123
pep8 --repeat --show-source testing123/setup.py testing123/testing123
{envpython} {toxinidir}/pecan/tests/scaffold_builder.py
+[testenv:scaffolds-26-rest-api]
+basepython = python2.6
+deps = {[testenv:scaffolds-base]deps}
+ unittest2
+changedir={envdir}/tmp
+commands=pecan create testing123 rest-api
+ {envpython} testing123/setup.py install
+ {envpython} testing123/setup.py test -q
+ pep8 --repeat --show-source testing123/setup.py testing123/testing123
+ {envpython} {toxinidir}/pecan/tests/scaffold_builder.py
+
[testenv:scaffolds-27]
basepython = python2.7
deps = {[testenv:scaffolds-base]deps}
@@ -30,6 +41,16 @@ commands=pecan create testing123
pep8 --repeat --show-source testing123/setup.py testing123/testing123
{envpython} {toxinidir}/pecan/tests/scaffold_builder.py
+[testenv:scaffolds-27-rest-api]
+basepython = python2.7
+deps = {[testenv:scaffolds-base]deps}
+changedir={[testenv:scaffolds-26]changedir}
+commands=pecan create testing123 rest-api
+ {envpython} testing123/setup.py install
+ {envpython} testing123/setup.py test -q
+ pep8 --repeat --show-source testing123/setup.py testing123/testing123
+ {envpython} {toxinidir}/pecan/tests/scaffold_builder.py
+
[testenv:scaffolds-32]
basepython = python3.2
deps = {[testenv:scaffolds-base]deps}
@@ -42,6 +63,18 @@ commands=pecan create testing123
pep8 --repeat --show-source testing123/setup.py testing123/testing123
{envpython} {toxinidir}/pecan/tests/scaffold_builder.py
+[testenv:scaffolds-32-rest-api]
+basepython = python3.2
+deps = {[testenv:scaffolds-base]deps}
+changedir={[testenv:scaffolds-26]changedir}
+commands=pecan create testing123 rest-api
+ curl "http://python-distribute.org/distribute_setup.py" -O
+ {envpython} distribute_setup.py
+ {envpython} testing123/setup.py install
+ {envpython} testing123/setup.py test -q
+ pep8 --repeat --show-source testing123/setup.py testing123/testing123
+ {envpython} {toxinidir}/pecan/tests/scaffold_builder.py
+
[testenv:scaffolds-33]
basepython = python3.3
deps = {[testenv:scaffolds-base]deps}
@@ -54,6 +87,18 @@ commands=pecan create testing123
pep8 --repeat --show-source testing123/setup.py testing123/testing123
{envpython} {toxinidir}/pecan/tests/scaffold_builder.py
+[testenv:scaffolds-33-rest-api]
+basepython = python3.3
+deps = {[testenv:scaffolds-base]deps}
+changedir={[testenv:scaffolds-26]changedir}
+commands=pecan create testing123 rest-api
+ curl "http://python-distribute.org/distribute_setup.py" -O
+ {envpython} distribute_setup.py
+ {envpython} testing123/setup.py install
+ {envpython} testing123/setup.py test -q
+ pep8 --repeat --show-source testing123/setup.py testing123/testing123
+ {envpython} {toxinidir}/pecan/tests/scaffold_builder.py
+
[testenv:scaffolds-34]
basepython = python3.4
deps = {[testenv:scaffolds-base]deps}
@@ -66,6 +111,18 @@ commands=pecan create testing123
pep8 --repeat --show-source testing123/setup.py testing123/testing123
{envpython} {toxinidir}/pecan/tests/scaffold_builder.py
+[testenv:scaffolds-34-rest-api]
+basepython = python3.4
+deps = {[testenv:scaffolds-base]deps}
+changedir={[testenv:scaffolds-26]changedir}
+commands=pecan create testing123 rest-api
+ curl "http://python-distribute.org/distribute_setup.py" -O
+ {envpython} distribute_setup.py
+ {envpython} testing123/setup.py install
+ {envpython} testing123/setup.py test -q
+ pep8 --repeat --show-source testing123/setup.py testing123/testing123
+ {envpython} {toxinidir}/pecan/tests/scaffold_builder.py
+
[testenv:wsme-stable]
basepython = python2.7
deps = nose