Usage

To use JSON models in a project:

import jsonmodels

Creating models

To create models you need to create class that inherits from jsonmodels.models.Base (and NOT jsonmodels.models.PreBase to which although refers links in documentation) and have class attributes which values inherits from jsonmodels.fields.BaseField (so all other fields classes from jsonmodels.fields).

class Cat(models.Base):

    name = fields.StringField(required=True)
    breed = fields.StringField()


class Dog(models.Base):

    name = fields.StringField(required=True)
    age = fields.IntField()


class Car(models.Base):

    registration_number = fields.StringField(required=True)
    engine_capacity = fields.FloatField()
    color = fields.StringField()


class Person(models.Base):

    name = fields.StringField(required=True)
    surname = fields.StringField(required=True)
    car = fields.EmbeddedField(Car)
    pets = fields.ListField([Cat, Dog])

Usage

After that you can use it as normal object. You can pass kwargs in constructor or jsonmodels.models.PreBase.populate() method.

>>> person = Person(name='Chuck')
>>> person.name
'Chuck'
>>> person.surname
None
>>> person.populate(surname='Norris')
>>> person.surname
'Norris'
>>> person.name
'Chuck'

Validation

You can specify which fields are required, if required value is absent during jsonmodels.models.PreBase.validate() the jsonmodels.error.ValidationError will be raised.

>>> bugs = Person(name='Bugs', surname='Bunny')
>>> bugs.validate()

>>> dafty = Person()
>>> dafty.validate()
*** ValidationError: Field is required!

Note that required fields are not raising error if no value was assigned during initialization, but first try of accessing will raise it.

>>> dafty = Person()
>>> dafty.name
*** ValidationError: Field is required!

Also validation is made every time new value is assigned, so trying assign int to StringField will also raise an error:

>>> dafty.name = 3
*** ValidationError: ('Value is wrong, expected type "basestring"', 3)

During casting model to JSON or JSONSchema explicite validation is always called.

Validators

Validators can be passed through validators keyword, as a single validator, or list of validators (so, as you may be expecting, you can’t pass object that extends List).

You can try to use validators shipped with this library. To get more details see jsonmodels.validators. Shipped validators affect generated schema out of the box, to use full potential JSON schema gives you.

Custom validators

You can always specify your own validators. Custom validator can be object with validate method (which takes precedence) or function (or callable object).

Each validator must raise exception to indicate validation didn’t pass. Returning values like False won’t have any effect.

>>> class RangeValidator(object):
...
...   def __init__(self, min, max):
...     # Some logic here.
...
...   def validate(self, value):
...     # Some logic here.

>>> def some_validator(value):
...   # Some logic here.

>>> class Person(models.Base):
...
...   name = fields.StringField(required=True, validators=some_validator)
...   surname = fields.StringField(required=True)
...   age = fields.IntField(
...     Car, validators=[some_validator, RangeValidator(0, 100)])

If your validator have method modify_schema you can use it to affect generated schema in any way. Given argument is schema for single field. For example:

>>> class Length(object):
...
... def validate(self, value):
...     # Some logic here.
...
... def modify_schema(self, field_schema):
...     if self.minimum_value:
...         field_schema['minLength'] = self.minimum_value
...
...     if self.maximum_value:
...         field_schema['maxLength'] = self.maximum_value

Default values

You can specify default value for each of field (and this default value will be shown in generated schema). Currently only scalars are accepted and model instances for EmbeddedField, like in example below:

class Pet(models.Base):
    kind = fields.StringField(default="Dog")

class Person(models.Base):
    name = fields.StringField(default="John Doe")
    age = fields.IntField(default=18)
    pet = fields.EmbeddedField(Pet, default=Pet(kind="Cat"))
    profession = fields.StringField(default=None)

With this schema generated look like this:

{
    "type": "object",
    "additionalProperties": false,
    "properties": {
        "age": {
            "type": "number",
            "default": 18
        },
    "name": {
            "type": "string",
            "default": "John Doe"
        },
        "pet": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
                "kind": {
                    "type": "string",
                    "default": "Dog"
                }
            },
            "default": {
                "kind": "Cat"
            }
        },
        "profession": {
            "type": "string",
            "default": null
        }
    }
}

Casting to Python struct (and JSON)

Instance of model can be easy casted to Python struct (and thanks to that, later to JSON). See jsonmodels.models.PreBase.to_struct().

>>> cat = Cat(name='Garfield')
>>> dog = Dog(name='Dogmeat', age=9)
>>> car = Car(registration_number='ASDF 777', color='red')
>>> person = Person(name='Johny', surname='Bravo', pets=[cat, dog])
>>> person.car = car
>>> person.to_struct()
# (...)

Having Python struct it is easy to cast it to JSON.

>>> import json
>>> person_json = json.dumps(person.to_struct())

Creating JSON schema for your model

JSON schema, although it is far more friendly than XML schema still have something in common with its old friend: people don’t like to write it and (probably) they shouldn’t do it or even read it. Thanks to jsonmodels it is possible to you to operate just on models.

>>> person = Person()
>>> schema = person.to_json_schema()

And thats it! You can serve then this schema through your API or use it for validation incoming data.

Different names in structure and objects

In case you want (or you must) use different names in generated/consumed data and its schema you can use name= param for your fields:

class Human(models.Base):

    name = fields.StringField()
    surname = fields.StringField(name='second-name')

The name value will be usable as surname in all places where you are using objects and will be seen as second-name in all structures - so in dict representation and jsonschema.

>>> john = Human(name='John', surname='Doe')
>>> john.surname
'Doe'
>>> john.to_struct()
{'name': 'John', 'second-name': 'Doe'}

Remember that your models must not have conflicting names in a way that it cannot be resolved by model. You can use cross references though, like this:

class Foo(models.Base):

    one = fields.IntField(name='two')
    two = fields.IntField(name='one')

But remember that structure name has priority so with Foo model above you could run into wrong assumptions:

>>> foo = Foo(one=1, two=2)
>>> foo.one
2  # Not 1, like expected
>>> foo.two
1  # Not 2, like expected