Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft: Add "resolve_types" argument to define() #1390

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
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: 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
22 changes: 22 additions & 0 deletions tests/test_next_gen.py
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,28 @@ 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
Loading