Add a pecan scaffold for generating a simple REST API.
Change-Id: Iae346dc2d9a9cc52f3c8b4b546793c8d9670f56d Fixes bug 1248822
This commit is contained in:
@@ -1,4 +1,6 @@
|
|||||||
recursive-include pecan/scaffolds/base *
|
recursive-include pecan/scaffolds/base *
|
||||||
include pecan/scaffolds/base/*
|
include pecan/scaffolds/base/*
|
||||||
|
recursive-include pecan/scaffolds/rest-api *
|
||||||
|
include pecan/scaffolds/rest-api/*
|
||||||
include pecan/middleware/resources/*
|
include pecan/middleware/resources/*
|
||||||
include LICENSE README.rst requirements.txt
|
include LICENSE README.rst requirements.txt
|
||||||
|
|||||||
@@ -42,6 +42,10 @@ class BaseScaffold(PecanScaffold):
|
|||||||
_scaffold_dir = ('pecan', os.path.join('scaffolds', 'base'))
|
_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):
|
def copy_dir(source, dest, variables, out_=sys.stdout, i=0):
|
||||||
"""
|
"""
|
||||||
Copies the ``source`` directory to the ``dest`` directory, where
|
Copies the ``source`` directory to the ``dest`` directory, where
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ def init_model():
|
|||||||
"""
|
"""
|
||||||
This is a stub method which is called at application startup time.
|
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
|
ORM classes, or perform any database initialization, this is the
|
||||||
recommended place to do it.
|
recommended place to do it.
|
||||||
|
|
||||||
|
|||||||
0
pecan/scaffolds/rest-api/+package+/__init__.py
Normal file
0
pecan/scaffolds/rest-api/+package+/__init__.py
Normal file
16
pecan/scaffolds/rest-api/+package+/app.py_tmpl
Normal file
16
pecan/scaffolds/rest-api/+package+/app.py_tmpl
Normal file
@@ -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
|
||||||
|
)
|
||||||
44
pecan/scaffolds/rest-api/+package+/controllers/root.py
Normal file
44
pecan/scaffolds/rest-api/+package+/controllers/root.py
Normal file
@@ -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!"
|
||||||
18
pecan/scaffolds/rest-api/+package+/errors.py
Normal file
18
pecan/scaffolds/rest-api/+package+/errors.py
Normal file
@@ -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'
|
||||||
|
)
|
||||||
15
pecan/scaffolds/rest-api/+package+/model/__init__.py
Normal file
15
pecan/scaffolds/rest-api/+package+/model/__init__.py
Normal file
@@ -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
|
||||||
22
pecan/scaffolds/rest-api/+package+/tests/__init__.py_tmpl
Normal file
22
pecan/scaffolds/rest-api/+package+/tests/__init__.py_tmpl
Normal file
@@ -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)
|
||||||
19
pecan/scaffolds/rest-api/+package+/tests/config.py_tmpl
Normal file
19
pecan/scaffolds/rest-api/+package+/tests/config.py_tmpl
Normal file
@@ -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
|
||||||
@@ -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.'
|
||||||
|
}
|
||||||
7
pecan/scaffolds/rest-api/+package+/tests/test_units.py
Normal file
7
pecan/scaffolds/rest-api/+package+/tests/test_units.py
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
from unittest import TestCase
|
||||||
|
|
||||||
|
|
||||||
|
class TestUnits(TestCase):
|
||||||
|
|
||||||
|
def test_units(self):
|
||||||
|
assert 5 * 5 == 25
|
||||||
41
pecan/scaffolds/rest-api/config.py_tmpl
Normal file
41
pecan/scaffolds/rest-api/config.py_tmpl
Normal file
@@ -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
|
||||||
6
pecan/scaffolds/rest-api/setup.cfg_tmpl
Normal file
6
pecan/scaffolds/rest-api/setup.cfg_tmpl
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
[nosetests]
|
||||||
|
match=^test
|
||||||
|
where=${package}
|
||||||
|
nocapture=1
|
||||||
|
cover-package=${package}
|
||||||
|
cover-erase=1
|
||||||
22
pecan/scaffolds/rest-api/setup.py_tmpl
Normal file
22
pecan/scaffolds/rest-api/setup.py_tmpl
Normal file
@@ -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'])
|
||||||
|
)
|
||||||
@@ -58,9 +58,8 @@ if __name__ == '__main__':
|
|||||||
try:
|
try:
|
||||||
# ...and that it's serving (valid) content...
|
# ...and that it's serving (valid) content...
|
||||||
resp = urlopen('http://localhost:8080/')
|
resp = urlopen('http://localhost:8080/')
|
||||||
assert resp.getcode() == 200
|
assert resp.getcode()
|
||||||
assert 'This is a sample Pecan project.' in \
|
assert len(resp.read().decode())
|
||||||
resp.read().decode()
|
|
||||||
except URLError:
|
except URLError:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
@@ -112,9 +111,8 @@ if __name__ == '__main__':
|
|||||||
try:
|
try:
|
||||||
# ...and that it's serving (valid) content...
|
# ...and that it's serving (valid) content...
|
||||||
resp = urlopen('http://localhost:%d/' % port)
|
resp = urlopen('http://localhost:%d/' % port)
|
||||||
assert resp.getcode() == 200
|
assert resp.getcode()
|
||||||
assert 'This is a sample Pecan project.' in \
|
assert len(resp.read().decode())
|
||||||
resp.read().decode()
|
|
||||||
except URLError:
|
except URLError:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
|
|||||||
1
setup.py
1
setup.py
@@ -118,6 +118,7 @@ setup(
|
|||||||
create = pecan.commands:CreateCommand
|
create = pecan.commands:CreateCommand
|
||||||
[pecan.scaffold]
|
[pecan.scaffold]
|
||||||
base = pecan.scaffolds:BaseScaffold
|
base = pecan.scaffolds:BaseScaffold
|
||||||
|
rest-api = pecan.scaffolds:RestAPIScaffold
|
||||||
[console_scripts]
|
[console_scripts]
|
||||||
pecan = pecan.commands:CommandRunner.handle_command_line
|
pecan = pecan.commands:CommandRunner.handle_command_line
|
||||||
gunicorn_pecan = pecan.commands.serve:gunicorn_run
|
gunicorn_pecan = pecan.commands.serve:gunicorn_run
|
||||||
|
|||||||
59
tox.ini
59
tox.ini
@@ -1,5 +1,5 @@
|
|||||||
[tox]
|
[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]
|
[testenv]
|
||||||
commands={envpython} setup.py test -v
|
commands={envpython} setup.py test -v
|
||||||
@@ -20,6 +20,17 @@ commands=pecan create testing123
|
|||||||
pep8 --repeat --show-source testing123/setup.py testing123/testing123
|
pep8 --repeat --show-source testing123/setup.py testing123/testing123
|
||||||
{envpython} {toxinidir}/pecan/tests/scaffold_builder.py
|
{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]
|
[testenv:scaffolds-27]
|
||||||
basepython = python2.7
|
basepython = python2.7
|
||||||
deps = {[testenv:scaffolds-base]deps}
|
deps = {[testenv:scaffolds-base]deps}
|
||||||
@@ -30,6 +41,16 @@ commands=pecan create testing123
|
|||||||
pep8 --repeat --show-source testing123/setup.py testing123/testing123
|
pep8 --repeat --show-source testing123/setup.py testing123/testing123
|
||||||
{envpython} {toxinidir}/pecan/tests/scaffold_builder.py
|
{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]
|
[testenv:scaffolds-32]
|
||||||
basepython = python3.2
|
basepython = python3.2
|
||||||
deps = {[testenv:scaffolds-base]deps}
|
deps = {[testenv:scaffolds-base]deps}
|
||||||
@@ -42,6 +63,18 @@ commands=pecan create testing123
|
|||||||
pep8 --repeat --show-source testing123/setup.py testing123/testing123
|
pep8 --repeat --show-source testing123/setup.py testing123/testing123
|
||||||
{envpython} {toxinidir}/pecan/tests/scaffold_builder.py
|
{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]
|
[testenv:scaffolds-33]
|
||||||
basepython = python3.3
|
basepython = python3.3
|
||||||
deps = {[testenv:scaffolds-base]deps}
|
deps = {[testenv:scaffolds-base]deps}
|
||||||
@@ -54,6 +87,18 @@ commands=pecan create testing123
|
|||||||
pep8 --repeat --show-source testing123/setup.py testing123/testing123
|
pep8 --repeat --show-source testing123/setup.py testing123/testing123
|
||||||
{envpython} {toxinidir}/pecan/tests/scaffold_builder.py
|
{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]
|
[testenv:scaffolds-34]
|
||||||
basepython = python3.4
|
basepython = python3.4
|
||||||
deps = {[testenv:scaffolds-base]deps}
|
deps = {[testenv:scaffolds-base]deps}
|
||||||
@@ -66,6 +111,18 @@ commands=pecan create testing123
|
|||||||
pep8 --repeat --show-source testing123/setup.py testing123/testing123
|
pep8 --repeat --show-source testing123/setup.py testing123/testing123
|
||||||
{envpython} {toxinidir}/pecan/tests/scaffold_builder.py
|
{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]
|
[testenv:wsme-stable]
|
||||||
basepython = python2.7
|
basepython = python2.7
|
||||||
deps = nose
|
deps = nose
|
||||||
|
|||||||
Reference in New Issue
Block a user