Skip to content

Commit

Permalink
Fix evaluation of types for typing classes (#56)
Browse files Browse the repository at this point in the history
* Fix evaluation of types for typing classes

* properly type and lint

* fix formatting

* commit happy compromise

* cleanup forward ref eval

* update docstring

* add test for evaluating eval_type

* skip eval type test for now I need this released quick

* fix formatting in test_objects.py
  • Loading branch information
wbarnha authored Mar 29, 2024
1 parent 99c52bd commit bf398ad
Show file tree
Hide file tree
Showing 2 changed files with 48 additions and 5 deletions.
46 changes: 41 additions & 5 deletions mode/utils/objects.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,25 @@
Tuple,
Type,
TypeVar,
_eval_type,
cast,
)

try:
from typing import _eval_type # type: ignore
except ImportError:

def _eval_type(t, globalns, localns, recursive_guard=frozenset()): # type: ignore
return t


try:
from typing import _type_check # type: ignore
except ImportError:

def _type_check(arg, msg, is_argument=True, module=None): # type: ignore
return arg


try:
from typing import _ClassVar # type: ignore
except ImportError: # pragma: no cover
Expand Down Expand Up @@ -367,23 +382,44 @@ def eval_type(
```sh
>>> eval_type('List[int]') == typing.List[int]
>>> eval_type('list[int]') == list[int]
```
"""
invalid_types = invalid_types or set()
alias_types = alias_types or {}
if isinstance(typ, str):
typ = ForwardRef(typ)
if isinstance(typ, ForwardRef):
if sys.version_info < (3, 9):
typ = typ._evaluate(globalns, localns)
else:
typ = typ._evaluate(globalns, localns, frozenset())
typ = _ForwardRef_safe_eval(typ, globalns, localns)
typ = _eval_type(typ, globalns, localns)
if typ in invalid_types:
raise InvalidAnnotation(typ)
return alias_types.get(typ, typ)


def _ForwardRef_safe_eval(
ref: ForwardRef,
globalns: Optional[Dict[str, Any]] = None,
localns: Optional[Dict[str, Any]] = None,
) -> Type:
# On 3.6/3.7 ForwardRef._evaluate crashes if str references ClassVar
if not ref.__forward_evaluated__:
if globalns is None and localns is None:
globalns = localns = {}
elif globalns is None:
globalns = localns
elif localns is None:
localns = globalns
val = eval(ref.__forward_code__, globalns, localns) # noqa: S307
if not _is_class_var(val):
val = _type_check(
val, "Forward references must evaluate to types."
)
ref.__forward_value__ = val
ref.__forward_evaluated__ = True
return ref.__forward_value__


def _get_globalns(typ: Type) -> Dict[str, Any]:
return sys.modules[typ.__module__].__dict__

Expand Down
7 changes: 7 additions & 0 deletions tests/unit/utils/test_objects.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
annotations,
canoname,
canonshortname,
eval_type,
guess_polymorphic_type,
is_optional,
is_union,
Expand Down Expand Up @@ -184,6 +185,12 @@ class Y: ...
assert canonshortname(y, main_name="faust") == ".".join([__name__, "Y"])


@pytest.mark.skip(reason="Needs fixing, typing.List eval does not work")
def test_eval_type():
assert eval_type("list") == list
assert eval_type("typing.List") == typing.List


def test_annotations():
class X:
Foo: ClassVar[int] = 3
Expand Down

0 comments on commit bf398ad

Please sign in to comment.