Skip to content

Commit

Permalink
Add ability to use custom validation func/class.
Browse files Browse the repository at this point in the history
This allows to use any other validators, like ``fastjsonschema``.
  • Loading branch information
playpauseandstop committed Oct 23, 2016
1 parent eecdc5d commit ad44c68
Show file tree
Hide file tree
Showing 6 changed files with 101 additions and 14 deletions.
11 changes: 11 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
1.0.0 (In Development)
======================

1.0.0a5 (2016-10-23)
====================

- Support validating schema via `fastjsonschema
<http://opensource.seznam.cz/python-fastjsonschema/>`_ or any other validator

1.0.0a4 (2016-09-01)
====================

- Pass ``kwargs`` to ``SentryHandler`` on configuring Sentry logging

1.0.0a3 (2016-08-08)
====================

Expand Down
2 changes: 1 addition & 1 deletion rororo/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,4 @@

__author__ = 'Igor Davydenko'
__license__ = 'BSD'
__version__ = '1.0.0a4'
__version__ = '1.0.0a5'
29 changes: 22 additions & 7 deletions rororo/schemas/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
from jsonschema.exceptions import ValidationError

from .exceptions import Error
from .utils import defaults
from .utils import defaults, validate_func_factory
from .validators import DefaultValidator


Expand All @@ -29,11 +29,14 @@ class Schema(object):

"""Validate request and response data against JSON Schema."""

__slots__ = ('error_class', 'module', 'response_factory',
'validator_class', '_valid_request')
__slots__ = (
'error_class', 'module', 'response_factory', 'validate_func',
'validation_error_class', 'validator_class', '_valid_request',
)

def __init__(self, module, *, response_factory=None, error_class=None,
validator_class=None):
validator_class=DefaultValidator, validate_func=None,
validation_error_class=ValidationError):
"""Initialize Schema object.
:param module: Module contains at least request and response schemas.
Expand All @@ -44,12 +47,24 @@ def __init__(self, module, *, response_factory=None, error_class=None,
:param validator_class:
Validator class to use for validating request and response data.
By default: ``rororo.schemas.validators.DefaultValidator``
:param validate_func:
Validate function to be called for validating request and response
data. Function will receive 2 args: ``schema`` and ``pure_data``.
By default: ``None``
:param validation_error_class:
Error class to be expected in case of validation error. By default:
``jsonschema.exceptions.ValidationError``
"""
self._valid_request = None

self.module = module
self.response_factory = response_factory
self.error_class = error_class
self.validator_class = validator_class or DefaultValidator

self.validator_class = validator_class
self.validate_func = (validate_func or
validate_func_factory(validator_class))
self.validation_error_class = validation_error_class

def make_error(self, message, *, error=None, error_class=None):
"""Return error instantiated from given message.
Expand Down Expand Up @@ -156,8 +171,8 @@ def _validate(self, data, schema):
:param schema: Schema to use for validation.
"""
try:
return self.validator_class(schema).validate(self._pure_data(data))
except ValidationError as err:
return self.validate_func(schema, self._pure_data(data))
except self.validation_error_class as err:
logger.error('Schema validation error',
exc_info=True,
extra={'schema': schema,
Expand Down
15 changes: 15 additions & 0 deletions rororo/schemas/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,18 @@ def defaults(current, *args):
for key, value in data.items():
current.setdefault(key, value)
return current


def validate_func_factory(validator_class):
"""Factory to return default validate function for Schema.
:param validator_class: JSONSchema suitable validator class.
"""
def validate_func(schema, pure_data):
"""Validate schema with given data.
:param schema: Schema representation to use.
:param pure_data: Pure data to validate.
"""
return validator_class(schema).validate(pure_data)
return validate_func
45 changes: 45 additions & 0 deletions tests/test_schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
from random import choice
from unittest import TestCase

import fastjsonschema

from aiohttp import web
from jsonschema.exceptions import ValidationError
from jsonschema.validators import validate
Expand Down Expand Up @@ -225,6 +227,34 @@ def test_schema_null_response_defined(self):
self.test_schema_no_response_defined(schemas.null_response)


class TestSchemaCustomValidation(TestCase):

def test_fastjsonschema(self):
error_class = fastjsonschema.JsonSchemaException
schema = Schema(FastSchemas(),
validate_func=fast_validate,
validation_error_class=error_class)

# Default error
with self.assertRaises(SchemaError):
raise schema.make_error('Dummy error')

# Validation error propagated
self.assertRaises(error_class,
schema.validate_request,
{'name': 'Something'})

# Proper request
data = schema.validate_request({'name': TEST_NAME})
self.assertEqual(data, {'name': TEST_NAME})

# Proper response
# NOTE: fastjsonschema expects number as an int, not float
timestamp = int(time.time())
response = schema.make_response({'name': TEST_NAME, 'time': timestamp})
self.assertEqual(response, {'name': TEST_NAME, 'time': timestamp})


class TestValidator(TestCase):

def test_invalid_default_value(self):
Expand Down Expand Up @@ -280,5 +310,20 @@ def test_defaults_multiple(self):
{'first': 1, 'second': 2, 'third': 2, 'fourth': 3})


class FastSchemas(object):

@property
def request(self):
return fastjsonschema.compile(schemas.index.request)

@property
def response(self):
return fastjsonschema.compile(schemas.index.response)


def fast_validate(schema, data):
return schema(data)


def json_response_factory(data):
return web.Response(text=json.dumps(data), content_type='application/json')
13 changes: 7 additions & 6 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@ envlist = flake8,py34,py35

[testenv]
deps =
aiohttp==0.22.5
aiohttp==1.0.5
coverage==4.2
fastjsonschema==1.0
jsl==0.2.4
jsonschema==2.5.1
multidict==1.2.2
multidict==2.1.2
nose==1.3.7
setenv =
PYTHONPATH=.
Expand All @@ -26,16 +27,16 @@ commands =
skip_install = True
deps =
flake8==3.0.4
flake8-bugbear==16.7.1
flake8-bugbear==16.10.1
flake8-commas==0.1.6
flake8-comprehensions==1.2.1
flake8-docstrings==1.0.2
flake8-import-order==0.9.2
flake8-import-order==0.10
flake8-quotes==0.8.1
mccabe==0.5.2
pep8-naming==0.4.1
pycodestyle==2.0.0
pydocstyle==1.0.0
pyflakes==1.2.3
pydocstyle==1.1.1
pyflakes==1.3.0
commands =
flake8 --statistics rororo/

0 comments on commit ad44c68

Please sign in to comment.