Skip to content
Merged
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
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