From db8b59624e9b55000562b162ee14107f3aca2192 Mon Sep 17 00:00:00 2001 From: AlexWaygood Date: Sun, 11 Jun 2023 19:26:17 +0100 Subject: [PATCH 1/6] Simplify `cattrs._compat.is_typeddict` --- src/cattrs/_compat.py | 36 +++++------------------------------- 1 file changed, 5 insertions(+), 31 deletions(-) diff --git a/src/cattrs/_compat.py b/src/cattrs/_compat.py index e6ad59f6..b337135e 100644 --- a/src/cattrs/_compat.py +++ b/src/cattrs/_compat.py @@ -20,15 +20,18 @@ from attr import fields as attrs_fields from attr import resolve_types +__all__ = ["ExtensionsTypedDict", "is_typeddict", "TypedDict"] + try: from typing_extensions import TypedDict as ExtensionsTypedDict except ImportError: ExtensionsTypedDict = None try: - from typing_extensions import _TypedDictMeta as ExtensionsTypedDictMeta + from typing_extensions import is_typeddict except ImportError: - ExtensionsTypedDictMeta = None + assert sys.version_info >= (3, 10) + from typing import is_typeddict if sys.version_info >= (3, 8): from typing import Final, Protocol, get_args, get_origin @@ -157,7 +160,6 @@ def get_final_base(type) -> Optional[type]: _AnnotatedAlias, _GenericAlias, _SpecialGenericAlias, - _TypedDictMeta, _UnionGenericAlias, ) @@ -234,20 +236,6 @@ def get_newtype_base(typ: Any) -> Optional[type]: return supertype return None - def is_typeddict(cls) -> bool: - return ( - cls.__class__ is _TypedDictMeta - or (is_generic(cls) and (cls.__origin__.__class__ is _TypedDictMeta)) - or ( - ExtensionsTypedDictMeta is not None - and cls.__class__ is ExtensionsTypedDictMeta - or ( - is_generic(cls) - and (cls.__origin__.__class__ is ExtensionsTypedDictMeta) - ) - ) - ) - def get_notrequired_base(type) -> "Union[Any, Literal[NOTHING]]": if get_origin(type) in (NotRequired, Required): return get_args(type)[0] @@ -462,20 +450,6 @@ def copy_with(type, args): """Replace a generic type's arguments.""" return type.copy_with(args) - def is_typeddict(cls) -> bool: - return ( - cls.__class__ is _TypedDictMeta - or (is_generic(cls) and (cls.__origin__.__class__ is _TypedDictMeta)) - or ( - ExtensionsTypedDictMeta is not None - and cls.__class__ is ExtensionsTypedDictMeta - or ( - is_generic(cls) - and (cls.__origin__.__class__ is ExtensionsTypedDictMeta) - ) - ) - ) - def get_notrequired_base(type) -> "Union[Any, Literal[NOTHING]]": if get_origin(type) in (NotRequired, Required): return get_args(type)[0] From 9b150c3bcd789f1890d2f6c6162901c385c42c9c Mon Sep 17 00:00:00 2001 From: AlexWaygood Date: Tue, 13 Jun 2023 16:47:37 +0100 Subject: [PATCH 2/6] Add `ExceptionGroup` to `__all__` as well --- src/cattrs/_compat.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/cattrs/_compat.py b/src/cattrs/_compat.py index b337135e..5aab65e6 100644 --- a/src/cattrs/_compat.py +++ b/src/cattrs/_compat.py @@ -20,7 +20,7 @@ from attr import fields as attrs_fields from attr import resolve_types -__all__ = ["ExtensionsTypedDict", "is_typeddict", "TypedDict"] +__all__ = ["ExceptionGroup", "ExtensionsTypedDict", "is_typeddict", "TypedDict"] try: from typing_extensions import TypedDict as ExtensionsTypedDict @@ -47,9 +47,9 @@ def get_origin(cl): from typing_extensions import Final, Protocol if sys.version_info >= (3, 11): - ExceptionGroup = ExceptionGroup + from builtins import ExceptionGroup else: - from exceptiongroup import ExceptionGroup as ExceptionGroup # noqa: PLC0414 + from exceptiongroup import ExceptionGroup def has(cls): From a2d6c85923f72dedace5e508706837ea77c7ee96 Mon Sep 17 00:00:00 2001 From: AlexWaygood Date: Wed, 14 Jun 2023 10:56:14 +0100 Subject: [PATCH 3/6] Fix the failing generic-typeddict test --- src/cattrs/_compat.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/cattrs/_compat.py b/src/cattrs/_compat.py index 5aab65e6..ca47b9b9 100644 --- a/src/cattrs/_compat.py +++ b/src/cattrs/_compat.py @@ -20,19 +20,13 @@ from attr import fields as attrs_fields from attr import resolve_types -__all__ = ["ExceptionGroup", "ExtensionsTypedDict", "is_typeddict", "TypedDict"] +__all__ = ["ExceptionGroup", "ExtensionsTypedDict", "TypedDict", "is_typeddict"] try: from typing_extensions import TypedDict as ExtensionsTypedDict except ImportError: ExtensionsTypedDict = None -try: - from typing_extensions import is_typeddict -except ImportError: - assert sys.version_info >= (3, 10) - from typing import is_typeddict - if sys.version_info >= (3, 8): from typing import Final, Protocol, get_args, get_origin @@ -51,6 +45,17 @@ def get_origin(cl): else: from exceptiongroup import ExceptionGroup +try: + from typing_extensions import is_typeddict as _is_typeddict +except ImportError: + assert sys.version_info >= (3, 10) + from typing import is_typeddict as _is_typeddict + + +def is_typeddict(cls): + """Thin wrapper around typing(_extensions).is_typeddict""" + return _is_typeddict(getattr(cls, "__origin__", cls)) + def has(cls): return hasattr(cls, "__attrs_attrs__") or hasattr(cls, "__dataclass_fields__") From b91ce0d3c59fef2cad149814285ae34d01d2eb2d Mon Sep 17 00:00:00 2001 From: AlexWaygood Date: Wed, 14 Jun 2023 11:04:32 +0100 Subject: [PATCH 4/6] More unnecessary imports of `_TypedDictMeta` --- src/cattrs/_compat.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/cattrs/_compat.py b/src/cattrs/_compat.py index ca47b9b9..98860ec7 100644 --- a/src/cattrs/_compat.py +++ b/src/cattrs/_compat.py @@ -357,9 +357,8 @@ def copy_with(type, args): from typing_extensions import get_origin as te_get_origin if sys.version_info >= (3, 8): - from typing import TypedDict, _TypedDictMeta + from typing import TypedDict else: - _TypedDictMeta = None TypedDict = ExtensionsTypedDict def is_annotated(type) -> bool: From 97774e2a6f8bdb71ec5b114a7daf4e706df794dc Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Wed, 14 Jun 2023 12:31:39 +0100 Subject: [PATCH 5/6] Changelog --- HISTORY.md | 1 + 1 file changed, 1 insertion(+) diff --git a/HISTORY.md b/HISTORY.md index da44cbbd..4e03196e 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -9,6 +9,7 @@ - Optimize and improve unstructuring of `Optional` (unions of one type and `None`). ([#380](https://github.com/python-attrs/cattrs/issues/380) [#381](https://github.com/python-attrs/cattrs/pull/381)) - Fix `format_exception` and `transform_error` type annotations. +- Improve the implementation of `cattrs._compat.is_typeddict`. The implementation is now simpler, and relies on fewer private implementation details from `typing` and typing_extensions. [#384](https://github.com/python-attrs/cattrs/pull/384) ## 23.1.2 (2023-06-02) From 019bc638a33c8bcc9d6f1b25e57ca2eeb0a0f964 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Wed, 14 Jun 2023 12:33:20 +0100 Subject: [PATCH 6/6] Brackets --- HISTORY.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index 4e03196e..0ee71b56 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -9,7 +9,7 @@ - Optimize and improve unstructuring of `Optional` (unions of one type and `None`). ([#380](https://github.com/python-attrs/cattrs/issues/380) [#381](https://github.com/python-attrs/cattrs/pull/381)) - Fix `format_exception` and `transform_error` type annotations. -- Improve the implementation of `cattrs._compat.is_typeddict`. The implementation is now simpler, and relies on fewer private implementation details from `typing` and typing_extensions. [#384](https://github.com/python-attrs/cattrs/pull/384) +- Improve the implementation of `cattrs._compat.is_typeddict`. The implementation is now simpler, and relies on fewer private implementation details from `typing` and typing_extensions. ([#384](https://github.com/python-attrs/cattrs/pull/384)) ## 23.1.2 (2023-06-02)