Skip to content
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
2 changes: 2 additions & 0 deletions HISTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

- Fix a regression when unstructuring dictionary values typed as `Any`.
([#453](https://github.com/python-attrs/cattrs/issues/453) [#462](https://github.com/python-attrs/cattrs/pull/462))
- Fix a regression when unstructuring unspecialized generic classes.
([#465](https://github.com/python-attrs/cattrs/issues/465))
- Optimize function source code caching.
([#445](https://github.com/python-attrs/cattrs/issues/445))
- Generate unique files only in case of linecache enabled.
Expand Down
2 changes: 1 addition & 1 deletion src/cattrs/converters.py
Original file line number Diff line number Diff line change
Expand Up @@ -956,7 +956,7 @@ def gen_unstructure_optional(self, cl: Type[T]) -> Callable[[T], Any]:
other = union_params[0] if union_params[1] is NoneType else union_params[1]

# TODO: Remove this special case when we make unstructuring Any consistent.
if other is Any:
if other is Any or isinstance(other, TypeVar):
handler = self.unstructure
else:
handler = self._unstructure_func.dispatch(other)
Expand Down
5 changes: 5 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import sys
from os import environ

import pytest
Expand Down Expand Up @@ -27,3 +28,7 @@ def converter_cls(request):
settings.register_profile("fast", settings.get_profile("tests"), max_examples=10)

settings.load_profile("fast" if "FAST" in environ else "tests")

collect_ignore = []
if sys.version_info < (3, 10):
collect_ignore.append("test_generics_604.py")
13 changes: 13 additions & 0 deletions tests/test_generics.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,9 @@ def test_unstructure_generic_attrs(genconverter):
class Inner(Generic[T]):
a: T

inner = Inner(Inner(1))
assert genconverter.unstructure(inner) == {"a": {"a": 1}}

@define
class Outer:
inner: Inner[int]
Expand All @@ -203,6 +206,16 @@ class OuterStr:
assert genconverter.structure(raw, OuterStr) == OuterStr(Inner("1"))


def test_unstructure_optional(genconverter):
"""Generics with optional fields work."""

@define
class C(Generic[T]):
a: Union[T, None]

assert genconverter.unstructure(C(C(1))) == {"a": {"a": 1}}


def test_unstructure_deeply_nested_generics(genconverter):
@define
class Inner:
Expand Down
16 changes: 16 additions & 0 deletions tests/test_generics_604.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
"""Tests for generics under PEP 604 (unions as pipes)."""
from typing import Generic, TypeVar

from attrs import define

T = TypeVar("T")


def test_unstructure_optional(genconverter):
"""Generics with optional fields work."""

@define
class C(Generic[T]):
a: T | None

assert genconverter.unstructure(C(C(1))) == {"a": {"a": 1}}