diff --git a/mode/utils/objects.py b/mode/utils/objects.py index 8a515809..8c197dc2 100644 --- a/mode/utils/objects.py +++ b/mode/utils/objects.py @@ -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 @@ -367,6 +382,7 @@ def eval_type( ```sh >>> eval_type('List[int]') == typing.List[int] + >>> eval_type('list[int]') == list[int] ``` """ invalid_types = invalid_types or set() @@ -374,16 +390,36 @@ def eval_type( 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__ diff --git a/tests/unit/utils/test_objects.py b/tests/unit/utils/test_objects.py index e1b6817f..4ae4081c 100644 --- a/tests/unit/utils/test_objects.py +++ b/tests/unit/utils/test_objects.py @@ -34,6 +34,7 @@ annotations, canoname, canonshortname, + eval_type, guess_polymorphic_type, is_optional, is_union, @@ -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