changeset 44:ea8c2d01a9d9

Begin pickling support for ValidatenProblems, _Schema and Context
author Franz Glasner <fzglas.hg@dom66.de>
date Wed, 02 Aug 2023 09:31:49 +0200
parents 4ca530618303
children 8a560e0c3180
files data_schema/__init__.py tests/test_schema.py
diffstat 2 files changed, 133 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- a/data_schema/__init__.py	Thu Jul 20 09:37:22 2023 +0200
+++ b/data_schema/__init__.py	Wed Aug 02 09:31:49 2023 +0200
@@ -32,6 +32,7 @@
 import copy
 import datetime
 import enum
+import pickle
 import re
 import urllib.parse
 
@@ -56,6 +57,9 @@
     WARNING = 30
     ERROR = 40
 
+    def __reduce_ex__(self, protocol):
+        return (getattr, (self.__class__, self._name_))
+
 
 @enum.unique
 class ERRORS(enum.Enum):
@@ -120,11 +124,17 @@
     E10058 = NC_("schema-msg", "failing `all-of' item")
     E10059 = NC_("schema-msg", "forbidden key detected")
 
+    def __reduce_ex__(self, protocol):
+        return (getattr, (self.__class__, self._name_))
+
 
 @enum.unique
 class WARNINGS(enum.Enum):
     W80000 = NC_("schema-msg", "duplicate dict key")
 
+    def __reduce_ex__(self, protocol):
+        return (getattr, (self.__class__, self._name_))
+
 
 # Check some invariants at import time
 for e in ERRORS.__members__:
@@ -217,6 +227,28 @@
         self.cause = cause
         self.index = index
 
+    def __eq__(self, other):
+        if not isinstance(other, ValidationProblem):
+            return NotImplemented
+        return ((self.code == other.code)
+                and (self.severity == other.severity)
+                and (self.hint == other.hint)
+                and (self.context == other.context)
+                and (self.cause == other.cause)
+                and (self.index == other.index))
+
+    def __getstate__(self):
+        return (1, self.code, self.severity, self.hint, self.context,
+                self.cause, self.index)
+
+    def __setstate__(self, state):
+        ver = state[0]
+        if ver == 1:
+            _dummy, self.code, self.severity, self.hint, self.context, self.cause, self.index = state
+        else:
+            raise pickle.UnpicklingError(
+                "Unsupported pickle version for ValidationProblem: %d" % (ver,))
+
     def __repr__(self):
         try:
             msg = " (" + problem_message(self) + ")"
@@ -265,6 +297,26 @@
         else:
             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
+
+    def __getstate__(self):
+        return (1, self.parent, self.is_sub_root)
+
+    def __setstate__(self, state):
+        ver = state[0]
+        if ver == 1:
+            _dummy, self.parent, self.is_sub_root = state
+            if self.parent is None:
+                self._schema_cache = {}
+        else:
+            raise pickle.UnpicklingError(
+                "Unsupported pickle version for _Schema: %d" % (ver,))
+
     @property
     def ROOT(self):
         """Get the root schema"""
@@ -397,6 +449,19 @@
         self._current_object = current_object
         self._settings = settings
 
+    def __getstate__(self):
+        return (1, self._parent, self._key, self._key_index, self._index,
+                self.root_object, self.root_schema,
+                self._current_object, self._settings)
+
+    def __setstate__(self, state):
+        ver = state[0]
+        if ver == 1:
+            _dummy, self._parent, self._key, self._key_index, self._index, self.root_object, self.root_schema, self._current_object, self._settings  = state
+        else:
+            raise pickle.UnpicklingError(
+                "Unsupported pickle version for _Context: %d" % (ver,))
+
     @property
     def parent(self):
         return self._parent
--- a/tests/test_schema.py	Thu Jul 20 09:37:22 2023 +0200
+++ b/tests/test_schema.py	Wed Aug 02 09:31:49 2023 +0200
@@ -2,6 +2,7 @@
 import copy
 import datetime
 import functools
+import pickle
 import re
 import unittest
 
@@ -22,6 +23,54 @@
     yield from ()
 
 
+class Pickling(unittest.TestCase):
+
+    def test_severity(self):
+        for sev in SEVERITY:
+            b = pickle.dumps(sev)
+            sev2 = pickle.loads(b)
+            self.assertEqual(sev, sev2)
+
+    def test_errors(self):
+        for err in ERRORS:
+            b = pickle.dumps(err)
+            err2 = pickle.loads(b)
+            self.assertEqual(err, err2)
+
+    def test_warnings(self):
+        for warn in ERRORS:
+            b = pickle.dumps(warn)
+            warn2 = pickle.loads(b)
+            self.assertEqual(warn, warn2)
+
+    def test_combination(self):
+        obj = (SEVERITY.ERROR, ERRORS.E10001, WARNINGS.W80000)
+        b = pickle.dumps(obj)
+        obj2 = pickle.loads(b)
+        self.assertEqual(obj, obj2)
+
+    def test_problem_1(self):
+        problem = data_schema.ValidationProblem(
+            code=ERRORS.E10002,
+            severity=SEVERITY.ERROR,
+            hint="a-hint")
+        b = pickle.dumps(problem)
+        problem2 = pickle.loads(b)
+        self.assertIsInstance(problem2, data_schema.ValidationProblem)
+        self.assertEqual(problem, problem2)
+
+    def test_problem_2(self):
+        problem = data_schema.ValidationProblem(
+            code=ERRORS.E10002,
+            severity=SEVERITY.ERROR,
+            hint="a-hint")
+
+        def _cmp(a, b):
+            return a == b
+
+        self.assertFalse(_cmp(problem, 1))
+
+
 class YAML(unittest.TestCase):
 
     """Tests to load Python objects from YAML with complex Python-specific
@@ -2483,6 +2532,25 @@
         self.assertEqual(1, len(pr))
         self.assertEqual(ERRORS.E10030, pr[0].code)
 
+    def test_number_pickle(self):
+        pr = list(data_schema.validate(1.8, {"$type": "number"}))
+        self.assertEqual(0, len(pr))
+        pr = list(data_schema.validate(1, {"$type": "num"}))
+        self.assertEqual(0, len(pr))
+
+        pr = list(data_schema.validate(
+            2.0,
+            {"$type": "number",
+             "min-value": 3,
+             "max-value": 1.3}))
+
+        pr2 = pickle.loads(pickle.dumps(pr))
+        self.assertEqual(2, len(pr2))
+        self.assertEqual(ERRORS.E10031, pr2[0].code)
+        self.assertEqual(ERRORS.E10032, pr2[1].code)
+        self.maxDiff = None
+        self.assertEqual(repr(pr), repr(pr2))
+
     def test_bool(self):
         pr = list(data_schema.validate(True, {"$type": "bool"}))
         self.assertEqual(0, len(pr))