Skip to content

Commit

Permalink
Merge branch 'master' of github.com:fuhrysteve/marshmallow-jsonschema…
Browse files Browse the repository at this point in the history
… into propertyOrder

 Conflicts:
	marshmallow_jsonschema/base.py
	tests/test_dump.py
  • Loading branch information
fuhrysteve committed Oct 1, 2016
2 parents 4e3fcb3 + 6f51771 commit 75eaf52
Show file tree
Hide file tree
Showing 8 changed files with 419 additions and 117 deletions.
3 changes: 1 addition & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@ before_install:
- pip install -U pip

install:
- pip install -U .[reco]
- pip install strict-rfc3339 jsonschema coveralls
- make installcheck

script: coverage run --source marshmallow_jsonschema -m py.test
after_success: coveralls
5 changes: 5 additions & 0 deletions CHANGES
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
0.3.0 (2016-06-12)
- add support for marshmallow validators (see #14)

0.2.0 (2016-05-25)
- add support for titles & descriptions in metadata
19 changes: 19 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@


installcheck:
pip install -U .[reco]
pip install strict-rfc3339 jsonschema coveralls coverage

check:
py.test -v

coverage:
coverage erase
coverage run --source marshmallow_jsonschema -m py.test -v
coverage report -m

pypitest:
python setup.py sdist upload -r pypitest

pypi:
python setup.py sdist upload -r pypi
87 changes: 66 additions & 21 deletions marshmallow_jsonschema/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@
import decimal
from collections import OrderedDict

from marshmallow import fields, missing, Schema
from marshmallow.compat import text_type, binary_type
from marshmallow import fields, missing, Schema, validate
from marshmallow.class_registry import get_class
from marshmallow.compat import text_type, binary_type, basestring

from .validation import handle_length, handle_one_of, handle_range


__all__ = ['JSONSchema']
Expand Down Expand Up @@ -67,6 +70,13 @@
}


FIELD_VALIDATORS = {
validate.Length: handle_length,
validate.OneOf: handle_one_of,
validate.Range: handle_range,
}


class JSONSchema(Schema):
properties = fields.Method('get_properties')
type = fields.Constant('object')
Expand All @@ -88,41 +98,76 @@ def get_properties(self, obj):
schema = field._jsonschema_type_mapping()
elif field.__class__ in mapping:
pytype = mapping[field.__class__]
schema = _from_python_type(field, pytype)
schema = self.__class__._from_python_type(field, pytype)
elif isinstance(field, fields.Nested):
schema = _from_nested_schema(field)
schema = self.__class__._from_nested_schema(field)
else:
raise ValueError('unsupported field type %s' % field)
if obj.ordered:
schema['propertyOrder'] = count + 1

# Apply any and all validators that field may have
for validator in field.validators:
if validator.__class__ in FIELD_VALIDATORS:
schema = FIELD_VALIDATORS[validator.__class__](
schema, field, validator, obj
)

properties[field.name] = schema

return properties

def get_required(self, obj):
required = []

for field_name, field in sorted(obj.fields.items()):
if field.required:
required.append(field.name)

return required

@classmethod
def _from_python_type(cls, field, pytype):
json_schema = {
'title': field.attribute or field.name,
}

def _from_python_type(field, pytype):
json_schema = {
'title': field.attribute or field.name,
}
for key, val in TYPE_MAP[pytype].items():
json_schema[key] = val
if field.default is not missing:
json_schema['default'] = field.default
for key, val in TYPE_MAP[pytype].items():
json_schema[key] = val

return json_schema
if field.default is not missing:
json_schema['default'] = field.default

if field.metadata.get('metadata', {}).get('description'):
json_schema['description'] = (
field.metadata['metadata'].get('description')
)

def _from_nested_schema(field):
schema = JSONSchema().dump(field.nested()).data
if field.many:
schema = {
'type': ["array"] if field.required else ['array', 'null'],
'items': schema
}
return schema
if field.metadata.get('metadata', {}).get('title'):
json_schema['title'] = field.metadata['metadata'].get('title')

return json_schema

@classmethod
def _from_nested_schema(cls, field):
if isinstance(field.nested, basestring):
nested = get_class(field.nested)
else:
nested = field.nested
schema = cls().dump(nested()).data

if field.metadata.get('metadata', {}).get('description'):
schema['description'] = (
field.metadata['metadata'].get('description')
)

if field.metadata.get('metadata', {}).get('title'):
schema['title'] = field.metadata['metadata'].get('title')

if field.many:
schema = {
'type': ["array"] if field.required else ['array', 'null'],
'items': schema,
}

return schema
106 changes: 106 additions & 0 deletions marshmallow_jsonschema/validation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
from marshmallow import fields


def handle_length(schema, field, validator, parent_schema):
"""Adds validation logic for ``marshmallow.validate.Length``, setting the
values appropriately for ``fields.List``, ``fields.Nested``, and
``fields.String``.
Args:
schema (dict): The original JSON schema we generated. This is what we
want to post-process.
field (fields.Field): The field that generated the original schema and
who this post-processor belongs to.
validator (marshmallow.validate.Length): The validator attached to the
passed in field.
parent_schema (marshmallow.Schema): The Schema instance that the field
belongs to.
Returns:
dict: A, possibly, new JSON Schema that has been post processed and
altered.
Raises:
ValueError: Raised if the `field` is something other than
`fields.List`, `fields.Nested`, or `fields.String`
"""
if isinstance(field, fields.String):
minKey = 'minLength'
maxKey = 'maxLength'
elif isinstance(field, (fields.List, fields.Nested)):
minKey = 'minItems'
maxKey = 'maxItems'
else:
raise ValueError("In order to set the Length validator for JSON "
"schema, the field must be either a List or a String")

if validator.min:
schema[minKey] = validator.min

if validator.max:
schema[maxKey] = validator.max

if validator.equal:
schema[minKey] = validator.equal
schema[maxKey] = validator.equal

return schema


def handle_one_of(schema, field, validator, parent_schema):
"""Adds the validation logic for ``marshmallow.validate.OneOf`` by setting
the JSONSchema `enum` property to the allowed choices in the validator.
Args:
schema (dict): The original JSON schema we generated. This is what we
want to post-process.
field (fields.Field): The field that generated the original schema and
who this post-processor belongs to.
validator (marshmallow.validate.OneOf): The validator attached to the
passed in field.
parent_schema (marshmallow.Schema): The Schema instance that the field
belongs to.
Returns:
dict: A, possibly, new JSON Schema that has been post processed and
altered.
"""
if validator.choices:
schema['enum'] = validator.choices

return schema


def handle_range(schema, field, validator, parent_schema):
"""Adds validation logic for ``marshmallow.validate.Range``, setting the
values appropriately ``fields.Number`` and it's subclasses.
Args:
schema (dict): The original JSON schema we generated. This is what we
want to post-process.
field (fields.Field): The field that generated the original schema and
who this post-processor belongs to.
validator (marshmallow.validate.Length): The validator attached to the
passed in field.
parent_schema (marshmallow.Schema): The Schema instance that the field
belongs to.
Returns:
dict: A, possibly, new JSON Schema that has been post processed and
altered.
"""
if not isinstance(field, fields.Number):
return schema

if validator.min:
schema['minimum'] = validator.min
schema['exclusiveMinimum'] = True
else:
schema['minimum'] = 0
schema['exclusiveMinimum'] = False

if validator.max:
schema['maximum'] = validator.max
schema['exclusiveMaximum'] = True

return schema
8 changes: 4 additions & 4 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ def read(fname):
try:
import pypandoc
long_description = pypandoc.convert('README.md', 'rst')
except (IOError, ImportError):
except (IOError, ImportError, OSError):
long_description = read('README.md')

setup(
name='marshmallow-jsonschema',
version='0.1.7',
version='0.3.0',
description='JSON Schema Draft v4 (http://json-schema.org/) formatting with marshmallow',
long_description=long_description,
author='Stephen Fuhry',
Expand All @@ -22,8 +22,8 @@ def read(fname):
packages=find_packages(exclude=("test*", )),
package_dir={'marshmallow-jsonschema': 'marshmallow-jsonschema'},
include_package_data=True,
install_requires=['marshmallow>=2.3.0'],
tests_require=['pytest>=2.1', 'jsonschema', 'strict-rfc3339'],
install_requires=['marshmallow>=2.9.0'],
tests_require=['pytest>=2.9.2', 'jsonschema', 'strict-rfc3339', 'coverage>=4.1'],
license=read('LICENSE'),
zip_safe=False,
keywords=('marshmallow-jsonschema marshmallow schema serialization '
Expand Down
9 changes: 6 additions & 3 deletions tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,16 @@ class Address(Schema):
street = fields.String(required=True)
number = fields.String(required=True)
city = fields.String(required=True)
floor = fields.String()
floor = fields.Integer(validate=validate.Range(min=1, max=4))


class GithubProfile(Schema):
uri = fields.String(required=True)


class UserSchema(Schema):
name = fields.String(required=True)
name = fields.String(required=True,
validate=validate.Length(min=1, max=255))
age = fields.Float()
created = fields.DateTime()
created_formatted = fields.DateTime(format="%Y-%m-%d", attribute="created")
Expand All @@ -38,8 +39,10 @@ class UserSchema(Schema):
since_created = fields.TimeDelta()
sex = fields.Str(validate=validate.OneOf(['male', 'female']))
various_data = fields.Dict()
addresses = fields.Nested(Address, many=True)
addresses = fields.Nested(Address, many=True,
validate=validate.Length(min=1, max=3))
github = fields.Nested(GithubProfile)
const = fields.String(validate=validate.Length(equal=50))


class BaseTest(unittest.TestCase):
Expand Down
Loading

0 comments on commit 75eaf52

Please sign in to comment.