Skip to content

Commit

Permalink
More modernization (#592)
Browse files Browse the repository at this point in the history
* More modernization

* Improve coverage

* Relock

* Remove dead code

* More coverage
  • Loading branch information
Tinche authored Oct 19, 2024
1 parent dce7347 commit 7235f7b
Show file tree
Hide file tree
Showing 14 changed files with 121 additions and 234 deletions.
3 changes: 2 additions & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:
python-version: "${{ matrix.python-version }}"
allow-python-prereleases: true
cache: true
version: "2.18.1"
version: "2.19.2"

- name: "Run Tox"
run: |
Expand Down Expand Up @@ -107,6 +107,7 @@ jobs:
- uses: "pdm-project/setup-pdm@v4"
with:
python-version: "3.12"
version: "2.19.2"

- name: "Install check-wheel-content and twine"
run: "python -m pip install twine check-wheel-contents"
Expand Down
43 changes: 2 additions & 41 deletions pdm.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 5 additions & 2 deletions src/cattrs/_compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,8 +149,11 @@ def fields_dict(type) -> dict[str, Union[Attribute, Field]]:
return attrs_fields_dict(type)


def adapted_fields(cl) -> list[Attribute]:
"""Return the attrs format of `fields()` for attrs and dataclasses."""
def adapted_fields(cl: type) -> list[Attribute]:
"""Return the attrs format of `fields()` for attrs and dataclasses.
Resolves `attrs` stringified annotations, if present.
"""
if is_dataclass(cl):
attrs = dataclass_fields(cl)
if any(isinstance(a.type, str) for a in attrs):
Expand Down
10 changes: 1 addition & 9 deletions src/cattrs/gen/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from collections.abc import Iterable, Mapping
from typing import TYPE_CHECKING, Any, Callable, Final, Literal, TypeVar

from attrs import NOTHING, Attribute, Factory, resolve_types
from attrs import NOTHING, Attribute, Factory
from typing_extensions import NoDefault

from .._compat import (
Expand Down Expand Up @@ -240,10 +240,6 @@ def make_dict_unstructure_fn(
origin = get_origin(cl)
attrs = adapted_fields(origin or cl) # type: ignore

if any(isinstance(a.type, str) for a in attrs):
# PEP 563 annotations - need to be resolved.
resolve_types(cl)

mapping = {}
if is_generic(cl):
mapping = generate_mapping(cl, mapping)
Expand Down Expand Up @@ -743,10 +739,6 @@ def make_dict_structure_fn(

attrs = adapted_fields(cl)

if any(isinstance(a.type, str) for a in attrs):
# PEP 563 annotations - need to be resolved.
resolve_types(cl)

# We keep track of what we're generating to help with recursive
# class graphs.
try:
Expand Down
14 changes: 2 additions & 12 deletions src/cattrs/gen/typeddicts.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from typing import TYPE_CHECKING, Any, Callable, Literal, TypeVar

from attrs import NOTHING, Attribute
from typing_extensions import _TypedDictMeta

try:
from inspect import get_annotations
Expand All @@ -15,17 +16,8 @@ def get_annots(cl) -> dict[str, Any]:
except ImportError:
# https://docs.python.org/3/howto/annotations.html#accessing-the-annotations-dict-of-an-object-in-python-3-9-and-older
def get_annots(cl) -> dict[str, Any]:
if isinstance(cl, type):
ann = cl.__dict__.get("__annotations__", {})
else:
ann = getattr(cl, "__annotations__", {})
return ann

return cl.__dict__.get("__annotations__", {})

try:
from typing_extensions import _TypedDictMeta
except ImportError:
_TypedDictMeta = None

from .._compat import (
TypedDict,
Expand Down Expand Up @@ -535,8 +527,6 @@ def _adapted_fields(cls: Any) -> list[Attribute]:


def _is_extensions_typeddict(cls) -> bool:
if _TypedDictMeta is None:
return False
return cls.__class__ is _TypedDictMeta or (
is_generic(cls) and (cls.__origin__.__class__ is _TypedDictMeta)
)
Expand Down
4 changes: 3 additions & 1 deletion tests/test_gen_dict.py
Original file line number Diff line number Diff line change
Expand Up @@ -336,18 +336,20 @@ def test_overriding_struct_hook(converter: BaseConverter) -> None:
class A:
a: int
b: str
c: int = 0

converter.register_structure_hook(
A,
make_dict_structure_fn(
A,
converter,
a=override(struct_hook=lambda v, _: ceil(v)),
c=override(struct_hook=lambda v, _: ceil(v)),
_cattrs_detailed_validation=converter.detailed_validation,
),
)

assert converter.structure({"a": 0.5, "b": 1}, A) == A(1, "1")
assert converter.structure({"a": 0.5, "b": 1, "c": 0.5}, A) == A(1, "1", 1)


def test_overriding_unstruct_hook(converter: BaseConverter) -> None:
Expand Down
2 changes: 1 addition & 1 deletion tests/test_gen_dict_563.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from dataclasses import dataclass

from attr import define
from attrs import define

from cattrs import Converter
from cattrs.gen import make_dict_structure_fn, make_dict_unstructure_fn
Expand Down
34 changes: 18 additions & 16 deletions tests/test_generics.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

T = TypeVar("T")
T2 = TypeVar("T2")
T3 = TypeVar("T3", bound=int)


def test_deep_copy():
Expand All @@ -31,9 +32,10 @@ def test_deep_copy():


@define
class TClass(Generic[T, T2]):
class TClass(Generic[T, T2, T3]):
a: T
b: T2
c: T3 = 0


@define
Expand All @@ -44,15 +46,15 @@ class GenericCols(Generic[T]):


@pytest.mark.parametrize(
("t", "t2", "result"),
("t", "t2", "t3", "result"),
(
(int, str, TClass(1, "a")),
(str, str, TClass("1", "a")),
(List[int], str, TClass([1, 2, 3], "a")),
(int, str, int, TClass(1, "a")),
(str, str, int, TClass("1", "a")),
(List[int], str, int, TClass([1, 2, 3], "a")),
),
)
def test_able_to_structure_generics(converter: BaseConverter, t, t2, result):
res = converter.structure(asdict(result), TClass[t, t2])
def test_able_to_structure_generics(converter: BaseConverter, t, t2, t3, result):
res = converter.structure(asdict(result), TClass[t, t2, t3])

assert res == result

Expand Down Expand Up @@ -103,20 +105,20 @@ class GenericCols(Generic[T]):


@pytest.mark.parametrize(
("t", "t2", "result"),
("t", "t2", "t3", "result"),
(
(TClass[int, int], str, TClass(TClass(1, 2), "a")),
(List[TClass[int, int]], str, TClass([TClass(1, 2)], "a")),
(TClass[int, int, int], str, int, TClass(TClass(1, 2), "a")),
(List[TClass[int, int, int]], str, int, TClass([TClass(1, 2)], "a")),
),
)
def test_structure_nested_generics(converter: BaseConverter, t, t2, result):
res = converter.structure(asdict(result), TClass[t, t2])
def test_structure_nested_generics(converter: BaseConverter, t, t2, t3, result):
res = converter.structure(asdict(result), TClass[t, t2, t3])

assert res == result


def test_able_to_structure_deeply_nested_generics_gen(converter):
cl = TClass[TClass[TClass[int, int], int], int]
cl = TClass[TClass[TClass[int, int, int], int, int], int, int]
result = TClass(TClass(TClass(1, 2), 3), 4)

res = converter.structure(asdict(result), cl)
Expand All @@ -130,7 +132,7 @@ class TClass2(Generic[T]):
c: T

data = TClass2(c="string")
res = converter.structure(asdict(data), Union[TClass[int, int], TClass2[str]])
res = converter.structure(asdict(data), Union[TClass[int, int, int], TClass2[str]])
assert res == data


Expand All @@ -141,7 +143,7 @@ class TClass2(Generic[T]):

data = [TClass2(c="string"), TClass(1, 2)]
res = converter.structure(
[asdict(x) for x in data], List[Union[TClass[int, int], TClass2[str]]]
[asdict(x) for x in data], List[Union[TClass[int, int, int], TClass2[str]]]
)
assert res == data

Expand All @@ -153,7 +155,7 @@ class TClass2(Generic[T]):

data = deque((TClass2(c="string"), TClass(1, 2)))
res = converter.structure(
[asdict(x) for x in data], Deque[Union[TClass[int, int], TClass2[str]]]
[asdict(x) for x in data], Deque[Union[TClass[int, int, int], TClass2[str]]]
)
assert res == data

Expand Down
4 changes: 2 additions & 2 deletions tests/test_preconf.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from json import dumps as json_dumps
from json import loads as json_loads
from platform import python_implementation
from typing import Any, Dict, Final, List, NamedTuple, NewType, Tuple, Union
from typing import Any, Dict, Final, List, NamedTuple, NewType, Union

import pytest
from attrs import define
Expand Down Expand Up @@ -190,7 +190,7 @@ def native_unions(
include_datetimes=True,
include_objectids=False,
include_literals=True,
) -> Tuple[Any, Any]:
) -> tuple[Any, Any]:
types = []
strats = {}
if include_strings:
Expand Down
16 changes: 8 additions & 8 deletions tests/test_unions.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from typing import Type, Union

import attr
import pytest
from attrs import define

from cattrs.converters import BaseConverter, Converter

Expand All @@ -18,11 +18,11 @@ def test_custom_union_toplevel_roundtrip(cls: Type[BaseConverter]):
"""
c = cls()

@attr.define
@define
class A:
a: int

@attr.define
@define
class B:
a: int

Expand Down Expand Up @@ -51,11 +51,11 @@ def test_310_custom_union_toplevel_roundtrip(cls: Type[BaseConverter]):
"""
c = cls()

@attr.define
@define
class A:
a: int

@attr.define
@define
class B:
a: int

Expand Down Expand Up @@ -83,15 +83,15 @@ def test_custom_union_clsfield_roundtrip(cls: Type[BaseConverter]):
"""
c = cls()

@attr.define
@define
class A:
a: int

@attr.define
@define
class B:
a: int

@attr.define
@define
class C:
f: Union[A, B]

Expand Down
6 changes: 2 additions & 4 deletions tests/test_unstructure.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
"""Tests for dumping."""

from typing import Type

from attr import asdict, astuple
from hypothesis import given
from hypothesis.strategies import data, just, lists, one_of, sampled_from

from cattr.converters import BaseConverter, UnstructureStrategy
from cattrs.converters import BaseConverter, UnstructureStrategy

from .untyped import (
dicts_of_primitives,
Expand Down Expand Up @@ -126,7 +124,7 @@ class Bar:


@given(lists(simple_classes()), one_of(just(tuple), just(list)))
def test_seq_of_simple_classes_unstructure(cls_and_vals, seq_type: Type):
def test_seq_of_simple_classes_unstructure(cls_and_vals, seq_type: type):
"""Dumping a sequence of primitives is a simple copy operation."""
converter = BaseConverter()

Expand Down
Loading

0 comments on commit 7235f7b

Please sign in to comment.