Skip to content

Commit

Permalink
add support for draft-6 examples
Browse files Browse the repository at this point in the history
  • Loading branch information
proppy committed Jan 28, 2020
1 parent 4d9ee84 commit 75287c3
Show file tree
Hide file tree
Showing 8 changed files with 103 additions and 18 deletions.
6 changes: 4 additions & 2 deletions genson/schema/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,13 +61,15 @@ def add_schema(self, schema):
del schema['$schema']
self._root_node.add_schema(schema)

def add_object(self, obj):
def add_object(self, obj, examples=False):
"""
Modify the schema to accommodate an object.
:param obj: any object or scalar that can be serialized in JSON
:param examples: whether or not to include examples values from the
given object.
"""
self._root_node.add_object(obj)
self._root_node.add_object(obj, examples)

def to_schema(self):
"""
Expand Down
7 changes: 5 additions & 2 deletions genson/schema/node.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,18 +36,21 @@ def add_schema(self, schema):
# return self for easy method chaining
return self

def add_object(self, obj):
def add_object(self, obj, examples=False):
"""
Modify the schema to accommodate an object.
arguments:
* `obj` (required - `dict`):
a JSON object to use in generating the schema.
* `examples` (optional - `bbool`):
whether or not to include examples values from the
given object.
"""

# delegate to SchemaType object
active_strategy = self._get_strategy_for_object(obj)
active_strategy.add_object(obj)
active_strategy.add_object(obj, examples)

# return self for easy method chaining
return self
Expand Down
12 changes: 6 additions & 6 deletions genson/schema/strategies/array.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,9 @@ def add_schema(self, schema):
if 'items' in schema:
self._items.add_schema(schema['items'])

def add_object(self, obj):
def add_object(self, obj, examples=False):
for item in obj:
self._items.add_object(item)
self._items.add_object(item, examples)

def items_to_schema(self):
return self._items.to_schema()
Expand All @@ -63,17 +63,17 @@ def __init__(self, node_class):
def add_schema(self, schema):
self.add_extra_keywords(schema)
if 'items' in schema:
self._add(schema['items'], 'add_schema')
self._add(schema['items'], lambda s, i: s.add_schema(i))

def add_object(self, obj):
self._add(obj, 'add_object')
def add_object(self, obj, examples=False):
self._add(obj, lambda s, i: s.add_object(i, examples))

def _add(self, items, func):
while len(self._items) < len(items):
self._items.append(self.node_class())

for subschema, item in zip(self._items, items):
getattr(subschema, func)(item)
func(subschema, item)

def items_to_schema(self):
return [item.to_schema() for item in self._items]
12 changes: 10 additions & 2 deletions genson/schema/strategies/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ def match_object(cls, obj):
def __init__(self, node_class):
self.node_class = node_class
self._extra_keywords = {}
self._examples = []

def add_schema(self, schema):
self.add_extra_keywords(schema)
Expand All @@ -43,11 +44,18 @@ def add_extra_keywords(self, schema):
'values ({1!r} vs. {2!r}). Using {1!r}').format(
keyword, self._extra_keywords[keyword], value))

def add_object(self, obj):
def add_example(self, example):
if example not in self._examples:
self._examples.append(example)

def add_object(self, obj, examples=False):
pass

def to_schema(self):
return copy(self._extra_keywords)
schema = copy(self._extra_keywords)
if len(self._examples) > 0:
schema["examples"] = copy(self._examples)
return schema

def __eq__(self, other):
""" Required for SchemaBuilder.__eq__ to work properly """
Expand Down
6 changes: 3 additions & 3 deletions genson/schema/strategies/object.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ def add_schema(self, schema):
else:
self._required &= set(schema['required'])

def add_object(self, obj):
def add_object(self, obj, examples=False):
properties = set()
for prop, subobj in obj.items():
pattern = None
Expand All @@ -53,10 +53,10 @@ def add_object(self, obj):
pattern = self._matching_pattern(prop)

if pattern is not None:
self._pattern_properties[pattern].add_object(subobj)
self._pattern_properties[pattern].add_object(subobj, examples)
else:
properties.add(prop)
self._properties[prop].add_object(subobj)
self._properties[prop].add_object(subobj, examples)

if self._required is None:
self._required = properties
Expand Down
8 changes: 7 additions & 1 deletion genson/schema/strategies/scalar.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ class String(TypedSchemaStrategy):
JS_TYPE = 'string'
PYTHON_TYPE = (str, type(u''))

def add_object(self, obj, examples=False):
if examples:
self.add_example(obj)


class Number(SchemaStrategy):
"""
Expand Down Expand Up @@ -73,9 +77,11 @@ def add_schema(self, schema):
if schema.get('type') == 'number':
self._type = 'number'

def add_object(self, obj):
def add_object(self, obj, examples=False):
if isinstance(obj, float):
self._type = 'number'
if examples:
self.add_example(obj)

def to_schema(self):
schema = super(Number, self).to_schema()
Expand Down
4 changes: 2 additions & 2 deletions test/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ def setUp(self):
def set_schema_options(self, **options):
self.builder = SchemaNode(**options)

def add_object(self, obj):
self.builder.add_object(obj)
def add_object(self, obj, examples=False):
self.builder.add_object(obj, examples)
self._objects.append(obj)

def add_schema(self, schema):
Expand Down
66 changes: 66 additions & 0 deletions test/test_gen_single.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ class TestArrayList(base.SchemaNodeTestCase):

def setUp(self):
base.SchemaNodeTestCase.setUp(self)
self.maxDiff = None

def test_empty(self):
self.add_object([])
Expand Down Expand Up @@ -209,3 +210,68 @@ def test_three_deep(self):
}
}
})


class TestExamples(base.SchemaNodeTestCase):

def setUp(self):
base.SchemaNodeTestCase.setUp(self)

def test_empty_examples(self):
self.add_object({}, examples=True)
self.assertResult({"type": "object"})

def test_no_examples(self):
self.add_object(1, examples=False)
self.assertResult({"type": "integer"})

def test_integer(self):
self.add_object(1, examples=True)
self.add_object(2, examples=True)
self.assertResult({"type": "integer", "examples": [1, 2]})

def test_string(self):
self.add_object("foo", examples=True)
self.add_object("bar", examples=True)
self.assertResult({"type": "string", "examples": ["foo", "bar"]})

def test_bool(self):
self.add_object(True, examples=True)
self.assertResult({"type": "boolean"})

def test_array(self):
self.add_object(["spam", "spam", "eggs", "spam"], examples=True)
self.assertResult({"type": "array", "items": {
"type": "string", "examples": ["spam", "eggs"]}
})

def test_tuple(self):
self.add_schema({'type': 'array', 'items': [
{"type": "string"},
{"type": "integer"},
{"type": "boolean"}
]})
self.add_object(["one", 2, False], examples=True)
self.assertResult({"type": "array", "items": [
{"type": "string", "examples": ["one"]},
{"type": "integer", "examples": [2]},
{"type": "boolean"},
]})

def test_object(self):
self.add_object({
"foo": "bar",
"hop": 1
}, examples=True)
self.add_object({
"foo": "nop",
"hop": 1
}, examples=True)
self.assertResult({
"required": ["foo", "hop"],
"type": "object",
"properties": {
"foo": {"type": "string", "examples": ["bar", "nop"]},
"hop": {"type": "integer", "examples": [1]}
}
})

0 comments on commit 75287c3

Please sign in to comment.