Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Request data extractors to allow posting other formats than JSON #192

Merged
merged 1 commit into from
Oct 28, 2013
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks, the tests should have been doing that from the beginning!

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