提交 7a54f047 编写于 作者: HansBug's avatar HansBug 😆

dev(hansbug): add support for constraint pickle

上级 60060d70
import pickle
import pytest import pytest
from treevalue import delayed from treevalue import delayed
from treevalue.tree.tree import TreeValue, cleaf from treevalue.tree.tree import TreeValue, cleaf
from treevalue.tree.tree.constraint import to_constraint, TypeConstraint, EmptyConstraint, LeafConstraint, \ from treevalue.tree.tree.constraint import to_constraint, TypeConstraint, EmptyConstraint, LeafConstraint, \
ValueConstraint, TreeConstraint, vval, vcheck, nval, ncheck, transact ValueConstraint, TreeConstraint, vval, vcheck, nval, ncheck, transact, CompositeConstraint, ValueValidateConstraint, \
ValueCheckConstraint, NodeCheckConstraint, NodeValidateConstraint
class GreaterThanConstraint(ValueConstraint): class GreaterThanConstraint(ValueConstraint):
...@@ -21,9 +24,35 @@ class GreaterThanConstraint(ValueConstraint): ...@@ -21,9 +24,35 @@ class GreaterThanConstraint(ValueConstraint):
return isinstance(other, GreaterThanConstraint) and self.value >= other.value return isinstance(other, GreaterThanConstraint) and self.value >= other.value
class OverValueCheck:
def __init__(self, value):
self.value = value
def __call__(self, v):
return v > self.value
class OverValueValidation:
def __init__(self, value):
self.value = value
def __call__(self, v):
if v <= self.value:
raise ValueError(f'Invalid value - {v!r}.')
# noinspection DuplicatedCode,PyArgumentList,PyTypeChecker # noinspection DuplicatedCode,PyArgumentList,PyTypeChecker
@pytest.mark.unittest @pytest.mark.unittest
class TestTreeTreeConstraint: class TestTreeTreeConstraint:
def test_example_constraint(self):
c = GreaterThanConstraint(10)
binary = pickle.dumps(c)
newc = pickle.loads(binary)
assert isinstance(newc, GreaterThanConstraint)
assert newc.value == 10
assert c == newc
def test_empty(self): def test_empty(self):
c1 = to_constraint(None) c1 = to_constraint(None)
assert isinstance(c1, EmptyConstraint) assert isinstance(c1, EmptyConstraint)
...@@ -48,6 +77,10 @@ class TestTreeTreeConstraint: ...@@ -48,6 +77,10 @@ class TestTreeTreeConstraint:
c1.validate(None) c1.validate(None)
c1.validate(TreeValue({'a': 1, 'b': {'x': 2, 'y': 3}})) c1.validate(TreeValue({'a': 1, 'b': {'x': 2, 'y': 3}}))
binary = pickle.dumps(c1)
newc = pickle.loads(binary)
assert isinstance(newc, EmptyConstraint)
def test_type(self): def test_type(self):
c1 = to_constraint(int) c1 = to_constraint(int)
assert isinstance(c1, TypeConstraint) assert isinstance(c1, TypeConstraint)
...@@ -106,6 +139,12 @@ class TestTreeTreeConstraint: ...@@ -106,6 +139,12 @@ class TestTreeTreeConstraint:
with pytest.raises(TypeError): with pytest.raises(TypeError):
c1.validate(t2) c1.validate(t2)
binary = pickle.dumps(c1)
newc = pickle.loads(binary)
assert isinstance(newc, TypeConstraint)
assert newc.type_ == int
assert newc == int
def test_leaf(self): def test_leaf(self):
c1 = to_constraint(cleaf()) c1 = to_constraint(cleaf())
assert isinstance(c1, LeafConstraint) assert isinstance(c1, LeafConstraint)
...@@ -145,6 +184,10 @@ class TestTreeTreeConstraint: ...@@ -145,6 +184,10 @@ class TestTreeTreeConstraint:
with pytest.raises(TypeError): with pytest.raises(TypeError):
c1.validate(t1) c1.validate(t1)
binary = pickle.dumps(c1)
newc = pickle.loads(binary)
assert isinstance(newc, LeafConstraint)
def test_custom_value(self): def test_custom_value(self):
c1 = GreaterThanConstraint(3) c1 = GreaterThanConstraint(3)
assert c1 assert c1
...@@ -343,6 +386,11 @@ class TestTreeTreeConstraint: ...@@ -343,6 +386,11 @@ class TestTreeTreeConstraint:
with pytest.raises(TypeError): with pytest.raises(TypeError):
c1.validate(t4) c1.validate(t4)
binary = pickle.dumps(c1)
newc = pickle.loads(binary)
assert isinstance(newc, CompositeConstraint)
assert newc == [GreaterThanConstraint(3), int]
def test_tree(self): def test_tree(self):
assert to_constraint({'a': None, 'b': []}) == to_constraint(None) assert to_constraint({'a': None, 'b': []}) == to_constraint(None)
...@@ -456,6 +504,18 @@ class TestTreeTreeConstraint: ...@@ -456,6 +504,18 @@ class TestTreeTreeConstraint:
with pytest.raises(TypeError): with pytest.raises(TypeError):
c1.validate(t7) c1.validate(t7)
binary = pickle.dumps(c1)
newc = pickle.loads(binary)
assert isinstance(newc, TreeConstraint)
assert newc == {
'a': [int, GreaterThanConstraint(3)],
'b': {
'x': [cleaf(), str, None],
'y': float,
},
'c': None,
}
def test_composite_tree(self): def test_composite_tree(self):
c1 = to_constraint([ c1 = to_constraint([
{ {
...@@ -510,6 +570,29 @@ class TestTreeTreeConstraint: ...@@ -510,6 +570,29 @@ class TestTreeTreeConstraint:
assert not (c1 <= {'c': None}) assert not (c1 <= {'c': None})
assert not (c1 < {'c': None}) assert not (c1 < {'c': None})
binary = pickle.dumps(c1)
newc = pickle.loads(binary)
assert isinstance(newc, TreeConstraint)
assert newc == [
{
'a': [int, object],
'b': {
'x': [str, None],
'y': object,
},
'c': None,
},
{
'b': {
'x': [cleaf(), None],
'y': float,
}
},
{
'a': GreaterThanConstraint(3),
},
]
def test_complex(self): def test_complex(self):
c1 = to_constraint([ c1 = to_constraint([
GreaterThanConstraint(4), GreaterThanConstraint(4),
...@@ -542,6 +625,30 @@ class TestTreeTreeConstraint: ...@@ -542,6 +625,30 @@ class TestTreeTreeConstraint:
}, },
] ]
binary = pickle.dumps(c1)
newc = pickle.loads(binary)
assert isinstance(newc, CompositeConstraint)
assert newc == [
GreaterThanConstraint(4),
{
'a': [int, object],
'b': {
'x': [int, None],
'y': object,
},
'c': None,
},
{
'b': {
'x': [cleaf(), None],
'y': int,
}
},
{
'a': GreaterThanConstraint(3),
},
]
def test_with_delay(self): def test_with_delay(self):
c1 = to_constraint([ c1 = to_constraint([
object, object,
...@@ -669,6 +776,22 @@ class TestTreeTreeConstraint: ...@@ -669,6 +776,22 @@ class TestTreeTreeConstraint:
with pytest.raises(AssertionError): with pytest.raises(AssertionError):
c2.validate(3) c2.validate(3)
cx = vval(OverValueValidation(5), 'over5')
binary = pickle.dumps(cx)
newcx = pickle.loads(binary)
assert isinstance(newcx, ValueValidateConstraint)
assert isinstance(newcx.func, OverValueValidation)
assert newcx.func.value == 5
assert newcx.name == 'over5'
cx = vcheck(OverValueCheck(5), 'over5')
binary = pickle.dumps(cx)
newcx = pickle.loads(binary)
assert isinstance(newcx, ValueCheckConstraint)
assert isinstance(newcx.func, OverValueCheck)
assert newcx.func.value == 5
assert newcx.name == 'over5'
def test_node_func(self): def test_node_func(self):
def _n_validate(x: TreeValue): def _n_validate(x: TreeValue):
if 'a' in x and 'b' in x: if 'a' in x and 'b' in x:
...@@ -772,6 +895,22 @@ class TestTreeTreeConstraint: ...@@ -772,6 +895,22 @@ class TestTreeTreeConstraint:
with pytest.raises(TypeError): with pytest.raises(TypeError):
c2.validate(10) c2.validate(10)
cx = nval(OverValueValidation(5), 'over5')
binary = pickle.dumps(cx)
newcx = pickle.loads(binary)
assert isinstance(newcx, NodeValidateConstraint)
assert isinstance(newcx.func, OverValueValidation)
assert newcx.func.value == 5
assert newcx.name == 'over5'
cx = ncheck(OverValueCheck(5), 'over5')
binary = pickle.dumps(cx)
newcx = pickle.loads(binary)
assert isinstance(newcx, NodeCheckConstraint)
assert isinstance(newcx.func, OverValueCheck)
assert newcx.func.value == 5
assert newcx.name == 'over5'
def test_hash_eq(self): def test_hash_eq(self):
d = { d = {
to_constraint(int): 2389, to_constraint(int): 2389,
......
...@@ -195,6 +195,9 @@ cdef class TypeConstraint(ValueConstraint): ...@@ -195,6 +195,9 @@ cdef class TypeConstraint(ValueConstraint):
cpdef bool _contains(self, Constraint other): cpdef bool _contains(self, Constraint other):
return isinstance(other, TypeConstraint) and issubclass(self.type_, other.type_) return isinstance(other, TypeConstraint) and issubclass(self.type_, other.type_)
def __reduce__(self):
return TypeConstraint, (self.type_,)
cdef inline str _c_func_fullname(object f): cdef inline str _c_func_fullname(object f):
cdef str fname = f.__name__ cdef str fname = f.__name__
cdef str mname = getattr(f, '__module__', '') cdef str mname = getattr(f, '__module__', '')
...@@ -214,6 +217,9 @@ cdef class ValueFuncConstraint(ValueConstraint): ...@@ -214,6 +217,9 @@ cdef class ValueFuncConstraint(ValueConstraint):
def __repr__(self): def __repr__(self):
return f'<{type(self).__name__} {self.name}>' return f'<{type(self).__name__} {self.name}>'
def __reduce__(self):
return type(self), (self.func, self.name)
@cython.final @cython.final
cdef class ValueValidateConstraint(ValueFuncConstraint): cdef class ValueValidateConstraint(ValueFuncConstraint):
cpdef inline void _validate_value(self, object instance) except*: cpdef inline void _validate_value(self, object instance) except*:
...@@ -245,7 +251,7 @@ cdef class LeafConstraint(Constraint): ...@@ -245,7 +251,7 @@ cdef class LeafConstraint(Constraint):
return isinstance(other, LeafConstraint) return isinstance(other, LeafConstraint)
cpdef inline Constraint _transaction(self, str key): cpdef inline Constraint _transaction(self, str key):
return _EMPTY_CONSTRAINT return _EMPTY_CONSTRAINT # pragma: no cover
def __repr__(self): def __repr__(self):
return f'<{type(self).__name__}>' return f'<{type(self).__name__}>'
...@@ -267,6 +273,9 @@ cdef class NodeFuncConstraint(NodeConstraint): ...@@ -267,6 +273,9 @@ cdef class NodeFuncConstraint(NodeConstraint):
def __repr__(self): def __repr__(self):
return f'<{type(self).__name__} {self.name}>' return f'<{type(self).__name__} {self.name}>'
def __reduce__(self):
return type(self), (self.func, self.name)
@cython.final @cython.final
cdef class NodeValidateConstraint(NodeFuncConstraint): cdef class NodeValidateConstraint(NodeFuncConstraint):
cpdef inline void _validate_node(self, object instance) except*: cpdef inline void _validate_node(self, object instance) except*:
...@@ -284,8 +293,11 @@ cpdef inline NodeCheckConstraint ncheck(object func, object name=None): ...@@ -284,8 +293,11 @@ cpdef inline NodeCheckConstraint ncheck(object func, object name=None):
return NodeCheckConstraint(func, str(name or _c_func_fullname(func))) return NodeCheckConstraint(func, str(name or _c_func_fullname(func)))
cdef class TreeConstraint(Constraint): cdef class TreeConstraint(Constraint):
def __cinit__(self, dict constraints): def __cinit__(self, dict constraints, bool need_sort=True):
self._constraints = {key: constraints[key] for key in sorted(constraints.keys())} if need_sort:
self._constraints = {key: constraints[key] for key in sorted(constraints.keys())}
else:
self._constraints = dict(constraints)
cpdef void _validate_node(self, object instance) except*: cpdef void _validate_node(self, object instance) except*:
pass pass
...@@ -322,6 +334,9 @@ cdef class TreeConstraint(Constraint): ...@@ -322,6 +334,9 @@ cdef class TreeConstraint(Constraint):
else: else:
return _EMPTY_CONSTRAINT return _EMPTY_CONSTRAINT
def __reduce__(self):
return TreeConstraint, (self._constraints, False)
cdef inline Constraint _s_tree_merge(list constraints): cdef inline Constraint _s_tree_merge(list constraints):
cdef dict cmap = {} cdef dict cmap = {}
cdef str key cdef str key
...@@ -362,8 +377,11 @@ cdef inline Constraint _s_tree(TreeConstraint constraint): ...@@ -362,8 +377,11 @@ cdef inline Constraint _s_tree(TreeConstraint constraint):
return _EMPTY_CONSTRAINT return _EMPTY_CONSTRAINT
cdef class CompositeConstraint(Constraint): cdef class CompositeConstraint(Constraint):
def __cinit__(self, list constraints): def __cinit__(self, list constraints, bool need_sort=True):
self._constraints = tuple(sorted(constraints, key=lambda x: repr(x._features()))) if need_sort:
self._constraints = tuple(sorted(constraints, key=lambda x: repr(x._features())))
else:
self._constraints = tuple(constraints)
cpdef void _validate_node(self, object instance) except*: cpdef void _validate_node(self, object instance) except*:
cdef Constraint cons cdef Constraint cons
...@@ -413,6 +431,9 @@ cdef class CompositeConstraint(Constraint): ...@@ -413,6 +431,9 @@ cdef class CompositeConstraint(Constraint):
cpdef Constraint _transaction(self, str key): cpdef Constraint _transaction(self, str key):
return CompositeConstraint([c._transaction(key) for c in self._constraints]) return CompositeConstraint([c._transaction(key) for c in self._constraints])
def __reduce__(self):
return CompositeConstraint, (list(self._constraints), False)
cdef inline void _rec_composite_iter(Constraint constraint, list lst): cdef inline void _rec_composite_iter(Constraint constraint, list lst):
cdef Constraint cons cdef Constraint cons
if isinstance(constraint, CompositeConstraint): if isinstance(constraint, CompositeConstraint):
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册