From 70cb5136266948fcf6cac34354faff6eb0780218 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tin=20Tvrtkovi=C4=87?= Date: Thu, 30 Nov 2023 18:33:12 +0100 Subject: [PATCH 1/3] Fix unstructuring underspecified generics --- HISTORY.md | 2 ++ src/cattrs/converters.py | 2 +- tests/conftest.py | 5 +++++ tests/test_generics.py | 13 +++++++++++++ tests/test_generics_604.py | 16 ++++++++++++++++ 5 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 tests/test_generics_604.py diff --git a/HISTORY.md b/HISTORY.md index 21e223a5..519cae9f 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -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. diff --git a/src/cattrs/converters.py b/src/cattrs/converters.py index 5e9e2b9c..1fd1dfe8 100644 --- a/src/cattrs/converters.py +++ b/src/cattrs/converters.py @@ -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) diff --git a/tests/conftest.py b/tests/conftest.py index 69d978ca..8fb7c71d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,3 +1,4 @@ +import sys from os import environ import pytest @@ -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[0] <= (3, 10): + collect_ignore.append("tests/test_generics_604.py") diff --git a/tests/test_generics.py b/tests/test_generics.py index 9d075187..2e07342b 100644 --- a/tests/test_generics.py +++ b/tests/test_generics.py @@ -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] @@ -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: diff --git a/tests/test_generics_604.py b/tests/test_generics_604.py new file mode 100644 index 00000000..a224f1af --- /dev/null +++ b/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}} From b9eb2e33fe56b4e145c8164d9466072db7cf90a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tin=20Tvrtkovi=C4=87?= Date: Thu, 30 Nov 2023 18:38:43 +0100 Subject: [PATCH 2/3] Fix check --- tests/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/conftest.py b/tests/conftest.py index 8fb7c71d..21e2deac 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -30,5 +30,5 @@ def converter_cls(request): settings.load_profile("fast" if "FAST" in environ else "tests") collect_ignore = [] -if sys.version_info[0] <= (3, 10): +if sys.version_info <= (3, 10): collect_ignore.append("tests/test_generics_604.py") From 1664c32aa8afa557ee1c784d3c4672e890a1ef7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tin=20Tvrtkovi=C4=87?= Date: Thu, 30 Nov 2023 18:54:47 +0100 Subject: [PATCH 3/3] Fix ignore maybe? --- tests/conftest.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 21e2deac..47306491 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -30,5 +30,5 @@ def converter_cls(request): settings.load_profile("fast" if "FAST" in environ else "tests") collect_ignore = [] -if sys.version_info <= (3, 10): - collect_ignore.append("tests/test_generics_604.py") +if sys.version_info < (3, 10): + collect_ignore.append("test_generics_604.py")