Skip to content

Commit

Permalink
Fix unstructuring underspecified generics (#466)
Browse files Browse the repository at this point in the history
* Fix unstructuring underspecified generics

* Fix check

* Fix ignore maybe?
  • Loading branch information
Tinche committed Nov 30, 2023
1 parent 88d4758 commit 48cb3d0
Show file tree
Hide file tree
Showing 5 changed files with 37 additions and 1 deletion.
2 changes: 2 additions & 0 deletions HISTORY.md
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
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
@@ -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
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
@@ -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}}

0 comments on commit 48cb3d0

Please sign in to comment.