Cornice — web services with Pyramid
by Tarek Ziadé
Since we’ve initially started Cornice at Services, we had more discussion about how we could make it easier for developers to validate an incoming request.
Our goal is :
- to be able to validate a request and if needed, to convert it to specific data structures
- to complete the documentation of our web services in Sphinx with those validation steps
Here’s a concrete example: A PUT request used to create a user in a database should come as a JSON object in the request body, which content would be validated and turned into a Person object, then sent to a SQL backend.
The web service needs in that case to reject any malformed request with a 400, and we’d also want to document this constraint in the web service documentation.
There are some existing tools in the validation arena, like FormEncode or Colander.
FormEncode and Colander both provide data validation via schemas, and FormEncode offers HTMLform generation.
When you need to validate an incoming request in your web service, those tools can fit your needs or simply be overkill. So, we wanted to integrate a validation step in Cornice without forcing the usage of a specific validation tool.
A simple callable
What we did is added a validator option that needs to point to a callable. The callable receives the request object and has to return an HTTP error code followed by a reason in case the request does not comply.
Here’s an example, a GET /foo that will return a 402 if the X-Paid header is missing :
from cornice import Service foo = Service(name='foo', path='/foo') def has_paid(request): """The request must have an X-Paid header containing a token. This header proves the user has paid """ if not 'X-Paid' in request.headers: return 402, 'You need to pay !' @foo.get(validator=has_paid) def get_value(request): """Returns the value. """ return 'Hello'
From there, Cornice will call has_paid prior to get_value. Cornice is also able to build the documentation of the web service, by merging the docstrings of get_value and has_paid.
Sphinx integration
We provide a Sphinx extension for documenting a Cornice based project. You can inject in your Sphinx document the web service description, via a service directive.
.. services:: :package: demoapp
In this example, the directive looks for all Cornice definitions in the demoapp package by scanning it, and injects their documentation in the Sphinx document.
The service directive provides a few options. For instance the name option will let you inject the documentation of one specific web service.
Colander integration
I said earlier simple callables where good enough for simple validation cases. Let’s take a more complex example.
Let’s say, you have a Person schema in Colander, that defines a person’s attributes — See Colander docs for details.
And you want to provide a PUT Web Service to create a person, where the request body is the person data serialized in JSON.
Here’s the full Cornice definition
from cornice import Service from cornice.schemas import save_converted, get_converted def check_person(request): """The request body must be a JSON object describing the Person""" try: person = json.loads(request) except ValueError: return 400, 'Bad Json data!' schema = Person() try: deserialized = schema.deserialized(person) except InvalidError, e: # the struct is invalid return 400, e.message save_converted(request, 'person', deserialized) service = Service(name='person', path='/person/{id}') @service.put(validator=check_person) def data_posted(request): person = get_converted(request, 'person') ... do the work on person ...
In this example, the validator checks that the request body is valid Json, then pass the unserialized mapping to Colander to check that it’s a valid Person record. Last, it uses the save_converted function we provide, to save the Person object into the request.
The web service then can pick it up with the get_converted function.
Next Steps
Our next steps is to build a library of useful built-in validators. Things like:
- is the request body is JSON ?
- do we have param X, if yes is it an integer ?
- any reusable validator we’ll think about
The final goals here are :
- have our web services code written in two phases: the validation phase, and the code itself. Because they are a lot of reusable bits in that first phase.
- reuse as much as possible docstrings to document our web services, to avoid the doc-is-not-up-to-date-anymore plague
You can follow the development of Cornice here: https://github.com/mozilla-services/cornice
And even participate by joining our Mailing List: https://mail.mozilla.org/listinfo/services-dev
Is there a way to enforce pyramid security on this? A quick example of how I currently do this in Pyramid would be:
1. Define a group finder that returns the account the user has access it.
2. On the Account class define an __acl__ that returns a group with its account_id.
3. I would create an Account Factory that gets the object and define the permission on the view that is required. So that a user can only hit data it is allowed to.
I would like to do this for my API as well so that they have an API KEY and from there I can look up their account and do the same access validation as if they were a logged in use on my website.
Here is sample code of what I’m describing:
http://paste2.org/p/1801900
You can pass to the decorator all options you would pass to a view_config decorator. (so permission)
You can also pass the factory to Service, using acl.
Your example should roughly look like this:
account = Service(‘account’, path=”/api/account/{account_id}’, acl=AccountFactory)
@account.get(permission=’access_event’)
def account(self):
return {}
[…] And even participate by joining our Mailing List: https://mail.mozilla.org/listinfo/services-dev Python Read the original post on Planet Python… […]
You comparison of FormEncode with Colander is not correct. FormEncode is in no way restricted to forms, it supports arbitrary nested schemas and works on simple mappings, too.
That’s right, I will correct this statement, thx
When I try to install terraria in the step 4 it gives me :Error parsingC:\windows\Microsoft.NET\Framework\v4.0.30319\config\machine.configParser returned error 0×80004005What am i doing wrong?I’m using Ubuntu 11.04 and wine 1.320 I alredy install the framework: dotNetFx40_Full_x86_x64.exe