Skip to content

Commit

Permalink
More copy work
Browse files Browse the repository at this point in the history
  • Loading branch information
Tinche committed Aug 30, 2022
1 parent de56f32 commit ac622c9
Show file tree
Hide file tree
Showing 3 changed files with 117 additions and 17 deletions.
31 changes: 17 additions & 14 deletions src/cattrs/converters.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ def __init__(
# Per-instance register of to-attrs converters.
# Singledispatch dispatches based on the first argument, so we
# store the function and switch the arguments in self.loads.
self._structure_func = MultiStrategyDispatch(self._structure_error)
self._structure_func = MultiStrategyDispatch(BaseConverter._structure_error)
self._structure_func.register_func_list(
[
(lambda cl: cl is Any or cl is Optional or cl is None, lambda v, _: v),
Expand Down Expand Up @@ -340,7 +340,8 @@ def _unstructure_union(self, obj):

# Python primitives to classes.

def _structure_error(self, _, cl):
@staticmethod
def _structure_error(_, cl):
"""At the bottom of the condition stack, we explode if we can't handle it."""
msg = "Unsupported type: {0!r}. Register a structure hook for " "it.".format(cl)
raise StructureHandlerNotFoundError(msg, type_=cl)
Expand Down Expand Up @@ -615,16 +616,8 @@ def _get_dis_func(union) -> Callable[..., Type]:
)
return create_uniq_field_dis_func(*union_types)

def __deepcopy__(self, memo) -> "BaseConverter":
res = BaseConverter(
self._dict_factory,
UnstructureStrategy.AS_DICT
if self._unstructure_attrs == self.unstructure_attrs_asdict
else UnstructureStrategy.AS_TUPLE,
self._prefer_attrib_converters,
self.detailed_validation,
)
return res
def __deepcopy__(self, _) -> "BaseConverter":
return self.copy()

def copy(
self,
Expand All @@ -633,7 +626,7 @@ def copy(
prefer_attrib_converters: Optional[bool] = None,
detailed_validation: Optional[bool] = None,
) -> "BaseConverter":
return self.__class__(
res = self.__class__(
dict_factory if dict_factory is not None else self._dict_factory,
unstruct_strat
if unstruct_strat is not None
Expand All @@ -650,6 +643,11 @@ def copy(
else self.detailed_validation,
)

res._unstructure_func = self._unstructure_func.copy(res._unstructure_identity)
res._structure_func = self._structure_func.copy(BaseConverter._structure_error)

return res


class Converter(BaseConverter):
"""A converter which generates specialized un/structuring functions."""
Expand Down Expand Up @@ -882,7 +880,7 @@ def copy(
prefer_attrib_converters: Optional[bool] = None,
detailed_validation: Optional[bool] = None,
) -> "Converter":
return self.__class__(
res = self.__class__(
dict_factory if dict_factory is not None else self._dict_factory,
unstruct_strat
if unstruct_strat is not None
Expand All @@ -907,5 +905,10 @@ def copy(
else self.detailed_validation,
)

res._unstructure_func = self._unstructure_func.copy(res._unstructure_identity)
res._structure_func = self._structure_func.copy(BaseConverter._structure_error)

return res


GenConverter = Converter
12 changes: 12 additions & 0 deletions src/cattrs/dispatch.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from copy import deepcopy
from functools import lru_cache, singledispatch
from typing import Any, Callable, List, Tuple, Union

Expand Down Expand Up @@ -89,6 +90,12 @@ def clear_cache(self):
self._direct_dispatch.clear()
self.dispatch.cache_clear()

def copy(self, fallback_func):
res = self.__class__(fallback_func)
res._function_dispatch = deepcopy(self._function_dispatch)
res._single_dispatch = deepcopy(self._single_dispatch)
return res


@attr.s(slots=True)
class FunctionDispatch:
Expand Down Expand Up @@ -125,3 +132,8 @@ def dispatch(self, typ):
raise StructureHandlerNotFoundError(
f"unable to find handler for {typ}", type_=typ
)

def __deepcopy__(self, _):
res = self.__class__()
res._handler_pairs = list(self._handler_pairs)
return res
91 changes: 88 additions & 3 deletions tests/test_copy.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
from collections import OrderedDict
from copy import deepcopy
from typing import Type
from re import S
from typing import Callable, Type

from attr import define
from hypothesis import given
from hypothesis.strategies import just, one_of

from cattrs import BaseConverter, UnstructureStrategy
from cattrs import BaseConverter, Converter, UnstructureStrategy

from . import unstructure_strats

Expand Down Expand Up @@ -36,23 +39,105 @@ def test_deepcopy(
assert c._prefer_attrib_converters == copy._prefer_attrib_converters


@given(strat=unstructure_strats, detailed_validation=..., prefer_attrib=...)
@given(
strat=unstructure_strats,
detailed_validation=...,
prefer_attrib=...,
dict_factory=one_of(just(dict), just(OrderedDict)),
)
def test_copy(
converter_cls: Type[BaseConverter],
strat: UnstructureStrategy,
prefer_attrib: bool,
detailed_validation: bool,
dict_factory: Callable,
):
c = converter_cls(
unstruct_strat=strat,
prefer_attrib_converters=prefer_attrib,
detailed_validation=detailed_validation,
dict_factory=dict_factory,
)

copy = c.copy()

assert c is not copy

assert c.unstructure(Simple(1)) == copy.unstructure(Simple(1))
assert c.detailed_validation == copy.detailed_validation
assert c._prefer_attrib_converters == copy._prefer_attrib_converters
assert c._dict_factory == copy._dict_factory


@given(
strat=unstructure_strats,
detailed_validation=...,
prefer_attrib=...,
dict_factory=one_of(just(dict), just(OrderedDict)),
omit_if_default=...,
)
def test_copy_converter(
strat: UnstructureStrategy,
prefer_attrib: bool,
detailed_validation: bool,
dict_factory: Callable,
omit_if_default: bool,
):
c = Converter(
unstruct_strat=strat,
prefer_attrib_converters=prefer_attrib,
detailed_validation=detailed_validation,
dict_factory=dict_factory,
omit_if_default=omit_if_default,
)

copy = c.copy()

assert c is not copy

assert c.unstructure(Simple(1)) == copy.unstructure(Simple(1))
assert c.detailed_validation == copy.detailed_validation
assert c._prefer_attrib_converters == copy._prefer_attrib_converters
assert c._dict_factory == copy._dict_factory
assert c.omit_if_default == copy.omit_if_default

another_copy = c.copy(omit_if_default=not omit_if_default)
assert c.omit_if_default != another_copy.omit_if_default


@given(
strat=unstructure_strats,
detailed_validation=...,
prefer_attrib=...,
dict_factory=one_of(just(dict), just(OrderedDict)),
)
def test_copy_hooks(
converter_cls: Type[BaseConverter],
strat: UnstructureStrategy,
prefer_attrib: bool,
detailed_validation: bool,
dict_factory: Callable,
):
"""Un/structure hooks are copied over."""
c = converter_cls(
unstruct_strat=strat,
prefer_attrib_converters=prefer_attrib,
detailed_validation=detailed_validation,
dict_factory=dict_factory,
)

c.register_unstructure_hook(Simple, lambda s: s.a)
c.register_structure_hook(Simple, lambda v, t: Simple(v))

assert c.unstructure(Simple(1)) == 1
assert c.structure(1, Simple) == Simple(1)

copy = c.copy()

assert c is not copy

assert c.unstructure(Simple(1)) == copy.unstructure(Simple(1))
assert copy.structure(copy.unstructure(Simple(1)), Simple) == Simple(1)
assert c.detailed_validation == copy.detailed_validation
assert c._prefer_attrib_converters == copy._prefer_attrib_converters
assert c._dict_factory == copy._dict_factory

0 comments on commit ac622c9

Please sign in to comment.