# HG changeset patch # User Franz Glasner # Date 1690987894 -7200 # Node ID 92ae1e882cef3e6b179ce349d6d99b6ef6b3bf59 # Parent 8a560e0c31808ec907c4ecdb296ff05f18c4ecab Enhance pickling support diff -r 8a560e0c3180 -r 92ae1e882cef data_schema/__init__.py --- a/data_schema/__init__.py Wed Aug 02 13:58:25 2023 +0200 +++ b/data_schema/__init__.py Wed Aug 02 16:51:34 2023 +0200 @@ -34,6 +34,7 @@ import enum import pickle import re +import sys import urllib.parse import rfc3986 @@ -147,8 +148,6 @@ TYPE_RE = type(re.compile(r"\A.+\Z")) -_SENTINEL = object() - SCHEMA_REF_KEY = "$ref" """Key name for schema references (like a symlink within a schema)""" @@ -159,6 +158,20 @@ """URI path to the current schema""" +class _SENTINELType(object): + + @staticmethod + def _get_single(module, name): + return getattr(sys.modules[module], name) + + def __reduce_ex__(self, proto): + """Make sure the _SENTINEL is ever only instantiated as singleton""" + return (_SENTINELType._get_single, (self.__module__, "_SENTINEL")) + + +_SENTINEL = _SENTINELType() + + def problem_message(pr): """ @@ -309,11 +322,7 @@ self.is_sub_root = is_sub_root def __reduce_ex__(self, proto): - rv = super().__reduce_ex__(proto) - #assert False, repr(rv) - print("RRRRRRR\n\n", repr(rv)) - assert False, repr(rv) - return rv + return super().__reduce_ex__(proto) def __getstate__(self): return (1, self.parent, self.is_sub_root) @@ -447,8 +456,10 @@ raise TypeError("one of `key` and `index` must be given in a non-root context") if root_object is not _SENTINEL: raise TypeError("non-root context may not have a root object") + self.root_object = root_object if root_schema is not _SENTINEL: raise TypeError("non-root context may not have a root schema") + self.root_schema = root_schema if key is not _SENTINEL and index is not _SENTINEL: raise ValueError("only one of `key` and `index` may be given in a context") if key_index is not _SENTINEL and key is _SENTINEL: @@ -473,6 +484,30 @@ raise pickle.UnpicklingError( "Unsupported pickle version for _Context: %d" % (ver,)) + def __eq__(self, other): + if not isinstance(other, Context): + return NotImplemented + return ((self._parent == other._parent) + and (self._key == other._key) + and (self._key_index == other._key_index) +# XXX FIXME ??? +# and (self.root_object == other.root_object) +# and (self.root_schema == other.root_schema) +# and (self._current_object == other._current_object) + and (self._settings == other._settings) + ) + + def __ne__(self, other): + # + # While the default in Python3 is sensible implementing is recommended + # when a built-in __eq__ is overwritten (Raymond Hettinger). + # + # Do not use not self == other because NotImplemented is not handled + # properly in some early Python versions (including Py2). + # + equal = self.__eq__(other) + return NotImplemented if equal is NotImplemented else not equal + @property def parent(self): return self._parent diff -r 8a560e0c3180 -r 92ae1e882cef tests/test_schema.py --- a/tests/test_schema.py Wed Aug 02 13:58:25 2023 +0200 +++ b/tests/test_schema.py Wed Aug 02 16:51:34 2023 +0200 @@ -25,6 +25,12 @@ class Pickling(unittest.TestCase): + def test_sentinel_singleinst(self): + s = data_schema._SENTINEL + s2 = pickle.loads(pickle.dumps(s)) + self.assertIs(s, s2) + self.assertIs(s2, s) + def test_severity(self): for sev in SEVERITY: b = pickle.dumps(sev) @@ -112,6 +118,12 @@ self.assertEqual(0, len(schema)) self.assertFalse(schema) + def test_root_creation_pickle(self): + schema = data_schema._Schema(None, True) + self.assertIsInstance(schema, dict) + schema2 = pickle.loads(pickle.dumps(schema)) + self.assertEqual(schema, schema2) + def test_root_creation_wrong(self): self.assertRaises( ValueError, @@ -126,6 +138,14 @@ self.assertTrue(schema.is_sub_root) self.assertIs(schema, schema.SELF) + def test_root_properties_pickle(self): + schema = data_schema._Schema(None, True) + schema2 = pickle.loads(pickle.dumps(schema)) + self.assertIsNone(schema2.parent) + self.assertIs(schema2, schema2.ROOT) + self.assertTrue(schema2.is_sub_root) + self.assertIs(schema2, schema2.SELF) + def test_dict_len_bool(self): schema = data_schema._Schema(None, True, a=1, b=2) self.assertTrue(schema) @@ -137,6 +157,15 @@ self.assertEqual(schema1, schema2) self.assertIsNot(schema1, schema2) + def test_pickle(self): + schema1 = data_schema._Schema(None, True, a=1, b=2) + schema2 = pickle.loads(pickle.dumps(schema1)) + self.assertEqual(schema1, schema2) + self.assertIsNot(schema1, schema2) + self.assertEqual(2, len(schema2)) + self.assertEqual(1, schema2["a"]) + self.assertEqual(2, schema2["b"]) + def test_copy(self): schema = data_schema._Schema(None, True, type="str") schema2 = schema.copy() @@ -200,6 +229,22 @@ self.assertTrue(ctx.root_schema is schema) self.assertTrue(ctx.settings is settings) + def test_root_context_pickle(self): + obj = object() + schema = object() + settings = data_schema.ValidationSettings( + skip_keys=[], break_on_keynames_problems=True, + data_stream_loader=None, + schema_loader=data_schema.default_schema_loader) + ctx = data_schema.Context( + None, root_object=obj, root_schema=schema, settings=settings) + self.assertEqual("", str(ctx)) + self.assertTrue(ctx.root_object is obj) + self.assertTrue(ctx.root_schema is schema) + self.assertTrue(ctx.settings is settings) + ctx2 = pickle.loads(pickle.dumps(ctx)) + self.assertEqual(ctx, ctx2) + def test_parent_of_root_context(self): obj = object() schema = object() @@ -289,6 +334,22 @@ ctx3 = data_schema.Context(ctx2, key="key3") self.assertEqual("", repr(ctx3)) + def test_repr_pickle(self): + settings = data_schema.ValidationSettings( + skip_keys=[], break_on_keynames_problems=True, + data_stream_loader=None, + schema_loader=data_schema.default_schema_loader) + root = data_schema.Context(None, settings=settings) + ctx1 = data_schema.Context(root, key="key1") + ctx2 = data_schema.Context(ctx1, index=2) + ctx3 = data_schema.Context(ctx2, key="key3") + + ctx3_2 = pickle.loads(pickle.dumps(ctx3)) + + self.assertEqual(ctx3, ctx3_2) + self.assertEqual(repr(ctx3), repr(ctx3_2)) + self.assertEqual("", repr(ctx3_2)) + def test_root(self): settings = data_schema.ValidationSettings( skip_keys=[], break_on_keynames_problems=True,