Skip to content

Commit

Permalink
Merge pull request #22 from sseg/inheritance
Browse files Browse the repository at this point in the history
Add new inheritance modes
  • Loading branch information
aromanovich committed Apr 24, 2016
2 parents f480cd5 + ceb38d9 commit ec6d994
Show file tree
Hide file tree
Showing 10 changed files with 289 additions and 91 deletions.
4 changes: 4 additions & 0 deletions docs/source/api/document.rst
Expand Up @@ -8,6 +8,10 @@ Document

.. autodata:: ALL_OF

.. autodata:: ANY_OF

.. autodata:: ONE_OF

.. autodata:: INLINE

.. autoclass:: Options
Expand Down
11 changes: 11 additions & 0 deletions docs/source/changelog.rst
@@ -1,6 +1,17 @@
Changelog
=========

Unreleased
----------

- Introduction of two new inheritance modes, oneOf and anyOf, by Steven Seguin.

0.2.2 2016-02-06
----------------

- Documentation fixes by mulhern <amulhern@redhat.com> (issue `#17`_).


0.2.1 2015-11-23
~~~~~~~~~~~~~~~~

Expand Down
7 changes: 4 additions & 3 deletions docs/source/tutorial.rst
Expand Up @@ -331,13 +331,14 @@ Now ``User.get_schema(ordered=True, role=DB_ROLE)`` returns the following schema

Document Inheritance
--------------------
There are two inheritance modes available in JSL: **inline** and **all-of**.
There are four inheritance modes available in JSL: **inline**, **all-of**, **any-of**, and **one-of**.

In the inline mode (used by default), a schema of the child document contains a copy
of its parent's fields.

In the all-of mode a schema of the child document is an allOf validator that contains references
to all parent schemas along with the schema that defines the child's fields.
In the the other three modes a schema of the child document is a validator of the type allOf, anyOf,
or oneOf that contains references to all parent schemas along with the schema that defines the
child's fields.

The inheritance mode can be set using the ``inheritance_mode`` document :class:`option <.Options>`.

Expand Down
2 changes: 1 addition & 1 deletion jsl/__init__.py
Expand Up @@ -16,7 +16,7 @@
__version_info__ = tuple(int(i) for i in __version__.split('.'))


from .document import Document, ALL_OF, INLINE
from .document import Document, ALL_OF, INLINE, ANY_OF, ONE_OF
from .fields import *
from .roles import *
from .exceptions import SchemaGenerationException
40 changes: 27 additions & 13 deletions jsl/document.py
Expand Up @@ -6,7 +6,7 @@
from .fields import BaseField, DocumentField, DictField
from .roles import DEFAULT_ROLE, Var, Scope, all_, construct_matcher, Resolvable, Resolution
from .resolutionscope import ResolutionScope, EMPTY_SCOPE
from ._compat import iteritems, iterkeys, itervalues, with_metaclass, OrderedDict, Prepareable
from ._compat import iteritems, iterkeys, with_metaclass, OrderedDict, Prepareable


def _set_owner_to_document_fields(cls):
Expand All @@ -15,11 +15,19 @@ def _set_owner_to_document_fields(cls):
field.owner_cls = cls


# INHERITANCE CONSTANTS AND MAPPING

INLINE = 'inline' # default inheritance mode
ALL_OF = 'all_of'
"""All-of inheritance mode"""
ANY_OF = 'any_of'
ONE_OF = 'one_of'

INLINE = 'inline'
"""Inline (default) inheritance mode"""
_INHERITANCE_MODES = {
INLINE: 'allOf', # used in the case that an inline class inherits from document bases
ALL_OF: 'allOf',
ANY_OF: 'anyOf',
ONE_OF: 'oneOf'
}


class Options(object):
Expand All @@ -41,7 +49,8 @@ class Options(object):
documents.
:type roles_to_propagate: callable, string or iterable
:param str inheritance_mode:
An :ref:`inheritance mode <inheritance>`: :data:`INLINE` (default) or :data:`ALL_OF`
An :ref:`inheritance mode <inheritance>`: one of :data:`INLINE` (default),
:data:`ALL_OF`, :data:`ANY_OF`, or :data:`ONE_OF`
.. versionadded:: 0.1.4
"""
Expand All @@ -66,10 +75,14 @@ def __init__(self, additional_properties=False, pattern_properties=None,
self.schema_uri = schema_uri
self.definition_id = definition_id
self.roles_to_propagate = construct_matcher(roles_to_propagate or all_)
if inheritance_mode not in (ALL_OF, INLINE):
if inheritance_mode not in _INHERITANCE_MODES:
raise ValueError(
'Unknown inheritance mode: {0!r}. '
'Must be one of the following: {1!r}'.format(inheritance_mode, [INLINE, ALL_OF]))
'Must be one of the following: {1!r}'.format(
inheritance_mode,
sorted([m for m in _INHERITANCE_MODES])
)
)
self.inheritance_mode = inheritance_mode


Expand Down Expand Up @@ -110,7 +123,7 @@ def __new__(mcs, name, bases, attrs):
for base in bases:
if issubclass(base, Document) and base is not Document:
parent_documents.update(base._parent_documents)
elif options.inheritance_mode == ALL_OF:
else:
fields = mcs.collect_fields([], attrs)
parent_documents = [base for base in bases
if issubclass(base, Document) and base is not Document]
Expand Down Expand Up @@ -312,7 +325,7 @@ def walk(cls, through_document_fields=False, visited_documents=frozenset()):
:returns: iterable of :class:`.BaseField`
"""
fields = cls._backend.walk(through_document_fields=through_document_fields,
visited_documents=visited_documents)
visited_documents=visited_documents)
next(fields) # we don't want to yield _field itself
return fields

Expand Down Expand Up @@ -379,16 +392,17 @@ def get_definitions_and_schema(cls, role=DEFAULT_ROLE, res_scope=EMPTY_SCOPE,
role=role, res_scope=res_scope, ordered=ordered, ref_documents=ref_documents)

if cls._parent_documents:
all_of = []
mode = _INHERITANCE_MODES[cls._options.inheritance_mode]
contents = []
for parent_document in cls._parent_documents:
parent_definitions, parent_schema = parent_document.get_definitions_and_schema(
role=role, res_scope=res_scope, ordered=ordered, ref_documents=ref_documents)
parent_definition_id = parent_document.get_definition_id()
definitions.update(parent_definitions)
definitions[parent_definition_id] = parent_schema
all_of.append(res_scope.create_ref(parent_definition_id))
all_of.append(schema)
schema = {'allOf': all_of}
contents.append(res_scope.create_ref(parent_definition_id))
contents.append(schema)
schema = {mode: contents}

if is_recursive:
definition_id = cls.get_definition_id()
Expand Down
18 changes: 9 additions & 9 deletions tests/test_document.py
Expand Up @@ -8,7 +8,7 @@
DateTimeField, ArrayField, OneOfField)
from jsl._compat import OrderedDict, iterkeys

from util import s
from util import normalize


def test_to_schema():
Expand Down Expand Up @@ -53,7 +53,7 @@ class Options(object):
'author': Task.author.get_schema(),
}
}
assert s(Task.get_schema()) == expected_task_schema
assert normalize(Task.get_schema()) == expected_task_schema


def test_document_options():
Expand Down Expand Up @@ -159,7 +159,7 @@ class Options(object):
}
schema = A.get_schema(ordered=True)
assert isinstance(schema, OrderedDict)
assert s(schema) == s(expected_schema)
assert normalize(schema) == normalize(expected_schema)
assert list(iterkeys(schema)) == ['id', '$schema', 'definitions', '$ref']

# let's make sure that all the references in resulting schema
Expand Down Expand Up @@ -237,7 +237,7 @@ class Options(object):
'$ref': '#/definitions/test_document.A',
}
schema = A.get_schema()
assert s(schema) == s(expected_schema)
assert normalize(schema) == normalize(expected_schema)

# let's make sure that all the references in resulting schema
# can be resolved
Expand Down Expand Up @@ -302,7 +302,7 @@ class C(Document):
}
},
}
assert s(Main.get_schema()) == s(expected_schema)
assert normalize(Main.get_schema()) == normalize(expected_schema)


def test_recursive_definitions_4():
Expand Down Expand Up @@ -345,7 +345,7 @@ class B(Document):
'definitions': expected_definitions,
'$ref': '#/definitions/test_document.Main',
}
assert s(Main.get_schema()) == s(expected_schema)
assert normalize(Main.get_schema()) == normalize(expected_schema)

class X(Document):
name = StringField()
Expand Down Expand Up @@ -374,7 +374,7 @@ class Z(Document):
}
},
}
assert s(Z.get_schema()) == s(expected_schema)
assert normalize(Z.get_schema()) == normalize(expected_schema)


def test_recursive_definitions_5():
Expand All @@ -385,7 +385,7 @@ class Options(object):
with Scope('test') as test:
test.field = DocumentField(RECURSIVE_REFERENCE_CONSTANT)

assert s(Test.get_schema(role='test')) == s({
assert normalize(Test.get_schema(role='test')) == normalize({
'$schema': 'http://json-schema.org/draft-04/schema#',
'$ref': '#/definitions/test',
'definitions': {
Expand Down Expand Up @@ -416,7 +416,7 @@ class Options(object):
definition_id = 'a'
derived_from = DocumentField(Children, as_ref=True)

assert s(A.get_schema()) == s({
assert normalize(A.get_schema()) == normalize({
'$schema': 'http://json-schema.org/draft-04/schema#',
'definitions': {
'a': {
Expand Down

0 comments on commit ec6d994

Please sign in to comment.