Skip to content

Commit 21cd9da

Browse files
committed
1st working draft
1 parent 8386b24 commit 21cd9da

File tree

2 files changed

+190
-21
lines changed

2 files changed

+190
-21
lines changed

src/test_typing_extensions.py

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@
8282
clear_overloads,
8383
dataclass_transform,
8484
deprecated,
85+
evaluate_forward_ref,
8586
final,
8687
get_annotations,
8788
get_args,
@@ -8020,6 +8021,127 @@ def test_pep_695_generics_with_future_annotations_nested_in_function(self):
80208021
set(results.generic_func.__type_params__)
80218022
)
80228023

8024+
class TestEvaluateForwardRefs(BaseTestCase):
8025+
def test_evaluate_forward_refs(self):
8026+
from typing import ForwardRef # noqa: F401 # needed for globals/locals
8027+
8028+
error_res = None
8029+
annotation = Annotated[Union[int, None], "data"]
8030+
NoneAlias = None
8031+
StrAlias = str
8032+
T_default = TypeVar("T_default", default=None)
8033+
Ts = TypeVarTuple("Ts")
8034+
NoneType = type(None)
8035+
8036+
class _EasyStr:
8037+
def __str__(self):
8038+
return self.__class__.__name__
8039+
8040+
class A(_EasyStr): pass
8041+
8042+
class B(Generic[T], _EasyStr):
8043+
a = "A"
8044+
bT = "B[T]"
8045+
8046+
cases = {
8047+
None: {
8048+
Format.VALUE: NoneType,
8049+
Format.FORWARDREF: NoneType,
8050+
Format.STRING: "None",
8051+
},
8052+
"NoneAlias": {
8053+
Format.VALUE: NoneType,
8054+
Format.FORWARDREF: NoneType,
8055+
Format.STRING: "NoneAlias",
8056+
},
8057+
str: str,
8058+
"StrAlias": {
8059+
Format.VALUE: str,
8060+
Format.FORWARDREF: str,
8061+
Format.STRING: "StrAlias",
8062+
},
8063+
"A": A,
8064+
"B[A]": {
8065+
Format.VALUE: B[A],
8066+
Format.FORWARDREF: B[A],
8067+
Format.STRING: "B[A]",
8068+
},
8069+
"B[NotAvailiable]": {
8070+
Format.VALUE: NameError,
8071+
Format.FORWARDREF: typing.ForwardRef("B[NotAvailiable]"),
8072+
Format.STRING: "B[NotAvailiable]",
8073+
},
8074+
"B[B[T]]": {
8075+
Format.VALUE: B[B[T]],
8076+
Format.FORWARDREF: B[B[T]],
8077+
Format.STRING: "B[B[T]]",
8078+
},
8079+
"NotAvailiable": {
8080+
Format.VALUE: NameError,
8081+
Format.FORWARDREF: typing.ForwardRef("NotAvailiable"),
8082+
Format.STRING: "NotAvailiable",
8083+
},
8084+
"B.a": {
8085+
Format.VALUE: A,
8086+
Format.FORWARDREF: A,
8087+
Format.STRING: "B.a",
8088+
},
8089+
"B.bT": {
8090+
Format.VALUE: B[T],
8091+
Format.FORWARDREF: B[T],
8092+
Format.STRING: "B.bT",
8093+
},
8094+
Annotated[None, "none"]: Annotated[None, "none"],
8095+
annotation: annotation,
8096+
# Optional["annotation"]: Optional[annotation],
8097+
Optional[int]: Optional[int],
8098+
Optional[List[str]]: Optional[List[str]],
8099+
"Union[str, None, str]": {
8100+
Format.VALUE: Optional[str],
8101+
Format.FORWARDREF: Optional[str],
8102+
Format.STRING: "Union[str, None, str]",
8103+
},
8104+
Union[str, None, "str"]: {
8105+
Format.VALUE: Optional[str],
8106+
Format.FORWARDREF: Optional[str],
8107+
Format.STRING: Union[str, None, "str"],
8108+
},
8109+
Unpack[Tuple[int, None]]: Unpack[Tuple[int, None]],
8110+
Unpack[Ts]: Unpack[Ts],
8111+
}
8112+
8113+
for annot, expected in cases.items():
8114+
#ref = typing._type_convert(annot)
8115+
ref = typing.ForwardRef(
8116+
str(annot)
8117+
if not isinstance(annot, type)
8118+
else annot.__name__
8119+
)
8120+
for format in Format.VALUE, Format.FORWARDREF, Format.STRING:
8121+
with self.subTest(format=format, ref=ref):
8122+
if isinstance(expected, dict):
8123+
check_expected = expected[format]
8124+
else:
8125+
check_expected = expected
8126+
if check_expected is NameError:
8127+
with self.assertRaises(NameError):
8128+
evaluate_forward_ref(
8129+
ref, locals=locals(), globals=globals(), format=format
8130+
)
8131+
del check_expected
8132+
continue
8133+
if format == Format.STRING:
8134+
check_expected = (
8135+
str(check_expected)
8136+
if not isinstance(check_expected, type)
8137+
else check_expected.__name__
8138+
)
8139+
result = evaluate_forward_ref(
8140+
ref, locals=locals(), globals=globals(), format=format
8141+
)
8142+
self.assertEqual(result, check_expected)
8143+
del check_expected
8144+
80238145

80248146
if __name__ == '__main__':
80258147
main()

src/typing_extensions.py

Lines changed: 68 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
'dataclass_transform',
6464
'deprecated',
6565
'Doc',
66+
'evaluate_forward_ref',
6667
'get_overloads',
6768
'final',
6869
'Format',
@@ -3799,6 +3800,7 @@ class Format(enum.IntEnum):
37993800
VALUE = 1
38003801
FORWARDREF = 2
38013802
SOURCE = 3
3803+
STRING = 3
38023804

38033805

38043806
if _PEP_649_OR_749_IMPLEMENTED:
@@ -3928,7 +3930,7 @@ def get_annotations(obj, *, globals=None, locals=None, eval_str=False,
39283930
value if not isinstance(value, str) else eval(value, globals, locals)
39293931
for key, value in ann.items() }
39303932
return return_value
3931-
3933+
39323934

39333935

39343936
if hasattr(typing, "evaluate_forward_ref"):
@@ -3950,14 +3952,14 @@ def _deprecation_warning_for_no_type_params_passed(funcname: str) -> None:
39503952
f"It will be disallowed in Python 3.15."
39513953
)
39523954
warnings.warn(depr_message, category=DeprecationWarning, stacklevel=3)
3953-
3955+
39543956
def evaluate_forward_ref(
39553957
forward_ref,
39563958
*,
39573959
owner=None,
39583960
globals=None,
39593961
locals=None,
3960-
type_params=_marker,
3962+
type_params=None,
39613963
format=Format.VALUE,
39623964
_recursive_guard=frozenset(),
39633965
):
@@ -3983,7 +3985,7 @@ def evaluate_forward_ref(
39833985
annotation and is a member of the annotationlib.Format enum.
39843986
39853987
"""
3986-
if type_params is _sentinel:
3988+
if hasattr(typing, "_sentinel") and type_params is typing._sentinel:
39873989
_deprecation_warning_for_no_type_params_passed("typing.evaluate_forward_ref")
39883990
type_params = ()
39893991
if format == Format.STRING:
@@ -3992,30 +3994,75 @@ def evaluate_forward_ref(
39923994
return forward_ref
39933995

39943996
try:
3995-
value = forward_ref.evaluate(
3996-
globals=globals, locals=locals, type_params=type_params, owner=owner
3997-
)
3997+
if hasattr(forward_ref, "evaluate"):
3998+
value = forward_ref.evaluate(
3999+
globals=globals, locals=locals, type_params=type_params, owner=owner
4000+
)
4001+
elif sys.version_info >= (3, 12, 4):
4002+
value = forward_ref._evaluate(
4003+
globalns=globals,
4004+
localns=locals,
4005+
type_params=type_params,
4006+
recursive_guard=frozenset(),
4007+
)
4008+
elif sys.version_info >= (3, 9):
4009+
value = forward_ref._evaluate(
4010+
globalns=globals, localns=locals, recursive_guard=frozenset()
4011+
)
4012+
else:
4013+
value = forward_ref._evaluate(
4014+
globalns=globals, localns=locals
4015+
)
39984016
except NameError:
39994017
if format == Format.FORWARDREF:
40004018
return forward_ref
40014019
else:
40024020
raise
40034021

4004-
type_ = typing._type_check(
4005-
value,
4006-
"Forward references must evaluate to types.",
4007-
is_argument=forward_ref.__forward_is_argument__,
4008-
allow_special_forms=forward_ref.__forward_is_class__,
4009-
)
4022+
if sys.version_info < (3, 10, 1):
4023+
type_ = typing._type_check(
4024+
value,
4025+
"Forward references must evaluate to types.",
4026+
is_argument=forward_ref.__forward_is_argument__,
4027+
#allow_special_forms=forward_ref.__forward_is_class__,
4028+
)
4029+
else:
4030+
type_ = typing._type_check(
4031+
value,
4032+
"Forward references must evaluate to types.",
4033+
is_argument=forward_ref.__forward_is_argument__,
4034+
allow_special_forms=forward_ref.__forward_is_class__,
4035+
)
4036+
if sys.version_info < (3, 9):
4037+
return typing._eval_type(
4038+
type_,
4039+
globals,
4040+
locals,
4041+
)
4042+
if sys.version_info < (3, 12, 5):
4043+
return typing._eval_type(
4044+
type_,
4045+
globals,
4046+
locals,
4047+
recursive_guard=_recursive_guard | {forward_ref.__forward_arg__},
4048+
)
4049+
if sys.version_info < (3, 14):
4050+
return typing._eval_type(
4051+
type_,
4052+
globals,
4053+
locals,
4054+
type_params,
4055+
recursive_guard=_recursive_guard | {forward_ref.__forward_arg__},
4056+
)
40104057
return typing._eval_type(
4011-
type_,
4012-
globals,
4013-
locals,
4014-
type_params,
4015-
recursive_guard=_recursive_guard | {forward_ref.__forward_arg__},
4016-
format=format,
4017-
owner=owner,
4018-
)
4058+
type_,
4059+
globals,
4060+
locals,
4061+
type_params,
4062+
recursive_guard=_recursive_guard | {forward_ref.__forward_arg__},
4063+
format=format,
4064+
owner=owner,
4065+
)
40194066

40204067

40214068
# Aliases for items that have always been in typing.

0 commit comments

Comments
 (0)