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 *
|
||||
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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
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:
|
||||
# ...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:
|
||||
|
||||
1
setup.py
1
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
|
||||
|
||||
59
tox.ini
59
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
|
||||
|
||||
Reference in New Issue
Block a user