diff --git a/genson/schema/builder.py b/genson/schema/builder.py index e35accd..b567e3a 100644 --- a/genson/schema/builder.py +++ b/genson/schema/builder.py @@ -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): """ diff --git a/genson/schema/node.py b/genson/schema/node.py index 7a7b37f..d220fda 100644 --- a/genson/schema/node.py +++ b/genson/schema/node.py @@ -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 diff --git a/genson/schema/strategies/array.py b/genson/schema/strategies/array.py index e97c76e..2139c67 100644 --- a/genson/schema/strategies/array.py +++ b/genson/schema/strategies/array.py @@ -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() @@ -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] diff --git a/genson/schema/strategies/base.py b/genson/schema/strategies/base.py index c2e83c2..0ee04d3 100644 --- a/genson/schema/strategies/base.py +++ b/genson/schema/strategies/base.py @@ -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) @@ -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 """ diff --git a/genson/schema/strategies/object.py b/genson/schema/strategies/object.py index 8b84521..cb43ee7 100644 --- a/genson/schema/strategies/object.py +++ b/genson/schema/strategies/object.py @@ -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 @@ -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 diff --git a/genson/schema/strategies/scalar.py b/genson/schema/strategies/scalar.py index 8468ba5..9790542 100644 --- a/genson/schema/strategies/scalar.py +++ b/genson/schema/strategies/scalar.py @@ -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): """ @@ -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() diff --git a/test/base.py b/test/base.py index dfc8ace..d825dfd 100644 --- a/test/base.py +++ b/test/base.py @@ -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): diff --git a/test/test_gen_single.py b/test/test_gen_single.py index 0dcfb2c..08eff8a 100644 --- a/test/test_gen_single.py +++ b/test/test_gen_single.py @@ -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([]) @@ -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]} + } + })