Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
Tin Tvrtkovic authored and Peter Gaultney committed Jan 28, 2021
1 parent a8f28df commit ef61ab6
Show file tree
Hide file tree
Showing 4 changed files with 56 additions and 21 deletions.
2 changes: 2 additions & 0 deletions HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ History
(`#115 <https://github.com/Tinche/cattrs/pull/115>`_)
* Fix `GenConverter` behavior with inheritance hierarchies of `attrs` classes.
(`#117 <https://github.com/Tinche/cattrs/pull/117>`_) (`#116 <https://github.com/Tinche/cattrs/issues/116>`_)
* Refactor `GenConverter.un/structure_attrs_fromdict` into `GenConverter.gen_un/structure_attrs_fromdict` to allow calling back to `Converter.un/structure_attrs_fromdict` without sideeffects.
(`#118 https://github.com/Tinche/cattrs/issues/118`_)

1.1.2 (2020-11-29)
------------------
Expand Down
39 changes: 21 additions & 18 deletions src/cattr/converters.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,6 @@
from enum import Enum
from functools import lru_cache
from typing import ( # noqa: F401, imported for Mypy.
Any,
Callable,
Dict,
FrozenSet,
Mapping,
Optional,
Sequence,
Set,
Tuple,
Type,
TypeVar,
)
from typing import Any, Callable, Dict, Mapping, Optional, Tuple, Type, TypeVar

from attr import fields, resolve_types

Expand Down Expand Up @@ -291,7 +279,8 @@ def _structure_default(self, obj, cl):
)
raise ValueError(msg)

def _structure_call(self, obj, cl):
@staticmethod
def _structure_call(obj, cl):
"""Just call ``cl`` with the given ``obj``.
This is just an optimization on the ``_structure_default`` case, when
Expand All @@ -314,7 +303,7 @@ def structure_attrs_fromtuple(

return cl(*conv_obj) # type: ignore

def _structure_attr_from_tuple(self, a, name, value):
def _structure_attr_from_tuple(self, a, _, value):
"""Handle an individual attrs attribute."""
type_ = a.type
if type_ is None:
Expand Down Expand Up @@ -452,7 +441,8 @@ def _structure_tuple(self, obj, tup: Type[T]):
for t, e in zip(tup_params, obj)
)

def _get_dis_func(self, union):
@staticmethod
def _get_dis_func(union):
# type: (Type) -> Callable[..., Type]
"""Fetch or try creating a disambiguation function for a union."""
union_types = union.__args__
Expand Down Expand Up @@ -491,7 +481,20 @@ def __init__(
self.omit_if_default = omit_if_default
self.type_overrides = type_overrides

def unstructure_attrs_asdict(self, obj: Any) -> Dict[str, Any]:
if unstruct_strat is UnstructureStrategy.AS_DICT:
# Override the attrs handler.
self._unstructure_func.register_func_list(
[
(_is_attrs_class, self.gen_unstructure_attrs_fromdict),
]
)
self._structure_func.register_func_list(
[
(_is_attrs_class, self.gen_structure_attrs_fromdict),
]
)

def gen_unstructure_attrs_fromdict(self, obj: Any) -> Dict[str, Any]:
attribs = fields(obj.__class__)
if any(isinstance(a.type, str) for a in attribs):
# PEP 563 annotations - need to be resolved.
Expand All @@ -513,7 +516,7 @@ def unstructure_attrs_asdict(self, obj: Any) -> Dict[str, Any]:
)
return h(obj)

def structure_attrs_fromdict(
def gen_structure_attrs_fromdict(
self, obj: Mapping[str, Any], cl: Type[T]
) -> T:
attribs = fields(cl)
Expand Down
27 changes: 27 additions & 0 deletions tests/metadata/test_genconverter.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,3 +172,30 @@ def test_type_overrides(cl_and_vals):
assert field.name not in unstructured
elif field.default == val:
assert field.name not in unstructured


def test_calling_back():
"""Calling unstructure_attrs_asdict from a hook should not override a manual hook."""
converter = Converter()

@attr.define
class C:
a: int = attr.ib(default=1)

def handler(obj):
return {
"type_tag": obj.__class__.__name__,
**converter.unstructure_attrs_asdict(obj),
}

converter.register_unstructure_hook(C, handler)

inst = C()

expected = {"type_tag": "C", "a": 1}

unstructured1 = converter.unstructure(inst)
unstructured2 = converter.unstructure(inst)

assert unstructured1 == expected, repr(unstructured1)
assert unstructured2 == unstructured1, repr(unstructured2)
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
from cattr.converters import GenConverter
import attr
import pytest

from cattr.converters import Converter, GenConverter

def test_inheritance():

@pytest.mark.parametrize("converter_cls", [GenConverter, Converter])
def test_inheritance(converter_cls):
@attr.s(auto_attribs=True)
class A:
i: int
Expand All @@ -11,7 +14,7 @@ class A:
class B(A):
j: int

converter = GenConverter()
converter = converter_cls()

# succeeds
assert A(1) == converter.structure(dict(i=1), A)
Expand Down

0 comments on commit ef61ab6

Please sign in to comment.