diff --git a/genson/schema/strategies/array.py b/genson/schema/strategies/array.py index 9b4af37..3082070 100644 --- a/genson/schema/strategies/array.py +++ b/genson/schema/strategies/array.py @@ -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) @@ -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): diff --git a/genson/schema/strategies/object.py b/genson/schema/strategies/object.py index 41426a7..4071ed1 100644 --- a/genson/schema/strategies/object.py +++ b/genson/schema/strategies/object.py @@ -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 diff --git a/test/test_custom.py b/test/test_custom.py index 291aaf5..f40e63d 100644 --- a/test/test_custom.py +++ b/test/test_custom.py @@ -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 @@ -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