Skip to content

Commit

Permalink
Address CR comments
Browse files Browse the repository at this point in the history
Add to stubs
Make it a decorator, because why not?
  • Loading branch information
euresti committed Jul 21, 2020
1 parent e47640b commit d99deba
Show file tree
Hide file tree
Showing 3 changed files with 31 additions and 1 deletion.
5 changes: 5 additions & 0 deletions src/attr/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,11 @@ class _Fields(Tuple[Attribute[Any], ...]):
def fields(cls: type) -> _Fields: ...
def fields_dict(cls: type) -> Dict[str, Attribute[Any]]: ...
def validate(inst: Any) -> None: ...
def resolve_types(
cls: _C,
globalns: Optional[Dict[str, Any]] = ...,
localns: Optional[Dict[str, Any]] = ...
) -> _C: ...

# TODO: add support for returning a proper attrs class from the mypy plugin
# we use Any instead of _CountingAttr so that e.g. `make_class('Foo', [attr.ib()])` is valid
Expand Down
10 changes: 9 additions & 1 deletion src/attr/_make.py
Original file line number Diff line number Diff line change
Expand Up @@ -1676,6 +1676,11 @@ def resolve_types(cls, globalns=None, localns=None):
"""
Resolve any strings and forward annotations in type annotations.
With no arguments, names will be looked up in the module in which the class was
created. If this is incorrect, e.g. if the name only exists inside a method,
you may pass globalns or localns to specify other dictionaries in which to look up
these names. See the docs of `typing.get_type_hints` for more details.
:param type cls: Class to resolve.
:param globalns: Dictionary containing global variables, if needed.
:param localns: Dictionary containing local variables, if needed.
Expand All @@ -1685,7 +1690,7 @@ def resolve_types(cls, globalns=None, localns=None):
class.
:raise NameError: If types cannot be resolved because of missing variables.
.. versionadded:: 17.4.0
.. versionadded:: 19.4.0
"""
try:
# Since calling get_type_hints is expensive we cache whether we've
Expand All @@ -1701,6 +1706,9 @@ def resolve_types(cls, globalns=None, localns=None):
_obj_setattr(field, "type", hints[field.name])
cls.__attrs_types_resolved__ = True

# Return the class so you can use it as a decorator too.
return cls


def validate(inst):
"""
Expand Down
17 changes: 17 additions & 0 deletions tests/test_annotations.py
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,23 @@ class A:
assert typing.List[int] == attr.fields(A).b.type
assert typing.List[int] == attr.fields(A).c.type

@pytest.mark.parametrize("slots", [True, False])
def test_resolve_types_decorator(self, slots):
"""
Types can be resolved using it as a decorator.
"""

@attr.resolve_types
@attr.s(slots=slots, auto_attribs=True)
class A:
a: typing.List[int]
b: typing.List["int"]
c: "typing.List[int]"

assert typing.List[int] == attr.fields(A).a.type
assert typing.List[int] == attr.fields(A).b.type
assert typing.List[int] == attr.fields(A).c.type

@pytest.mark.parametrize("slots", [True, False])
def test_self_reference(self, slots):
"""
Expand Down

0 comments on commit d99deba

Please sign in to comment.