Skip to content

Commit

Permalink
Merge ee74e4e into 1663fb5
Browse files Browse the repository at this point in the history
  • Loading branch information
bswck committed Apr 16, 2024
2 parents 1663fb5 + ee74e4e commit a44384c
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 5 deletions.
4 changes: 2 additions & 2 deletions eval_type_backport/__init__.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
from .eval_type_backport import eval_type_backport
from .eval_type_backport import ForwardRef, eval_type_backport

try:
from .version import __version__
except ImportError: # pragma: no cover
# version.py is auto-generated with the git tag when building
__version__ = '???'

__all__ = ['eval_type_backport', '__version__']
__all__ = ['ForwardRef', 'eval_type_backport', '__version__']
28 changes: 28 additions & 0 deletions eval_type_backport/eval_type_backport.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,34 @@ def visit_Subscript(self, node) -> ast.Subscript:
return ast.fix_missing_locations(replacement)


class ForwardRef(typing.ForwardRef, _root=True): # type: ignore[call-arg,misc]
"""
Like `typing.ForwardRef`, but lets older Python versions use newer typing features.
Specifically, when evaluated, this transforms `X | Y` into `typing.Union[X, Y]`
and `list[X]` into `typing.List[X]` etc. (for all the types made generic in PEP 585)
if the original syntax is not supported in the current Python version.
"""

def _evaluate(
self,
globalns: dict[str, Any] | None,
localns: dict[str, Any] | None,
recursive_guard: frozenset[str] | None = None,
) -> Any:
try:
return super()._evaluate(
globalns,
localns,
# assume `recursive_guard` is provided by typing,
# so if it's not None, it is supported in typing.ForwardRef
*() if recursive_guard is None else (recursive_guard,),
)
except TypeError as e:
if not is_backport_fixable_error(e):
raise
return _eval_direct(self, globalns, localns)


def _eval_direct(
value: typing.ForwardRef,
globalns: dict[str, Any] | None = None,
Expand Down
40 changes: 37 additions & 3 deletions tests/test_eval_type_backport.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,23 @@
from __future__ import annotations

import collections
import contextlib
import importlib
import re
import sys
import typing as t

import pytest

from eval_type_backport import eval_type_backport
from eval_type_backport.eval_type_backport import new_generic_types
from eval_type_backport import ForwardRef, eval_type_backport
from eval_type_backport.eval_type_backport import _eval_direct, new_generic_types

str((collections, contextlib, re)) # mark these as used (by eval calls)


eval_type = t._eval_type # type: ignore[attr-defined]


def eval_kwargs(code: str):
result = []
for globalns in (None, globals(), {'t': t}, {}):
Expand Down Expand Up @@ -43,7 +49,7 @@ def check_eval(code: str, expected: t.Any):
if sys.version_info >= (3, 10):
assert eval(code) == expected
assert (
t._eval_type( # type: ignore
eval_type( # type: ignore
ref,
globalns=kwargs['globalns'],
localns=kwargs['localns'],
Expand Down Expand Up @@ -346,3 +352,31 @@ def test_copy_forward_ref_attrs():
**({} if sys.version_info < (3, 9, 8) else {'is_class': True}),
)
eval_type_backport(ref, globalns=globals(), localns=locals())


def test_forward_ref_calls_eval_direct(monkeypatch: pytest.MonkeyPatch):
eval_direct_called: bool

def _eval_direct_wrapper(
value: t.ForwardRef,
globalns: dict[str, t.Any] | None = None,
localns: dict[str, t.Any] | None = None,
):
nonlocal eval_direct_called
eval_direct_called = True
return _eval_direct(value, globalns, localns)

monkeypatch.setattr(
importlib.import_module('eval_type_backport.eval_type_backport'),
'_eval_direct',
_eval_direct_wrapper,
)

for ref_arg, should_call_eval_direct in (
('int', False),
('list[int]', sys.version_info < (3, 9)),
('int | str', sys.version_info < (3, 10)),
):
eval_direct_called = False
eval_type(ForwardRef(ref_arg), None, None)
assert eval_direct_called is should_call_eval_direct

0 comments on commit a44384c

Please sign in to comment.