New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add eval_type_backport to handle union operator and builtin generic subscripting in older Pythons #8209
Add eval_type_backport to handle union operator and builtin generic subscripting in older Pythons #8209
Changes from 32 commits
75986a9
ca60caa
a163ea3
8033556
0495f0f
780b46a
4c536b6
d7f874b
09d2e93
d7b1462
c65ae3d
3aa88da
ca09a03
4890fc8
c2d9d22
40a41fb
7da17e0
a998d14
4f465a3
71ca7cf
f060876
2b42d65
dcbdd28
959c755
71c912e
9847058
6beab5b
b79692b
6bc0ee4
f423659
d3d5584
aa21092
8da4294
60aa70f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,7 +8,7 @@ | |
from collections.abc import Callable | ||
from functools import partial | ||
from types import GetSetDescriptorType | ||
from typing import TYPE_CHECKING, Any, Final, ForwardRef | ||
from typing import TYPE_CHECKING, Any, Final | ||
|
||
from typing_extensions import Annotated, Literal, TypeAliasType, TypeGuard, get_args, get_origin | ||
|
||
|
@@ -213,20 +213,52 @@ def get_cls_type_hints_lenient(obj: Any, globalns: dict[str, Any] | None = None) | |
return hints | ||
|
||
|
||
def eval_type_lenient(value: Any, globalns: dict[str, Any] | None, localns: dict[str, Any] | None) -> Any: | ||
def eval_type_lenient(value: Any, globalns: dict[str, Any] | None = None, localns: dict[str, Any] | None = None) -> Any: | ||
"""Behaves like typing._eval_type, except it won't raise an error if a forward reference can't be resolved.""" | ||
if value is None: | ||
value = NoneType | ||
elif isinstance(value, str): | ||
value = _make_forward_ref(value, is_argument=False, is_class=True) | ||
|
||
try: | ||
return typing._eval_type(value, globalns, localns) # type: ignore | ||
return eval_type_backport(value, globalns, localns) | ||
except NameError: | ||
# the point of this function is to be tolerant to this case | ||
return value | ||
|
||
|
||
def eval_type_backport( | ||
value: Any, globalns: dict[str, Any] | None = None, localns: dict[str, Any] | None = None | ||
) -> Any: | ||
"""Like `typing._eval_type`, but falls back to the `eval_type_backport` package if it's | ||
installed to let older Python versions use newer typing features. | ||
Specifically, 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. | ||
""" | ||
try: | ||
return typing._eval_type( # type: ignore | ||
value, globalns, localns | ||
) | ||
except TypeError as e: | ||
if not (isinstance(value, typing.ForwardRef) and is_backport_fixable_error(e)): | ||
raise | ||
try: | ||
from eval_type_backport import eval_type_backport | ||
except ImportError: | ||
raise RuntimeError( | ||
'In order to use newer typing features on older Python versions, ' | ||
'you need to install the `eval_type_backport` package.' | ||
) from e | ||
|
||
return eval_type_backport(value, globalns, localns, try_default=False) | ||
|
||
|
||
def is_backport_fixable_error(e: TypeError) -> bool: | ||
msg = str(e) | ||
return msg.startswith('unsupported operand type(s) for |: ') or "' object is not subscriptable" in msg | ||
|
||
|
||
def get_function_type_hints( | ||
function: Callable[..., Any], *, include_keys: set[str] | None = None, types_namespace: dict[str, Any] | None = None | ||
) -> dict[str, Any]: | ||
|
@@ -248,7 +280,7 @@ def get_function_type_hints( | |
elif isinstance(value, str): | ||
value = _make_forward_ref(value) | ||
|
||
type_hints[name] = typing._eval_type(value, globalns, types_namespace) # type: ignore | ||
type_hints[name] = eval_type_backport(value, globalns, types_namespace) | ||
|
||
return type_hints | ||
|
||
|
@@ -363,11 +395,15 @@ def get_type_hints( # noqa: C901 | |
if isinstance(value, str): | ||
value = _make_forward_ref(value, is_argument=False, is_class=True) | ||
|
||
value = typing._eval_type(value, base_globals, base_locals) # type: ignore | ||
value = eval_type_backport(value, base_globals, base_locals) | ||
hints[name] = value | ||
return ( | ||
hints if include_extras else {k: typing._strip_annotations(t) for k, t in hints.items()} # type: ignore | ||
) | ||
if not include_extras and hasattr(typing, '_strip_annotations'): | ||
return { | ||
k: typing._strip_annotations(t) # type: ignore | ||
for k, t in hints.items() | ||
} | ||
else: | ||
return hints | ||
|
||
if globalns is None: | ||
if isinstance(obj, types.ModuleType): | ||
|
@@ -403,28 +439,13 @@ def get_type_hints( # noqa: C901 | |
is_argument=not isinstance(obj, types.ModuleType), | ||
is_class=False, | ||
) | ||
value = typing._eval_type(value, globalns, localns) # type: ignore | ||
value = eval_type_backport(value, globalns, localns) | ||
if name in defaults and defaults[name] is None: | ||
value = typing.Optional[value] | ||
hints[name] = value | ||
return hints if include_extras else {k: typing._strip_annotations(t) for k, t in hints.items()} # type: ignore | ||
|
||
|
||
if sys.version_info < (3, 9): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why don't we need this anymore? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. AFAICT this could always have been replaced with |
||
|
||
def evaluate_fwd_ref( | ||
ref: ForwardRef, globalns: dict[str, Any] | None = None, localns: dict[str, Any] | None = None | ||
) -> Any: | ||
return ref._evaluate(globalns=globalns, localns=localns) | ||
|
||
else: | ||
|
||
def evaluate_fwd_ref( | ||
ref: ForwardRef, globalns: dict[str, Any] | None = None, localns: dict[str, Any] | None = None | ||
) -> Any: | ||
return ref._evaluate(globalns=globalns, localns=localns, recursive_guard=frozenset()) | ||
|
||
|
||
def is_dataclass(_cls: type[Any]) -> TypeGuard[type[StandardDataclass]]: | ||
# The dataclasses.is_dataclass function doesn't seem to provide TypeGuard functionality, | ||
# so I created this convenience function | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
???