Skip to content

Commit 0f3cf76

Browse files
committed
Add "resolve_types" argument to define()
Fixes: #1286
1 parent 598494a commit 0f3cf76

File tree

5 files changed

+50
-0
lines changed

5 files changed

+50
-0
lines changed

src/attr/__init__.pyi

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,7 @@ def attrs(
279279
field_transformer: _FieldTransformer | None = ...,
280280
match_args: bool = ...,
281281
unsafe_hash: bool | None = ...,
282+
resolve_types: bool = ...,
282283
) -> _C: ...
283284
@overload
284285
@dataclass_transform(order_default=True, field_specifiers=(attrib, field))
@@ -307,6 +308,7 @@ def attrs(
307308
field_transformer: _FieldTransformer | None = ...,
308309
match_args: bool = ...,
309310
unsafe_hash: bool | None = ...,
311+
resolve_types: bool = ...,
310312
) -> Callable[[_C], _C]: ...
311313
def fields(cls: type[AttrsInstance]) -> Any: ...
312314
def fields_dict(cls: type[AttrsInstance]) -> dict[str, Attribute[Any]]: ...

src/attr/_make.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -645,6 +645,7 @@ class _ClassBuilder:
645645
"_is_exc",
646646
"_on_setattr",
647647
"_pre_init_has_args",
648+
"_resolve_types",
648649
"_slots",
649650
"_weakref_slot",
650651
"_wrote_own_setattr",
@@ -666,6 +667,7 @@ def __init__(
666667
on_setattr,
667668
has_custom_setattr,
668669
field_transformer,
670+
resolve_types,
669671
):
670672
attrs, base_attrs, base_map = _transform_attrs(
671673
cls,
@@ -683,6 +685,7 @@ def __init__(
683685
self._base_attr_map = base_map
684686
self._attr_names = tuple(a.name for a in attrs)
685687
self._slots = slots
688+
self._resolve_types = resolve_types
686689
self._frozen = frozen
687690
self._weakref_slot = weakref_slot
688691
self._cache_hash = cache_hash
@@ -766,6 +769,12 @@ def build_class(self):
766769
):
767770
cls.__attrs_init_subclass__()
768771

772+
if self._resolve_types:
773+
# Need to import here to avoid circular imports
774+
from . import _funcs
775+
776+
cls = _funcs.resolve_types(cls)
777+
769778
return cls
770779

771780
def _patch_original_class(self):
@@ -1267,6 +1276,7 @@ def attrs(
12671276
field_transformer=None,
12681277
match_args=True,
12691278
unsafe_hash=None,
1279+
resolve_types=False,
12701280
):
12711281
r"""
12721282
A class decorator that adds :term:`dunder methods` according to the
@@ -1333,6 +1343,8 @@ def attrs(
13331343
If a class has an *inherited* classmethod called
13341344
``__attrs_init_subclass__``, it is executed after the class is created.
13351345
.. deprecated:: 24.1.0 *hash* is deprecated in favor of *unsafe_hash*.
1346+
.. versionadded:: 25.1.0
1347+
Added the *resolve_types* argument.
13361348
"""
13371349
if repr_ns is not None:
13381350
import warnings
@@ -1385,6 +1397,7 @@ def wrap(cls):
13851397
on_setattr,
13861398
has_own_setattr,
13871399
field_transformer,
1400+
resolve_types,
13881401
)
13891402
if _determine_whether_to_implement(
13901403
cls, repr, auto_detect, ("__repr__",)

src/attr/_next_gen.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ def define(
4343
on_setattr=None,
4444
field_transformer=None,
4545
match_args=True,
46+
resolve_types=False,
4647
):
4748
r"""
4849
A class decorator that adds :term:`dunder methods` according to
@@ -235,6 +236,14 @@ def define(
235236
non-keyword-only ``__init__`` parameter names on Python 3.10 and
236237
later. Ignored on older Python versions.
237238
239+
resolve_types (bool):
240+
If True, automatically call :func:`~attrs.resolve_types()` on the
241+
class.
242+
243+
If you need to explicitly pass a global or local namespace, you
244+
should leave this at False and explicitly call
245+
:func:`~attrs.resolve_types()` instead.
246+
238247
collect_by_mro (bool):
239248
If True, *attrs* collects attributes from base classes correctly
240249
according to the `method resolution order
@@ -319,6 +328,8 @@ def define(
319328
.. versionadded:: 24.3.0
320329
Unless already present, a ``__replace__`` method is automatically
321330
created for `copy.replace` (Python 3.13+ only).
331+
.. versionadded:: 25.1.0
332+
Added the *resolve_types* argument.
322333
323334
.. note::
324335
@@ -366,6 +377,7 @@ def do_it(cls, auto_attribs):
366377
on_setattr=on_setattr,
367378
field_transformer=field_transformer,
368379
match_args=match_args,
380+
resolve_types=resolve_types,
369381
)
370382

371383
def wrap(cls):

src/attrs/__init__.pyi

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,7 @@ def define(
179179
on_setattr: _OnSetAttrArgType | None = ...,
180180
field_transformer: _FieldTransformer | None = ...,
181181
match_args: bool = ...,
182+
resolve_types: bool = ...,
182183
) -> _C: ...
183184
@overload
184185
@dataclass_transform(field_specifiers=(attrib, field))
@@ -205,6 +206,7 @@ def define(
205206
on_setattr: _OnSetAttrArgType | None = ...,
206207
field_transformer: _FieldTransformer | None = ...,
207208
match_args: bool = ...,
209+
resolve_types: bool = ...,
208210
) -> Callable[[_C], _C]: ...
209211

210212
mutable = define

tests/test_next_gen.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,27 @@ class D(B, C):
425425

426426
assert d.x == d.xx()
427427

428+
def test_resolve_types(self):
429+
"""
430+
Types can optionally be resolve directly by the decorator.
431+
"""
432+
@attrs.define(resolve_types=True)
433+
class A:
434+
x: "int" = 10
435+
436+
assert attrs.fields(A).x.type is int
437+
438+
def test_resolve_types_default_off(self):
439+
"""
440+
Types are not resolved by default.
441+
"""
442+
443+
@attrs.define(resolve_types=False)
444+
class A:
445+
x: "int" = 10
446+
447+
assert attrs.fields(A).x.type == "int"
448+
428449

429450
class TestAsTuple:
430451
def test_smoke(self):

0 commit comments

Comments
 (0)