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

dev(hansbug): add support for constraint pickle

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