diff --git a/test/schemas/country.json b/test/schemas/country.json new file mode 100644 index 0000000..730eb0d --- /dev/null +++ b/test/schemas/country.json @@ -0,0 +1,10 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema", + "name": "Country", + "properties": { + "name": {"type": "string"}, + "population": {"type": "integer"}, + "overlord": { "$ref": "person.json#" } + }, + "additionalProperties": false +} \ No newline at end of file diff --git a/test/schemas/person.json b/test/schemas/person.json new file mode 100644 index 0000000..e517a53 --- /dev/null +++ b/test/schemas/person.json @@ -0,0 +1,10 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema", + "name": "Person", + "type" : "object", + "properties": { + "title": { "type": "string" }, + "firstname": { "type": "string" }, + "lastname": { "type": "string" } + } +} \ No newline at end of file diff --git a/test/test_core.py b/test/test_core.py index 21ceca1..90983de 100644 --- a/test/test_core.py +++ b/test/test_core.py @@ -14,6 +14,7 @@ import copy import unittest +import os import json @@ -40,6 +41,24 @@ complex_fixture = { }, } +parent_fixture = { + 'name': 'Parent', + 'properties': { + 'name': {'type': 'string'}, + 'children': {'type': 'array', 'items': [{'type': 'object'}]} + }, + 'required': ['name', 'children'] +} + +child_fixture = { + 'name': 'Child', + 'properties': { + 'age': {'type':'integer'}, + 'mother': {'type': 'object'} + }, + 'required': ['age', 'mother'] +} + nameless_fixture = { 'properties': { @@ -105,46 +124,46 @@ class TestCore(unittest.TestCase): exc = warlock.InvalidOperation self.assertRaises(exc, sweden.update, {'population': 'N/A'}) self.assertRaises(exc, sweden.update, {'overloard': 'Bears'}) - + def test_naming(self): Country = warlock.model_factory(fixture) - self.assertEqual(Country.__name__, 'Country') - + self.assertEqual('Country', Country.__name__) + Country2 = warlock.model_factory(fixture, name='Country2') - self.assertEqual(Country2.__name__, 'Country2') - + self.assertEqual('Country2', Country2.__name__) + nameless = warlock.model_factory(nameless_fixture) - self.assertEqual(nameless.__name__, 'Model') - + self.assertEqual('Model', nameless.__name__) + nameless2 = warlock.model_factory(nameless_fixture, name='Country3') - self.assertEqual(nameless2.__name__, 'Country3') + self.assertEqual('Country3', nameless2.__name__) def test_deepcopy(self): """Make sure we aren't leaking references.""" Mixmaster = warlock.model_factory(complex_fixture) mike = Mixmaster(sub={'foo': 'mike'}) - self.assertEquals(mike.sub['foo'], 'mike') + self.assertEquals('mike', mike.sub['foo']) mike_1 = mike.copy() mike_1['sub']['foo'] = 'james' - self.assertEquals(mike.sub['foo'], 'mike') + self.assertEquals('mike', mike.sub['foo']) mike_2 = dict(six.iteritems(mike)) mike_2['sub']['foo'] = 'james' - self.assertEquals(mike.sub['foo'], 'mike') + self.assertEquals('mike', mike.sub['foo']) mike_2 = dict(mike.items()) mike_2['sub']['foo'] = 'james' - self.assertEquals(mike.sub['foo'], 'mike') + self.assertEquals('mike', mike.sub['foo']) mike_3_sub = list(six.itervalues(mike))[0] mike_3_sub['foo'] = 'james' - self.assertEquals(mike.sub['foo'], 'mike') + self.assertEquals('mike', mike.sub['foo']) mike_3_sub = list(mike.values())[0] mike_3_sub['foo'] = 'james' - self.assertEquals(mike.sub['foo'], 'mike') + self.assertEquals('mike', mike.sub['foo']) def test_forbidden_methods(self): Country = warlock.model_factory(fixture) @@ -159,7 +178,7 @@ class TestCore(unittest.TestCase): sweden = Country(name='Sweden', population=9379116) sweden['name'] = 'Finland' - self.assertEqual(sweden['name'], 'Finland') + self.assertEqual('Finland', sweden['name']) del sweden['name'] self.assertRaises(AttributeError, getattr, sweden, 'name') @@ -169,7 +188,7 @@ class TestCore(unittest.TestCase): sweden = Country(name='Sweden', population=9379116) sweden.name = 'Finland' - self.assertEqual(sweden.name, 'Finland') + self.assertEqual('Finland', sweden.name) delattr(sweden, 'name') self.assertRaises(AttributeError, getattr, sweden, 'name') @@ -236,3 +255,50 @@ class TestCore(unittest.TestCase): for patch in json.loads(sweden.patch): self.assertTrue(patch in patches) + + def test_resolver(self): + from jsonschema import RefResolver + schemas_path = 'file://' + os.path.join(os.path.dirname(__file__), 'schemas/') + resolver = RefResolver(schemas_path, None) + + country_schema_file = open(os.path.join(os.path.dirname(__file__), 'schemas/') + 'country.json') + person_schema_file = open(os.path.join(os.path.dirname(__file__), 'schemas/') + 'person.json') + + country_schema = json.load(country_schema_file) + person_schema = json.load(person_schema_file) + Country = warlock.model_factory(country_schema, resolver) + Person = warlock.model_factory(person_schema, resolver) + + england = Country( + name="England", + population=53865800, + overlord=Person( + title="Queen", + firstname="Elizabeth", + lastname="Windsor" + ) + ) + expected = { + 'name': 'England', + 'population': 53865800, + 'overlord': { + 'title': 'Queen', + 'lastname': 'Windsor', + 'firstname': 'Elizabeth' + } + } + self.assertEqual(england, expected) + + def test_recursive_models(self): + Parent = warlock.model_factory(parent_fixture) + Child = warlock.model_factory(child_fixture) + + mom = Parent(name='Abby', children=[]) + + teenager = Child(age=15, mother=mom) + toddler = Child(age=3, mother=mom) + + mom.children = [teenager, toddler] + + self.assertEqual(mom.children[0].age, 15) + self.assertEqual(mom.children[1].age, 3) diff --git a/warlock/core.py b/warlock/core.py index affdaa9..c4486c7 100644 --- a/warlock/core.py +++ b/warlock/core.py @@ -19,19 +19,24 @@ import copy from . import model -def model_factory(schema, base_class=model.Model, name=None): +def model_factory(schema, resolver=None, base_class=model.Model, name=None): """Generate a model class based on the provided JSON Schema :param schema: dict representing valid JSON schema :param name: A name to give the class, if `name` is not in `schema` """ schema = copy.deepcopy(schema) + resolver = resolver class Model(base_class): def __init__(self, *args, **kwargs): self.__dict__['schema'] = schema + self.__dict__['resolver'] = resolver base_class.__init__(self, *args, **kwargs) + if resolver is not None: + Model.resolver = resolver + if name is not None: Model.__name__ = name elif 'name' in schema: diff --git a/warlock/model.py b/warlock/model.py index 666128c..1fe2606 100644 --- a/warlock/model.py +++ b/warlock/model.py @@ -137,6 +137,9 @@ class Model(dict): def validate(self, obj): """Apply a JSON schema to an object""" try: - jsonschema.validate(obj, self.schema) + if self.resolver is not None: + jsonschema.validate(obj, self.schema, resolver=self.resolver) + else: + jsonschema.validate(obj, self.schema) except jsonschema.ValidationError as exc: raise exceptions.ValidationError(str(exc))