Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

VS-1557: Allow unstructuring of attrs classes in typing.Any-typed fields #17

Merged
merged 2 commits into from
Nov 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## v2.0.2

- Fixes regression in post cattrs-1.1.2 where attrs objects in `typing.Any` fields are
returned as-is and not unstructured

## v2.0.1

- Fixes error in structuring parameterized generic wildcats
Expand Down
19 changes: 19 additions & 0 deletions tests/test_try_struc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from typecats import Cat, try_struc


def test_with_no_default():
@Cat
class Thing(dict):
name: str

assert try_struc(Thing, None) is None
assert Thing.try_struc(None) is None


def test_with_default():
@Cat
class Thing(dict):
name: str = ""

assert try_struc(Thing, None) is None
assert Thing.try_struc(None) is None
Comment on lines +1 to +19
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had this file leftover in my working tree from something in the past. It's not related to this PR but wouldn't hurt.

71 changes: 71 additions & 0 deletions tests/test_unstruc_any.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import typing as ty

from typecats import Cat, register_unstruc_hook_func


@Cat
class Container:
one: ty.Any
many: ty.List[ty.Any]


@Cat
class SecondContainer:
thing: ty.Any


@Cat
class SomeCat:
field: str


class HasUnstrucHook:
def __init__(self, unstruc_field):
self.unstruc_field = unstruc_field


class WithoutUnstrucHook:
def __init__(self, some_field):
self.some_field = some_field


def test_unstruc_cat_types_in_any_fields():
container = Container(
one=SecondContainer(
thing=SomeCat(field="a"),
),
many=[
SecondContainer(
thing=SomeCat(field="b"),
),
SomeCat(field="c"),
],
)

expected = {
"one": {"thing": {"field": "a"}},
"many": [{"thing": {"field": "b"}}, {"field": "c"}],
}

assert container.unstruc() == expected


def test_unstruc_of_non_cat_in_any_field_with_unstruc_hook():
register_unstruc_hook_func(
lambda t: t == HasUnstrucHook, lambda x: {"unstruc_field": x.unstruc_field}
)

# This has no unstruc hook registered and should therefore be returned as-is
no_unstruc = WithoutUnstrucHook("some_str_0")

container = Container(
one=HasUnstrucHook("some_str_1"),
many=[no_unstruc, HasUnstrucHook("some_str_2")],
)

expected = {
"one": {"unstruc_field": "some_str_1"},
"many": [no_unstruc, {"unstruc_field": "some_str_2"}],
}

assert container.unstruc() == expected
2 changes: 1 addition & 1 deletion typecats/__about__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""typecats"""
__version__ = "2.0.1"
__version__ = "2.0.2"
__author__ = "Peter Gaultney"
__author_email__ = "pgaultney@xoi.io"
18 changes: 18 additions & 0 deletions typecats/patch.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,20 @@ def unstructure_strip_defaults(obj):
return unstructure_strip_defaults


def _is_any(cl: ty.Type) -> bool:
return cl == ty.Any


def unstructure_any_from_runtime_type(
gen_converter: GenConverter, obj: ty.Any
) -> ty.Union[ty.Any, dict]:
cls = type(obj)
if is_attrs_class(cls):
return gen_converter.unstructure(obj, cls)
else:
return gen_converter.unstructure(obj)


def patch_converter_for_typecats(converter: GenConverter) -> GenConverter:
if converter in __patched_converters:
return converter
Expand All @@ -70,5 +84,9 @@ def patch_converter_for_typecats(converter: GenConverter) -> GenConverter:
has_with_generic, partial(unstructure_strip_defaults_factory, converter)
)

converter.register_unstructure_hook_func(
_is_any, partial(unstructure_any_from_runtime_type, converter)
)

__patched_converters.append(converter)
return converter