From b6d12bd24660fcf5de09c97f58a85d21cfb08f46 Mon Sep 17 00:00:00 2001 From: Joseph Perez Date: Thu, 15 Oct 2020 15:02:01 +0200 Subject: [PATCH] Fix generic merged field in 3.6 --- apischema/dataclasses/cache.py | 14 +++++++------- setup.py | 2 +- tests/test_cache.py | 2 +- tests/test_metadata.py | 23 +++++++++++++++++++++++ 4 files changed, 32 insertions(+), 9 deletions(-) create mode 100644 tests/test_metadata.py diff --git a/apischema/dataclasses/cache.py b/apischema/dataclasses/cache.py index 7007aaad..01287a71 100644 --- a/apischema/dataclasses/cache.py +++ b/apischema/dataclasses/cache.py @@ -390,9 +390,13 @@ def _serialization( ) -def _deserialization_merged_aliases(cls: Type) -> AbstractSet[str]: +def _deserialization_merged_aliases(cls: Type, field_name: str) -> AbstractSet[str]: """Return all aliases used in cls deserialization.""" cls = getattr(cls, "__origin__", None) or cls + if not dataclasses.is_dataclass(cls): + raise TypeError( + f"{cls.__name__}.{field_name}: Merged field must have a dataclass type" + ) types = get_type_hints(cls, include_extras=True) result: Set[str] = set() for field in fields_items(cls).values(): @@ -401,7 +405,7 @@ def _deserialization_merged_aliases(cls: Type) -> AbstractSet[str]: if MERGED_METADATA in field.metadata: # No need to check overlapping here because it will be checked # when merged dataclass will be cached - result |= _deserialization_merged_aliases(types[field.name]) + result |= _deserialization_merged_aliases(types[field.name], field.name) elif PROPERTIES_METADATA in field.metadata: raise TypeError("Merged dataclass cannot have properties field") else: @@ -530,11 +534,7 @@ def cache_fields(cls: Type): if MERGED_METADATA in metadata: if any(key in metadata for key in INCOMPATIBLE_WITH_MERGED): raise TypeError(f"{error_prefix}Incompatible metadata with merged") - if not dataclasses.is_dataclass(field_type): - raise TypeError( - f"{error_prefix}Merged field must have a dataclass type" - ) - merged_aliases = _deserialization_merged_aliases(field_type) + merged_aliases = _deserialization_merged_aliases(field_type, field.name) lists.merged.append((merged_aliases, new_field)) elif PROPERTIES_METADATA in metadata: if any(key in metadata for key in INCOMPATIBLE_WITH_PROPERTIES): diff --git a/setup.py b/setup.py index 3dfaede0..877d3c68 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ setup( name="apischema", - version="0.10.1", + version="0.10.2", url="https://github.com/wyfo/apischema", author="Joseph Perez", author_email="joperez@hotmail.fr", diff --git a/tests/test_cache.py b/tests/test_cache.py index 6eb20482..4d1fb5ee 100644 --- a/tests/test_cache.py +++ b/tests/test_cache.py @@ -34,4 +34,4 @@ class D(Generic[T]): def test_merged_aliases(): - assert _deserialization_merged_aliases(A) == {"a", "g", "h", "i", "e"} + assert _deserialization_merged_aliases(A, "") == {"a", "g", "h", "i", "e"} diff --git a/tests/test_metadata.py b/tests/test_metadata.py new file mode 100644 index 00000000..15bfb963 --- /dev/null +++ b/tests/test_metadata.py @@ -0,0 +1,23 @@ +from dataclasses import dataclass, field +from typing import Generic, TypeVar + +from apischema import deserialize +from apischema.metadata import merged + +T = TypeVar("T") + + +@dataclass +class A(Generic[T]): + pass + + +@dataclass +class B(Generic[T]): + a1: A = field(metadata=merged) + a2: A[T] = field(metadata=merged) + a3: A[int] = field(metadata=merged) + + +def test_merged_generic_dataclass(): + deserialize(B, {}) # it works