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: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ $ git clone [email protected]:your_name_here/cattrs.git

```shell
$ cd cattrs/
$ poetry install
$ poetry install --all-extras
```

4. Create a branch for local development::
Expand Down
1 change: 1 addition & 0 deletions HISTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
([#350](https://github.com/python-attrs/cattrs/issues/350) [#353](https://github.com/python-attrs/cattrs/pull/353))
- Subclasses structuring and unstructuring is now supported via a custom `include_subclasses` strategy.
([#312](https://github.com/python-attrs/cattrs/pull/312))
- Add support for `typing_extensions.Annotated` when the python version is less than `3.9`. ([#366](https://github.com/python-attrs/cattrs/pull/366))
- Add unstructuring and structuring support for the standard library `deque`.
([#355](https://github.com/python-attrs/cattrs/pull/355))

Expand Down
8 changes: 4 additions & 4 deletions src/cattrs/_compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,17 +174,17 @@ def get_final_base(type) -> Optional[type]:

from collections import Counter as ColCounter
from typing import Counter, Union, _GenericAlias
from typing_extensions import Annotated, NotRequired, Required
from typing_extensions import get_origin as te_get_origin

if is_py38:
from typing import TypedDict, _TypedDictMeta
else:
_TypedDictMeta = None
TypedDict = ExtensionsTypedDict

from typing_extensions import NotRequired, Required

def is_annotated(_):
return False
def is_annotated(type) -> bool:
return te_get_origin(type) is Annotated

def is_tuple(type):
return type in (Tuple, tuple) or (
Expand Down
35 changes: 35 additions & 0 deletions tests/test_converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -653,3 +653,38 @@ class Outer:

structured = converter.structure(raw, Outer)
assert structured == Outer(Inner(2), [Inner(2)])


def test_annotated_with_typing_extensions_attrs():
"""Annotation support works for attrs classes."""
from typing_extensions import Annotated
from typing import List

converter = Converter()

@attr.define
class Inner:
a: int

@attr.define
class Outer:
i: Annotated[Inner, "test"] # noqa
j: List[Annotated[Inner, "test"]] # noqa

orig = Outer(Inner(1), [Inner(1)])
raw = converter.unstructure(orig)

assert raw == {"i": {"a": 1}, "j": [{"a": 1}]}

structured = converter.structure(raw, Outer)
assert structured == orig

# Now register a hook and rerun the test.
converter.register_unstructure_hook(Inner, lambda v: {"a": 2})

raw = converter.unstructure(Outer(Inner(1), [Inner(1)]))

assert raw == {"i": {"a": 2}, "j": [{"a": 2}]}

structured = converter.structure(raw, Outer)
assert structured == Outer(Inner(2), [Inner(2)])