Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions genson/schema/strategies/array.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ def add_schema(self, schema):
self._items.add_schema(schema['items'])

def add_object(self, obj):
super().add_object(obj)
for item in obj:
self._items.add_object(item)

Expand Down Expand Up @@ -66,6 +67,7 @@ def add_schema(self, schema):
self._add(schema['items'], 'add_schema')

def add_object(self, obj):
super().add_object(obj)
self._add(obj, 'add_object')

def _add(self, items, func):
Expand Down
1 change: 1 addition & 0 deletions genson/schema/strategies/object.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ def add_schema(self, schema):
self._required &= required

def add_object(self, obj):
super().add_object(obj)
properties = set()
for prop, subobj in obj.items():
pattern = None
Expand Down
94 changes: 93 additions & 1 deletion test/test_custom.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import unittest
from genson import SchemaBuilder
from genson.schema.strategies import (
SchemaStrategy, Number, BASIC_SCHEMA_STRATEGIES)
SchemaStrategy, Number, List, Tuple, Object, BASIC_SCHEMA_STRATEGIES)
from . import base


Expand Down Expand Up @@ -58,6 +58,98 @@ def test_add_schema(self):
'maximum': 10})


class RecordingMixin(SchemaStrategy):
"""
cooperative-inheritance mixin that records every object routed to a
strategy. It relies on each concrete strategy's ``add_object``
override chaining up through ``super().add_object`` -- exactly the
contract PR #79 restored for ``Number``.
"""
# each concrete subclass supplies its own list
log = None

def add_object(self, obj):
type(self).log.append(obj)
super().add_object(obj)


class RecordingNumber(Number, RecordingMixin):
log = []


class RecordingList(List, RecordingMixin):
log = []


class RecordingTuple(Tuple, RecordingMixin):
log = []


class RecordingObject(Object, RecordingMixin):
log = []


class RecordingNumberBuilder(SchemaBuilder):
STRATEGIES = (RecordingNumber,)


class RecordingListBuilder(SchemaBuilder):
STRATEGIES = (RecordingList,)


class RecordingTupleBuilder(SchemaBuilder):
STRATEGIES = (RecordingTuple,)


class RecordingObjectBuilder(SchemaBuilder):
STRATEGIES = (RecordingObject,)


class TestAddObjectSuperChaining(unittest.TestCase):
"""
Every built-in strategy that overrides ``add_object`` must call
``super().add_object`` so cooperative subclass hooks run. PR #79
fixed this for ``Number``; ``List``, ``Tuple`` and ``Object`` must
honour the same contract.
"""

def setUp(self):
for cls in (RecordingNumber, RecordingList,
RecordingTuple, RecordingObject):
cls.log = []

def test_number_super_chaining(self):
# control: already fixed by PR #79
RecordingNumberBuilder().add_object(5)
self.assertEqual(RecordingNumber.log, [5])

def test_list_super_chaining(self):
# empty container isolates the hook from item recursion
RecordingListBuilder().add_object([])
self.assertEqual(RecordingList.log, [[]])

def test_tuple_super_chaining(self):
RecordingTupleBuilder().add_object([])
self.assertEqual(RecordingTuple.log, [[]])

def test_object_super_chaining(self):
RecordingObjectBuilder().add_object({})
self.assertEqual(RecordingObject.log, [{}])

def test_nested_object_and_array_hooks_all_fire(self):
# a single nested value must reach the hook at every level:
# outer object -> inner list -> inner objects
class NestedBuilder(SchemaBuilder):
STRATEGIES = (RecordingObject, RecordingList, RecordingNumber)

NestedBuilder().add_object({'items': [{'n': 1}, {'n': 2}]})
self.assertEqual(RecordingObject.log,
[{'items': [{'n': 1}, {'n': 2}]},
{'n': 1}, {'n': 2}])
self.assertEqual(RecordingList.log, [[{'n': 1}, {'n': 2}]])
self.assertEqual(RecordingNumber.log, [1, 2])


class DuplicateStrategiesSchemaBuilder(SchemaBuilder):
"""many duplicate strategies to exercise deduplication"""
EXTRA_STRATEGIES = BASIC_SCHEMA_STRATEGIES * 100
Expand Down