Skip to content

Commit

Permalink
Add "resolve_types" argument to define()
Browse files Browse the repository at this point in the history
  • Loading branch information
sscherfke committed Dec 25, 2024
1 parent 598494a commit 0f3cf76
Show file tree
Hide file tree
Showing 5 changed files with 50 additions and 0 deletions.
2 changes: 2 additions & 0 deletions src/attr/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,7 @@ def attrs(
field_transformer: _FieldTransformer | None = ...,
match_args: bool = ...,
unsafe_hash: bool | None = ...,
resolve_types: bool = ...,
) -> _C: ...
@overload
@dataclass_transform(order_default=True, field_specifiers=(attrib, field))
Expand Down Expand Up @@ -307,6 +308,7 @@ def attrs(
field_transformer: _FieldTransformer | None = ...,
match_args: bool = ...,
unsafe_hash: bool | None = ...,
resolve_types: bool = ...,
) -> Callable[[_C], _C]: ...
def fields(cls: type[AttrsInstance]) -> Any: ...
def fields_dict(cls: type[AttrsInstance]) -> dict[str, Attribute[Any]]: ...
Expand Down
13 changes: 13 additions & 0 deletions src/attr/_make.py
Original file line number Diff line number Diff line change
Expand Up @@ -645,6 +645,7 @@ class _ClassBuilder:
"_is_exc",
"_on_setattr",
"_pre_init_has_args",
"_resolve_types",
"_slots",
"_weakref_slot",
"_wrote_own_setattr",
Expand All @@ -666,6 +667,7 @@ def __init__(
on_setattr,
has_custom_setattr,
field_transformer,
resolve_types,
):
attrs, base_attrs, base_map = _transform_attrs(
cls,
Expand All @@ -683,6 +685,7 @@ def __init__(
self._base_attr_map = base_map
self._attr_names = tuple(a.name for a in attrs)
self._slots = slots
self._resolve_types = resolve_types
self._frozen = frozen
self._weakref_slot = weakref_slot
self._cache_hash = cache_hash
Expand Down Expand Up @@ -766,6 +769,12 @@ def build_class(self):
):
cls.__attrs_init_subclass__()

if self._resolve_types:
# Need to import here to avoid circular imports
from . import _funcs

cls = _funcs.resolve_types(cls)

return cls

def _patch_original_class(self):
Expand Down Expand Up @@ -1267,6 +1276,7 @@ def attrs(
field_transformer=None,
match_args=True,
unsafe_hash=None,
resolve_types=False,
):
r"""
A class decorator that adds :term:`dunder methods` according to the
Expand Down Expand Up @@ -1333,6 +1343,8 @@ def attrs(
If a class has an *inherited* classmethod called
``__attrs_init_subclass__``, it is executed after the class is created.
.. deprecated:: 24.1.0 *hash* is deprecated in favor of *unsafe_hash*.
.. versionadded:: 25.1.0
Added the *resolve_types* argument.
"""
if repr_ns is not None:
import warnings
Expand Down Expand Up @@ -1385,6 +1397,7 @@ def wrap(cls):
on_setattr,
has_own_setattr,
field_transformer,
resolve_types,
)
if _determine_whether_to_implement(
cls, repr, auto_detect, ("__repr__",)
Expand Down
12 changes: 12 additions & 0 deletions src/attr/_next_gen.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ def define(
on_setattr=None,
field_transformer=None,
match_args=True,
resolve_types=False,
):
r"""
A class decorator that adds :term:`dunder methods` according to
Expand Down Expand Up @@ -235,6 +236,14 @@ def define(
non-keyword-only ``__init__`` parameter names on Python 3.10 and
later. Ignored on older Python versions.
resolve_types (bool):
If True, automatically call :func:`~attrs.resolve_types()` on the
class.
If you need to explicitly pass a global or local namespace, you
should leave this at False and explicitly call
:func:`~attrs.resolve_types()` instead.
collect_by_mro (bool):
If True, *attrs* collects attributes from base classes correctly
according to the `method resolution order
Expand Down Expand Up @@ -319,6 +328,8 @@ def define(
.. versionadded:: 24.3.0
Unless already present, a ``__replace__`` method is automatically
created for `copy.replace` (Python 3.13+ only).
.. versionadded:: 25.1.0
Added the *resolve_types* argument.
.. note::
Expand Down Expand Up @@ -366,6 +377,7 @@ def do_it(cls, auto_attribs):
on_setattr=on_setattr,
field_transformer=field_transformer,
match_args=match_args,
resolve_types=resolve_types,
)

def wrap(cls):
Expand Down
2 changes: 2 additions & 0 deletions src/attrs/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ def define(
on_setattr: _OnSetAttrArgType | None = ...,
field_transformer: _FieldTransformer | None = ...,
match_args: bool = ...,
resolve_types: bool = ...,
) -> _C: ...
@overload
@dataclass_transform(field_specifiers=(attrib, field))
Expand All @@ -205,6 +206,7 @@ def define(
on_setattr: _OnSetAttrArgType | None = ...,
field_transformer: _FieldTransformer | None = ...,
match_args: bool = ...,
resolve_types: bool = ...,
) -> Callable[[_C], _C]: ...

mutable = define
Expand Down
21 changes: 21 additions & 0 deletions tests/test_next_gen.py
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,27 @@ class D(B, C):

assert d.x == d.xx()

def test_resolve_types(self):
"""
Types can optionally be resolve directly by the decorator.
"""
@attrs.define(resolve_types=True)
class A:
x: "int" = 10

assert attrs.fields(A).x.type is int

def test_resolve_types_default_off(self):
"""
Types are not resolved by default.
"""

@attrs.define(resolve_types=False)
class A:
x: "int" = 10

assert attrs.fields(A).x.type == "int"


class TestAsTuple:
def test_smoke(self):
Expand Down

0 comments on commit 0f3cf76

Please sign in to comment.