Cornice provides helpers to build & document Web Services with Pyramid.
The full documentation is available at: https://cornice.readthedocs.io
Build Web Services with Pyramid.
Home Page: https://cornice.readthedocs.io
License: Other
Cornice provides helpers to build & document Web Services with Pyramid.
The full documentation is available at: https://cornice.readthedocs.io
Currently, we're creating rst nodes to create the documentaiton. This means that we have code that's not easy to read and not easy to write. The benefit of this is that we're able to produce valid rst nods and thus that we can generate the documentation in different formats (pdf, html, latex, etc.).
However, we had been thinking with tarek about changing this in favor of a simple templating system, ala jinja.
It could be useful, now that we have a way to describe the APIs with Colander schemas, to have in the documentation example of calls using curl (for instance).
a client with an api and a CLI
# case 1 : static
@service.post(accept=('text/plain', 'application/json'))
def _service(request):
... if accept is in headers and not listed => 406...
# case 2: dynamic
def _get_accept(request):
""" docstring USED by the doc"""
...
return False, accepted
def _get_accept2(request):
""" docstring USED by the doc"""
request.validated['res'] = result
return True, None
@service.post(accept=_get_accept)
def _service(request):
... the decorator calls _get_accept, and returns a 406 if False
... get accept can prepare something to be used by the main function to return a response (response.validated['xxx'] = xxx)
request.response = request.validated['res']
You're unable to specify an acl on a @view decorator for a resource. Here is an example:
@resource(path='/api/users/{username}')
class UserResource(object):
def __init__(self, request):
self.request = request
def auth_user(self, request):
# not called
return True
@view(acl=auth_user)
def get(self):
# expect to have acl already called; not true
This 'acl' actually gets attached when the resource is instantiated (wrapped) in cornice/resource.py:65. However, it never gets called.
from webob import HTTPUnauthorized
doesn't work with the pip installed webob package and does not comply with the example which explicitly uses :
raise exc.HTTPUnauthorized()
Should be replaced with:
from webob import exc
_USERS[user['name']] = user['token']
replace _USERS = [] with _USERS = {} seems to make it, but I haven't dig enough to know if it actually should be a dict or a list :-)
I have a platform for services that hosts Pyramid & Cornice. A dynamic configuration may optionally register REST services written using Cornice. However, if no Cornice services are registered, I get a KeyError
in a Pyramid tween registered by Cornice.
Here's a sample program that reproduces the problem.
from pyramid.config import Configurator
from pyramid.response import Response
from wsgiref.simple_server import make_server
def hello(request):
return Response('Hello, world!')
if __name__ == '__main__':
# Create a Pyramid application that installs
# cornice but creates no cornice services.
application = Configurator()
application.include('cornice')
# Register some normal Pyramid application.
application.add_view(hello, route_name='hello')
application.add_route(name='hello', pattern='/hello')
# Do not install Cornice services.
# Launch the application.
server = make_server('', 8000, application.make_wsgi_app())
server.serve_forever()
You can launch it using:
virtualenv python-env
python-env/Scripts/activate
pip install pyramid
pip install cornice
python cornice-bug.py
Then, open your browser and access:
http://localhost:8000/hello
and you get an HTTP 500 error. The console shows:
KeyError: 'cornice_services'
triggered in cornice/pyramidhook.py
, line 81:
service = request.registry['cornice_services'].get(pattern)
The problem seems to be that the cornice_tween
that processes the handler's response expects the request.registry
to contain a 'cornice_services'
key. However, if register_service_views()
is never called (via the config.add_cornice_service()
directive), this key is never set.
Can you make the tween use registry.get('cornice_services',{})
and skip the tween if the key is missing?
Thanks!
For instance, we should cry if we put "headers" instead of "header" when doing request.errors.add()
.
Pyramid already defines predicated arguments and we're redefining them in cornice, we should rather use them.
I want to use with sphinxcontrib.httpdomain
hello = Service(name='hello', path='/hello',description=desc)
@hello.get()
def get_info(request):
"""
.. http:get:: /api/v1/version/{id:int}
Retrieve a single Version
:query id: A Version id.
** Example response **
But, I can't build.
sphinx-build -b html -d _build/doctrees . _build/html
Running Sphinx v1.1.3
loading pickled environment... not yet created
building [html]: targets for 1 source files that are out of date
updating environment: 1 added, 0 changed, 0 removed
self.env <cornice.ext.sphinxext.Env object at 0x9cc26ec>
Exception occurred:
File "/usr/local/lib/python2.7/dist-packages/sphinxcontrib/httpdomain.py", line 189, in add_target_and_index
self.env.domaindata['http'][self.method][sig] = (self.env.docname, '')
KeyError: 'http'
The full traceback has been saved in /tmp/sphinx-err-fV9Q8W.log, if you want to report the issue to the developers.
Please also report this if it was a user error, so that a better error message can be provided next time.
Either send bugs to the mailing list at <http://groups.google.com/group/sphinx-dev/>,
or report them in the tracker at <http://bitbucket.org/birkenfeld/sphinx/issues/>. Thanks!
In sphinxcontrib/httpdomain.py
def add_target_and_index(self, name_cls, sig, signode):
signode['ids'].append(http_resource_anchor(*name_cls[1:]))
self.env.domaindata['http'][self.method][sig] = (self.env.docname, '')
In cornice/ext/sphinxext.py
class Env(object):
temp_data = {}
docname = ''
domaindata = dict()
def rst2node(data):
"""Converts a reStructuredText into its node
"""
if not data:
return
parser = docutils.parsers.rst.Parser()
document = docutils.utils.new_document('<>')
document.settings = docutils.frontend.OptionParser().get_default_values()
document.settings.tab_width = 4
document.settings.pep_references = False
document.settings.rfc_references = False
document.settings.env = Env()
I don't know both of sphinx,cornice yet. What is problem? :-)
It would be great to be able to write this:
@resource(path='/something')
class MyService(Service):
def __init__(self, request):
self.request = request
@view(validators=['one', 'two']
def get(self):
...
def one(self):
...
def two(self):
...
Currently, validators have to work like (untested, fictive example code):
def user_validator(request):
try:
request.validated['user'] = get_user(request.matchdict['user'])
except KeyError:
request.errors.add('querystring', 'user', 'User field required.')
# -> 400, bad request
except MyNotFound:
raise HTTPNotFound('User {} not found.'.format(user))
# -> 404, not found
To keep it consistent, I’d like to be able to attach a status code to errors so I could write:
request.errors.add('querystring',
'user',
'User {} not found.'.format(user),
status_code=404)
Omitting location
kwarg in Colander SchemaNode
fails in Cornice.
Like this :
class ModelDefinition(MappingSchema):
title = SchemaNode(String())
@model_definition.put(schema=ModelDefinition)
def create_model_definition(request):
...
raises :
File "/home/mle/Code/daybed/env/local/lib/python2.7/site-packages/cornice-0.8-py2.7.egg/cornice/schemas.py", line 24, in get_attributes
return filter(_filter, self._attributes)
File "/home/mle/Code/daybed/env/local/lib/python2.7/site-packages/cornice-0.8-py2.7.egg/cornice/schemas.py", line 21, in _filter
return (attr.location in to_list(location) and
AttributeError: 'SchemaNode' object has no attribute 'location'
body
location (or querystring
) could either be used by default, or raise a more explicit error at instanciation if not any is specified.
we return a nice json mapping on any 400 error
I think it makes sense to have the same structure for all other errors
see: https://github.com/mozilla-services/cornice/blob/master/examples/messaging/messaging/views.py#L21
Right now the validators must return the status and a message, we want something more structurized:
status, message, details
where details is a list of errors the validator found. each error is a dict containing those keys:
The response will be a json object containing all this information.
Cornice returns a 405 if a 404 occurs on a defined path. We need to do this only if the method is not defined because when an app do a 404 on purpose, it's transformed into a 405
example : https://bitbucket.org/tarek/err.li/src/e5116cae1a17/errli/views.py#cl-45
We want to list in the doc everything Cornice does for free when using it (406, xrsf etc
We're using webtest to test the behavior of cornice applications, but we're not explaning how we do that in the documentation. That would be useful to do so for newcomers.
I've tried various ways the get the sphinx-extension working, but nothing seems working.
In the following I can reproduce all the steps I've taken:
the result is an empty block where I expect the autogenerated API
wouter@midgard: /opt/dev/sandbox-cornice > bin/sphinx-quickstart
Welcome to the Sphinx 1.1.3 quickstart utility.
Please enter values for the following settings (just press Enter to
accept a default value, if one is given in brackets).
Enter the root path for documentation.
Root path for the documentation [.]: docs
You have two options for placing the build directory for Sphinx output.
Either, you use a directory "_build" within the root path, or you separate
"source" and "build" directories within the root path.
Separate source and build directories (y/N) [n]: n
Inside the root directory, two more directories will be created; "_templates"
for custom HTML templates and "_static" for custom stylesheets and other static
files. You can enter another prefix (such as ".") to replace the underscore.
Name prefix for templates and static dir [_]:
The project name will occur in several places in the built documentation.
Project name: Demo
Author name(s): Wouter
Sphinx has the notion of a "version" and a "release" for the
software. Each version can have multiple releases. For example, for
Python the version is something like 2.5 or 3.0, while the release is
something like 2.5.1 or 3.0a1. If you don't need this dual structure,
just set both to the same value.
Project version: 0.1
Project release [0.1]: 0.1
The file name suffix for source files. Commonly, this is either ".txt"
or ".rst". Only files with this suffix are considered documents.
Source file suffix [.rst]:
One document is special in that it is considered the top node of the
"contents tree", that is, it is the root of the hierarchical structure
of the documents. Normally, this is "index", but if your "index"
document is a custom template, you can also set this to another filename.
Name of your master document (without suffix) [index]:
Sphinx can also add configuration for epub output:
Do you want to use the epub builder (y/N) [n]:
Please indicate if you want to use one of the following Sphinx extensions:
autodoc: automatically insert docstrings from modules (y/N) [n]:
doctest: automatically test code snippets in doctest blocks (y/N) [n]:
intersphinx: link between Sphinx documentation of different projects (y/N) [n]:
todo: write "todo" entries that can be shown or hidden on build (y/N) [n]:
coverage: checks for documentation coverage (y/N) [n]:
pngmath: include math, rendered as PNG images (y/N) [n]:
mathjax: include math, rendered in the browser by MathJax (y/N) [n]:
ifconfig: conditional inclusion of content based on config values (y/N) [n]:
viewcode: include links to the source code of documented Python objects (y/N) [n]:
A Makefile and a Windows command file can be generated for you so that you
only have to run e.g. `make html' instead of invoking sphinx-build
directly.
Create Makefile? (Y/n) [y]:
Create Windows command file? (Y/n) [y]: n
Creating file docs/conf.py.
Creating file docs/index.rst.
Creating file docs/Makefile.
Finished: An initial directory structure has been created.
You should now populate your master file docs/index.rst and create other documentation
source files. Use the Makefile to build the docs, like so:
make builder
where "builder" is one of the supported builders, e.g. html, latex or linkcheck.
wouter@midgard: /opt/dev/sandbox-cornice >
vi Makefile # so we don"t have to activate thevirtual environment
SPHINXBUILD = /opt/dev/sandbox-cornice/bin/sphinx-buildvi conf.py
>>> import cornice
>>> sys.path.insert(0, os.path.abspath(cornice.__file__))
>>> extensions = ['cornice.sphinxext']
vi index.rst
.. services::
📦 cornice.tests.validationappmake html
the results:
cornice is found, includeme is run, add_apidoc is indeed called,
but apidocs in _get_services is returning an empty dict
def _get_services(self, package, ignore):
from pyramid.config import Configurator
conf = Configurator()
conf.include('cornice')
conf.scan(package, ignore=ignore)
by_service = defaultdict(dict)
apidocs = conf.registry.settings.get('apidocs', {}) <-- this is always an empty dict
I don't see what I'm missing here.
However creating an application and getting the apidoc from the request does not have this problem:
cd /opt/dev/sandbox-cornice
bin/paster create --t cornice demo
Enter appname (Application name) ['']: Demo
Enter description (One-line description of the project) ['']: Demo
Enter author (Author name) ['']: Woutercd demo
/opt/dev/sandbox-cornice/bin/python setup.py develop
/opt/dev/sandbox-cornice/bin/paster serve demo.ini
--> no problems running the applicationvi demo/views.py
9 @hello.get()
10 def get_info(request):
11 """Returns Hello in JSON."""
12 print 'APIDOC: ', request.registry.settings.get('apidocs')
13 return {'Hello': 'World'}
~/opt/dev/sandbox-cornice/bin/paster serve demo.ini
APIDOC:
{('/', 'GET'): {'filters': [<function filter_json_xsrf at 0x1b05758>], 'service': , 'request_method': 'GET', 'renderer': 'simplejson', 'func': <function get_info at 0x1b68cf8>, 'validators': []}}
23:48 < tarek> we can add a list of them in cornice for sure,
23:48 < tarek> stuff like 'invalid json' is something that would be better of an error code
23:48 @mconnor tarek: mostly it's "if I get HTTP 400, with error 6, it should always be 'JSON parse failure' and not some other state"
the code that registers view is insanely complicated. In order to implenet Issue #39, we should refactor it:
Cornice uses venusian's scan against the package to build the API doc.
Sometimes you need to ignore some modules
Let's add an ignore option:
http://docs.pylonsproject.org/projects/venusian/en/latest/?awesome#ignore-scan-argument
The way that the schema's attributes are being handled in sphinxext.py is with:
if 'schema' in args:
schema = args['schema']
attrs_node = nodes.inline()
for location in ('headers', 'querystring', 'body'):
attributes = schema.get_attributes(location=location)
if attributes:
attrs_node += nodes.inline(
text='values in the %s' % location)
location_attrs = nodes.bullet_list()
for attr in attributes:
temp = nodes.list_item()
desc = "%s : " % attr.name
if hasattr(attr, 'type'):
desc += " %s, " % attr.type
The problem is that Colander's node objects do not have a 'type' property, and so sphinx is never adding the type to docs.
These objects do, however, have an attribute called 'typ', which is a colander data-type object. We can then use isinstance() to check which data-type the schema node is representing.
I would like to re-factor to something like the following:
if 'schema' in args:
schema = args['schema']
attrs_node = nodes.inline()
for location in ('headers', 'querystring', 'body'):
attributes = schema.get_attributes(location=location)
if attributes:
attrs_node += nodes.inline(
text='values in the %s' % location)
location_attrs = nodes.bullet_list()
for attr in attributes:
temp = nodes.list_item()
desc = "%s : " % attr.name
if hasattr(attr, 'type'):
# I'm not sure where the type property is at all in colander but leave it in as to not risk breaking anything
attr_type = attr.type
elif hasattr(attr, 'typ'):
attr_type = self._colander_data_type(attr.typ)
desc += " %s, " % attr_type
And a new protected method to determine the data-type:
def _colander_data_type(self, typ):
"""
Get the data-type for an object in a colander schema node.
"""
if isinstance(typ, colander.Mapping):
data_type = 'mapping'
elif isinstance(typ, colander.Tuple):
data_type = 'tuple'
elif isinstance(typ, colander.Sequence):
data_type = 'sequence'
elif isinstance(typ, colander.Seq):
data_type = 'sequence'
elif isinstance(typ, colander.String):
data_type = 'string'
elif isinstance(typ, colander.Integer):
data_type = 'integer'
elif isinstance(typ, colander.Int):
data_type = 'integer'
elif isinstance(typ, colander.Float):
data_type = 'float'
elif isinstance(typ, colander.Decimal):
data_type = 'decimal'
elif isinstance(typ, colander.Boolean):
data_type = 'boolean'
elif isinstance(typ, colander.Bool):
data_type = 'boolean'
elif isinstance(typ, colander.GlobalObject):
data_type = 'global'
elif isinstance(typ, colander.DateTime):
data_type = 'datetime'
elif isinstance(typ, colander.Date):
data_type = 'date'
elif isinstance(typ, colander.Time):
data_type = 'time'
return data_type
This could be helpful with creating a sample curl request and help with generating the full hierarchy of a schema for requests containing JSON, which is another issue ;)
As a larger architectural thought, I wonder if we should pass the service object into each validator by default. IOW, make the signature of a validator function "validate(service, request)" or similar. How common is it to make a validator that closes over the service object like this?
Right now the errors look like this:
[{"location": "url", "name": "delete", "description": "Delete must be 'false' if specified."}]
Ideally it should look like:
{'status': 'error',
'errors': [{"location": "url", "name": "delete", "description": "Delete must be 'false' if specified."}]
}
So that one can always look to see what the status is, and read the errors if applicable.
we need to explain in the doc our errors json standard
https://github.com/mozilla-services/cornice/blob/master/cornice/schemas.py#L42
It would be good if we could use schema binding
http://docs.pylonsproject.org/projects/colander/en/latest/binding.html#what-is-schema-binding
I had a field that had to validate based on some other schema value, and I solved this by using mixed approach passing both schema validator and a callable, but having deferred validators would allow us to have uniform approach to the problem.
Cornice could bind the schema by default.
Could help to have versionned APIs for instance
We want to allow validators for all apis of a service
It would be really helpful if we had examples that could be copy-paste-run to see how each feature is meant to be used, from both a service and resource approach.
Documentation that confuses me:
Doing validation and filtering at class level If you want to use class methods to do validation, you can do so by passing the klass parameter to the hook_view or @method decorators, plus a string representing the name of the method you want to invoke on validation. This means something like this: class MyClass: def validate_it(request): # put the validation logic here @service.get(klass=MyClass, validators=('validate_it',)) def view(request): # do something with the request
How does the validation work in the method validate_it? Do I just add errors to the request? What gets returned (if anything)? If I try to fill in the blanks and run it I get:
File "/local/lib/python2.7/site-packages/cornice/service.py", line 320, in wrapper ob = args['klass'](request) TypeError: this constructor takes no arguments
If I add an init method the request never makes it back to my view. Is this a bug in Cornice or am I not using klass correctly?
Another Example:
Managing ACLs You can also specify a way to deal with ACLs: pass in a function that takes a request and returns an ACL, and that ACL will be applied to all views in the service: foo = Service(name='foo', path='/foo', acl=_check_acls)
What goes in _check_acls? What would be an acceptable ACE? How do I tie an ACE to an http method? What if I'm using resources instead of services?
A working example for each feature documented, no matter how simple, would help.
SPORE provides a way to test that the generated descriptions are valid according to a particular schema.
This needs https://github.com/rjbs/rx to check that the schema is valid. @Natim is currently packaging rx for python, then we will be able to test that the generated description is valid according to this schema.
Hi. _USERS should be set to an empty dict in the messaging example, not an empty list.
With Pyramid 1.3, and venusian==1.0a5, the ignore (which seems to be [''] by default, totally ignores every service.
This method is called in _get_services() somewhere in sphinxext.py.
Probably we could just ignore the empty string when populating the 'ignore' list in run(), near the top.
Following André Caron's mail on services-dev, here is an issue about the registration order of the views when using the resource feature.
Here is the complete mail
We're experimenting with Cornice to expose some REST APIs and I
found the following limitation: if we register multiple services
with the "@resource" decorator, we get this weird situation where
some URLs are not directed to the right views.After some investigation into Pyramid internals, we extracted
the (ordered) list of URLs like so:routes = configurator.get_routes_mapper().get_routes()
for route in routes:
logger.debug('WebServices: URL "%s" -> method "%s".',
route.pattern, route.name)This shows that URLs appear ordered within a single service, but
services don't appear in the order they are defined within the
module (all the services are in the same file for the moment).
This causes some more general URL patterns to appear first in the
list and they capture URLs destined to other views.This seems to be caused by Venusian, which does not call the
deferred decorators in the original sequence.I know this issue is rather subtle and conflicting URL patterns
is not the ideal situation, but I think that the Cornice
documentation should warn against using "@resource" decorators on
a large scale and should recommend using the
"config.add_cornice_service()" explicitly instead.Note that even if Venusian respected the class declaration order
within a single module, the problem could still occur on a larger
scale (module import order would still influence the order of URL
patterns).You might be interested in looking at the routing system used by
Werkzeug since it provides some automatic URL pattern ordering to
avoid this confusion:Flask uses the Werkzeug routing system which has was designed
to automatically order routes by complexity. This means that
you can declare routes in arbitrary order and they will still
work as expected. This is a requirement if you want to
properly implement decorator based routing since decorators
could be fired in undefined order when the application is
split into multiple modules.
When registering the NotFound view, webob.exc.HTTNotFound is used as context. This will not work as expected. Using pyramid.httpexceptions.HTTPNotFound as context does give the expected result.
IE has some rather unfortunately content-type-sniffing behaviour that can be used to trigger XSS attacks via a JSON API, as described here:
http://blog.watchfire.com/wfblog/2011/10/json-based-xss-exploitation.html
https://superevr.com/blog/2012/exploiting-xss-in-ajax-web-applications/
It would be interesting to try to make cornice safe-by-default against this attacked, e.g. by automatically including X-Content-Type-Options header that disables this sniffing.
drop a warning if a service sends back a json array
currently, the setup.py needs pyramid and sphinx to be installed. As the last modifications allow to not use pyramid, and because the sphinx plugin is optional, it could make sense to not have these dependencies as hard deps.
I propose to remove these and to add some try/import/except importerror statements in the code.
What do you think?
It'd be great if it were possible to register validators on Resources.
Consider the following definition :
class ModelField(MappingSchema):
name = SchemaNode(String())
description = SchemaNode(String())
class ModelFields(SequenceSchema):
field = ModelField()
class ModelDefinition(MappingSchema):
title = SchemaNode(String(), location="body")
fields = ModelFields(validator=Length(min=1), location="body")
Posting this valid data poses no problem :
{"title": "Mushroom", "fields": [{"name": "genre", "description": "Genre"}]}
Posting data with error in the nested field schema, like this :
{"title": "Mushroom", "fields": [{"schmil": "Blick"}]}
Fails with a keyerror in Cornice :
File "/home/mle/Code/daybed/env/local/lib/python2.7/site-packages/pyramid-1.3-py2.7.egg/pyramid/config/views.py", line 333, in rendered_view
result = view(context, request)
File "/home/mle/Code/daybed/env/local/lib/python2.7/site-packages/cornice-0.8-py2.7.egg/cornice/service.py", line 30, in call_service
validator(request)
File "/home/mle/Code/daybed/env/local/lib/python2.7/site-packages/cornice-0.8-py2.7.egg/cornice/validators.py", line 52, in validator
_validate_fields('body', body)
File "/home/mle/Code/daybed/env/local/lib/python2.7/site-packages/cornice-0.8-py2.7.egg/cornice/validators.py", line 44, in _validate_fields
request.errors.add(location, attr.name, e.asdict()[attr.name])
KeyError: 'fields'
Full example : https://github.com/spiral-project/daybed/blob/master/HACK.rst
let's do that!
In cases where one doesn't need/want to use cornice's Service API, it'd still be useful to be able to take advantage of consistent validation and error replies for a service view.
Hey guys,
I tried to get resources working as described here: http://cornice.readthedocs.org/en/latest/resources.html
Didn't get it to run though so I tried to run the tests, but the tests that test resources fail as well (all other tests pass)
Python: 2.7.2
Cornice: Latest from github
Test output:
(pylons_pyramid)macbook-7:tmp mfeller$ python -m unittest discover -s cornice/cornice/tests/
Traceback (most recent call last):
File "/private/tmp/cornice/cornice/tests/test_resource.py", line 71, in test_accept_headers
params=json.dumps({'test': 'yeah'})).json,
File "/Users/mfeller/programming/python/virtualenv/pylons_pyramid/lib/python2.7/site-packages/webtest/app.py", line 807, in post
content_type=content_type)
File "/Users/mfeller/programming/python/virtualenv/pylons_pyramid/lib/python2.7/site-packages/webtest/app.py", line 787, in _gen_request
expect_errors=expect_errors)
File "/Users/mfeller/programming/python/virtualenv/pylons_pyramid/lib/python2.7/site-packages/webtest/app.py", line 1075, in do_request
self._check_status(status, res)
File "/Users/mfeller/programming/python/virtualenv/pylons_pyramid/lib/python2.7/site-packages/webtest/app.py", line 1111, in _check_status
res.body))
AppError: Bad response: 404 Not Found (not 200 OK or 3xx redirect for http://localhost/users)
404 Not Found
The resource could not be found.
/users
Traceback (most recent call last):
File "/private/tmp/cornice/cornice/tests/test_resource.py", line 55, in test_basic_resource
self.app.get("/users").json,
File "/Users/mfeller/programming/python/virtualenv/pylons_pyramid/lib/python2.7/site-packages/webtest/app.py", line 753, in get
expect_errors=expect_errors)
File "/Users/mfeller/programming/python/virtualenv/pylons_pyramid/lib/python2.7/site-packages/webtest/app.py", line 1075, in do_request
self._check_status(status, res)
File "/Users/mfeller/programming/python/virtualenv/pylons_pyramid/lib/python2.7/site-packages/webtest/app.py", line 1111, in _check_status
res.body))
AppError: Bad response: 404 Not Found (not 200 OK or 3xx redirect for http://localhost/users)
404 Not Found
The resource could not be found.
/users
Ran 14 tests in 0.194s
FAILED (errors=2)
Is there anything I'm doing wrong?
Cheers,
Martin
In my effort to get cornice to work smoothly with Pyramid's hybrid routing (URL dispatch + Traversal), I've come across the need to put the view decorators on a class instead of a function, like so:
foo = Service(
name='foo',
path='/foo/*traverse',
description='Foo',
factory='foo.RootFactory'
)
@foo.get(context=FooResource)
class ViewFoo(object):
def __init__(self, request):
self.request = request
self.foo = request.context
def __call__(self):
foo_data = self.foo.some_method()
if foo_data:
return {'status': 'OK', 'data': foo_data }
else:
return {'status': 'NOT_FOUND_OR_NOT_ALLOWED' }
The problem is that cornice does not appear to support putting view decorators on classes, only on callables. What I'm doing above is a standard Pyramid idiom for decorating a class with a view configuration, and it makes sense that cornice should support this.
The patch below does seem to fix the issue, but I'm pretty sure that there is a better way to do this. ;) 30 minutes of digging around the Pyramid and Cornice source code did not reveal it, though -- so I'm posting this here as a first effort to get the issue moving:
--- service.py.orig 2012-11-28 10:35:31.000000000 -0800
+++ service.py 2012-11-28 10:36:37.000000000 -0800
@@ -2,6 +2,7 @@
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
# You can obtain one at http://mozilla.org/MPL/2.0/.
import functools
+import inspect
import warnings
from cornice.validators import (
@@ -346,6 +347,10 @@
if len(request.errors) > 0:
return args['error_handler'](request.errors)
+ # if the view decorator was on a class, it still needs to be called.
+ if inspect.isclass(view_):
+ response = response()
+
# We can't apply filters at this level, since "response" may not have
# been rendered into a proper Response object yet. Instead, give the
# request a reference to its api_kwargs so that a tween can apply them.
We need to document all options of the Services class, and the various decorators, including resource
when following the tutorial i get the following error upon starting up the app with bin/pserve
:
# bin/pserve messaging/messaging.ini
Traceback (most recent call last):
File "bin/pserve", line 9, in <module>
load_entry_point('pyramid==1.4a2', 'console_scripts', 'pserve')()
File "/Users/tomster/Development/cornice/examples/lib/python2.7/site-packages/pyramid/scripts/pserve.py", line 47, in main
return command.run()
File "/Users/tomster/Development/cornice/examples/lib/python2.7/site-packages/pyramid/scripts/pserve.py", line 290, in run
relative_to=base, global_conf=vars)
File "/Users/tomster/Development/cornice/examples/lib/python2.7/site-packages/pyramid/scripts/pserve.py", line 318, in loadapp
return loadapp(app_spec, name=name, relative_to=relative_to, **kw)
File "/Users/tomster/Development/cornice/examples/lib/python2.7/site-packages/paste/deploy/loadwsgi.py", line 247, in loadapp
return loadobj(APP, uri, name=name, **kw)
File "/Users/tomster/Development/cornice/examples/lib/python2.7/site-packages/paste/deploy/loadwsgi.py", line 272, in loadobj
return context.create()
File "/Users/tomster/Development/cornice/examples/lib/python2.7/site-packages/paste/deploy/loadwsgi.py", line 710, in create
return self.object_type.invoke(self)
File "/Users/tomster/Development/cornice/examples/lib/python2.7/site-packages/paste/deploy/loadwsgi.py", line 146, in invoke
return fix_call(context.object, context.global_conf, **context.local_conf)
File "/Users/tomster/Development/cornice/examples/lib/python2.7/site-packages/paste/deploy/util.py", line 56, in fix_call
val = callable(*args, **kw)
File "/Users/tomster/Development/cornice/examples/messaging/messaging/__init__.py", line 10, in main
return config.make_wsgi_app()
File "/Users/tomster/Development/cornice/examples/lib/python2.7/site-packages/pyramid/config/__init__.py", line 955, in make_wsgi_app
self.commit()
File "/Users/tomster/Development/cornice/examples/lib/python2.7/site-packages/pyramid/config/__init__.py", line 629, in commit
self.action_state.execute_actions(introspector=self.introspector)
File "/Users/tomster/Development/cornice/examples/lib/python2.7/site-packages/pyramid/config/__init__.py", line 1064, in execute_actions
for action in resolveConflicts(self.actions):
File "/Users/tomster/Development/cornice/examples/lib/python2.7/site-packages/pyramid/config/__init__.py", line 1148, in resolveConflicts
discriminator = undefer(action['discriminator'])
File "/Users/tomster/Development/cornice/examples/lib/python2.7/site-packages/pyramid/registry.py", line 250, in undefer
v = v.resolve()
File "/Users/tomster/Development/cornice/examples/lib/python2.7/site-packages/pyramid/registry.py", line 242, in resolve
return self.func()
File "/Users/tomster/Development/cornice/examples/lib/python2.7/site-packages/pyramid/config/views.py", line 1099, in discrim_func
order, preds, phash = predlist.make(self, **pvals)
File "/Users/tomster/Development/cornice/examples/lib/python2.7/site-packages/pyramid/config/util.py", line 273, in make
raise ConfigurationError('Unknown predicate values: %r' % (kw,))
pyramid.exceptions.ConfigurationError: Unknown predicate values: {'validator': (<function valid_token at 0x107a4cf50>, <function valid_message at 0x107a4e140>)}
I'm applying the suggestion for site-wide validators, but the following code has no effect:
import cornice.validators
def validate_auth_token(request):
# Validate token found in custom HTTP header.
cornice.validators.DEFAULT_VALIDATORS.append(validate_auth_token)
The validator is not invoked.
Looking at cornice/service.py
shows that the Service.default_validators
is initialized with DEFAULT_VALIDATORS
, but the Service.default_validators
attribute is never used.
pyramid 1.3 no longer uses any paster-commands, but uses pserve/pcreate instead
Example commit for this compatibility
Pylons/pyramid_jqm@9b09087
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.