Skip to content

Commit

Permalink
Merge pull request #192 from makinacorpus/request-data-extractors
Browse files Browse the repository at this point in the history
Request data extractors to allow posting other formats than JSON
  • Loading branch information
almet committed Oct 28, 2013
2 parents 25c734d + b33c108 commit 9bd011b
Show file tree
Hide file tree
Showing 10 changed files with 289 additions and 142 deletions.
4 changes: 3 additions & 1 deletion CHANGES.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
0.16 - XXXX-XX-XX
=================

-
- Add support for validation of input content other than JSON against Colander
schemas: built-in support of form-urlencoded and configuration hooks for
other content types

0.15 - 2013-10-09
=================
Expand Down
7 changes: 6 additions & 1 deletion cornice/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
from cornice.pyramidhook import (
wrap_request,
register_service_views,
handle_exceptions
handle_exceptions,
add_deserializer,
)
from cornice.util import ContentTypePredicate

Expand Down Expand Up @@ -38,10 +39,14 @@ def includeme(config):

#config.add_directive('add_apidoc', add_apidoc)
config.add_directive('add_cornice_service', register_service_views)
config.add_directive('add_cornice_deserializer', add_deserializer)
config.add_subscriber(add_renderer_globals, BeforeRender)
config.add_subscriber(wrap_request, NewRequest)
config.add_renderer('simplejson', util.json_renderer)
config.add_view_predicate('content_type', ContentTypePredicate)
config.add_cornice_deserializer('application/x-www-form-urlencoded',
util.extract_form_urlencoded_data)
config.add_cornice_deserializer('application/json', util.extract_json_data)

settings = config.get_settings()
if settings.get('handle_exceptions', True):
Expand Down
19 changes: 16 additions & 3 deletions cornice/pyramidhook.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@

from cornice.service import decorate_view
from cornice.errors import Errors
from cornice.util import is_string, to_list, match_accept_header, \
match_content_type_header, content_type_matches
from cornice.util import (
is_string, to_list, match_accept_header, match_content_type_header,
content_type_matches,
)
from cornice.cors import (
get_cors_validator,
get_cors_preflight_view,
Expand Down Expand Up @@ -174,7 +176,7 @@ def register_service_views(config, service):
decorated_view = decorate_view(view, dict(args), method)

for item in ('filters', 'validators', 'schema', 'klass',
'error_handler') + CORS_PARAMETERS:
'error_handler', 'deserializer') + CORS_PARAMETERS:
if item in args:
del args[item]

Expand Down Expand Up @@ -296,3 +298,14 @@ def _mungle_view_args(args, predicate_list):
else:
# otherwise argument value is just a scalar
args[kind] = value


def add_deserializer(config, content_type, deserializer):
registry = config.registry

def callback():
if not hasattr(registry, 'cornice_deserializers'):
registry.cornice_deserializers = {}
registry.cornice_deserializers[content_type] = deserializer

config.action(content_type, callable=callback)
4 changes: 4 additions & 0 deletions cornice/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -478,6 +478,10 @@ def wrapper(request):
if is_string(view):
view_ = getattr(ob, view.lower())

# set data deserializer
if 'deserializer' in args:
request.deserializer = args['deserializer']

# do schema validation
if 'schema' in args:
validate_colander_schema(args['schema'], request)
Expand Down
6 changes: 6 additions & 0 deletions cornice/tests/test_schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from cornice.errors import Errors
from cornice.tests.support import TestCase
from cornice.schemas import CorniceSchema, validate_colander_schema
from cornice.util import extract_json_data

try:
from colander import (
Expand Down Expand Up @@ -148,6 +149,11 @@ def __init__(self, body):
self.GET = {}
self.POST = {}
self.validated = {}
self.registry = {
'cornice_deserializers': {
'application/json': extract_json_data
}
}

dummy_request = MockRequest('{"bar": "required_data"}')
setattr(dummy_request, 'errors', Errors(dummy_request))
Expand Down
46 changes: 23 additions & 23 deletions cornice/tests/test_service_description.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# 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 json
import warnings

from pyramid import testing
Expand Down Expand Up @@ -107,8 +106,8 @@ def test_schema_validation(self):
# and if we add the required values in the body of the post,
# then we should be good
data = {'foo': 'yeah', 'bar': 'open'}
resp = self.app.post('/foobar?yeah=test',
params=json.dumps(data), status=200)
resp = self.app.post_json('/foobar?yeah=test',
params=data, status=200)

self.assertEqual(resp.json, {u'baz': None, "test": "succeeded"})

Expand All @@ -119,40 +118,41 @@ def test_schema_validation2(self):
def test_bar_validator(self):
# test validator on bar attribute
data = {'foo': 'yeah', 'bar': 'closed'}
resp = self.app.post('/foobar?yeah=test', params=json.dumps(data),
status=400)
resp = self.app.post_json('/foobar?yeah=test', params=data,
status=400)

self.assertEqual(resp.json, {
u'errors': [{u'description': u'The bar is not open.',
u'location': u'body',
u'name': u'bar'}],
u'location': u'body',
u'name': u'bar'}],
u'status': u'error'})

def test_foo_required(self):
# test required attribute
data = {'bar': 'open'}
resp = self.app.post('/foobar?yeah=test', params=json.dumps(data),
status=400)
resp = self.app.post_json('/foobar?yeah=test', params=data,
status=400)

self.assertEqual(resp.json, {
u'errors': [{u'description': u'foo is missing',
u'location': u'body',
u'name': u'foo'}],
u'location': u'body',
u'name': u'foo'}],
u'status': u'error'})

def test_default_baz_value(self):
# test required attribute
data = {'foo': 'yeah', 'bar': 'open'}
resp = self.app.post('/foobar?yeah=test', params=json.dumps(data),
status=200)
resp = self.app.post_json('/foobar?yeah=test', params=data,
status=200)

self.assertEqual(resp.json, {u'baz': None, "test": "succeeded"})

def test_ipsum_error_message(self):
# test required attribute
data = {'foo': 'yeah', 'bar': 'open', 'ipsum': 5}
resp = self.app.post('/foobar?yeah=test', params=json.dumps(data),
status=400)
resp = self.app.post_json('/foobar?yeah=test',
params=data,
status=400)

self.assertEqual(resp.json, {
u'errors': [
Expand All @@ -165,8 +165,8 @@ def test_integers_fail(self):
# test required attribute
data = {'foo': 'yeah', 'bar': 'open', 'ipsum': 2,
'integers': ('a', '2')}
resp = self.app.post('/foobar?yeah=test', params=json.dumps(data),
status=400)
resp = self.app.post_json('/foobar?yeah=test', data,
status=400)

self.assertEqual(resp.json, {
u'errors': [
Expand All @@ -179,8 +179,8 @@ def test_integers_ok(self):
# test required attribute
data = {'foo': 'yeah', 'bar': 'open', 'ipsum': 2,
'integers': ('1', '2')}
self.app.post('/foobar?yeah=test', params=json.dumps(data),
status=200)
self.app.post_json('/foobar?yeah=test', params=data,
status=200)

def test_nested_schemas(self):

Expand All @@ -190,9 +190,9 @@ def test_nested_schemas(self):
nested_data = {"title": "Mushroom",
"fields": [{"schmil": "Blick"}]}

self.app.post('/nested', params=json.dumps(data), status=200)
self.app.post('/nested', params=json.dumps(nested_data),
status=400)
self.app.post_json('/nested', params=data, status=200)
self.app.post_json('/nested', params=nested_data,
status=400)

def test_qux_header(self):
resp = self.app.delete('/foobar', status=400)
Expand All @@ -204,4 +204,4 @@ def test_qux_header(self):
u'status': u'error'})

self.app.delete('/foobar', headers={'X-Qux': 'Hotzenplotz'},
status=200)
status=200)
Loading

0 comments on commit 9bd011b

Please sign in to comment.