means may not have any information about their scope, so passing
arguments to this method may be necessary to evaluate them successfully.
- .. important::
-
- Once a :class:`~ForwardRef` instance has been evaluated, it caches
- the evaluated value, and future calls to :meth:`evaluate` will return
- the cached value, regardless of the parameters passed in.
-
.. versionadded:: 3.14
.. versionadded:: 3.7.4
.. versionchanged:: 3.14
- This is now an alias for :class:`annotationlib.ForwardRef`.
+ This is now an alias for :class:`annotationlib.ForwardRef`. Several undocumented
+ behaviors of this class have been changed; for example, after a ``ForwardRef`` has
+ been evaluated, the evaluated value is no longer cached.
.. function:: evaluate_forward_ref(forward_ref, *, owner=None, globals=None, locals=None, type_params=None, format=annotationlib.Format.VALUE)
...
NameError: name 'Undefined' is not defined
>>> get_annotations(func, format=Format.FORWARDREF)
- {'arg': ForwardRef('Undefined')}
+ {'arg': ForwardRef('Undefined', owner=<function func at 0x...>)}
>>> get_annotations(func, format=Format.STRING)
{'arg': 'Undefined'}
# preserved for compatibility with the old typing.ForwardRef class. The remaining
# names are private.
_SLOTS = (
- "__forward_evaluated__",
- "__forward_value__",
"__forward_is_argument__",
"__forward_is_class__",
"__forward_module__",
"__weakref__",
"__arg__",
- "__ast_node__",
- "__code__",
"__globals__",
- "__owner__",
+ "__code__",
+ "__ast_node__",
"__cell__",
+ "__owner__",
"__stringifier_dict__",
)
raise TypeError(f"Forward reference must be a string -- got {arg!r}")
self.__arg__ = arg
- self.__forward_evaluated__ = False
- self.__forward_value__ = None
self.__forward_is_argument__ = is_argument
self.__forward_is_class__ = is_class
self.__forward_module__ = module
+ self.__globals__ = None
self.__code__ = None
self.__ast_node__ = None
- self.__globals__ = None
self.__cell__ = None
self.__owner__ = owner
If the forward reference cannot be evaluated, raise an exception.
"""
- if self.__forward_evaluated__:
- return self.__forward_value__
if self.__cell__ is not None:
try:
- value = self.__cell__.cell_contents
+ return self.__cell__.cell_contents
except ValueError:
pass
- else:
- self.__forward_evaluated__ = True
- self.__forward_value__ = value
- return value
if owner is None:
owner = self.__owner__
else:
code = self.__forward_code__
value = eval(code, globals=globals, locals=locals)
- self.__forward_evaluated__ = True
- self.__forward_value__ = value
return value
def _evaluate(self, globalns, localns, type_params=_sentinel, *, recursive_guard):
def __eq__(self, other):
if not isinstance(other, ForwardRef):
return NotImplemented
- if self.__forward_evaluated__ and other.__forward_evaluated__:
- return (
- self.__forward_arg__ == other.__forward_arg__
- and self.__forward_value__ == other.__forward_value__
- )
return (
self.__forward_arg__ == other.__forward_arg__
and self.__forward_module__ == other.__forward_module__
+ # Use "is" here because we use id() for this in __hash__
+ # because dictionaries are not hashable.
+ and self.__globals__ is other.__globals__
+ and self.__forward_is_class__ == other.__forward_is_class__
+ and self.__code__ == other.__code__
+ and self.__ast_node__ == other.__ast_node__
+ and self.__cell__ == other.__cell__
+ and self.__owner__ == other.__owner__
)
def __hash__(self):
- return hash((self.__forward_arg__, self.__forward_module__))
+ return hash((
+ self.__forward_arg__,
+ self.__forward_module__,
+ id(self.__globals__), # dictionaries are not hashable, so hash by identity
+ self.__forward_is_class__,
+ self.__code__,
+ self.__ast_node__,
+ self.__cell__,
+ self.__owner__,
+ ))
def __or__(self, other):
return types.UnionType[self, other]
return types.UnionType[other, self]
def __repr__(self):
- if self.__forward_module__ is None:
- module_repr = ""
- else:
- module_repr = f", module={self.__forward_module__!r}"
- return f"ForwardRef({self.__forward_arg__!r}{module_repr})"
+ extra = []
+ if self.__forward_module__ is not None:
+ extra.append(f", module={self.__forward_module__!r}")
+ if self.__forward_is_class__:
+ extra.append(", is_class=True")
+ if self.__owner__ is not None:
+ extra.append(f", owner={self.__owner__!r}")
+ return f"ForwardRef({self.__forward_arg__!r}{''.join(extra)})"
class _Stringifier:
# represent a single name).
assert isinstance(node, (ast.AST, str))
self.__arg__ = None
- self.__forward_evaluated__ = False
- self.__forward_value__ = None
self.__forward_is_argument__ = False
self.__forward_is_class__ = is_class
self.__forward_module__ = None
if __name__ != 'test.support':
raise ImportError('support must be imported from the test package')
+import annotationlib
import contextlib
import functools
import inspect
return get_fips_mode() != 0
+class EqualToForwardRef:
+ """Helper to ease use of annotationlib.ForwardRef in tests.
+
+ This checks only attributes that can be set using the constructor.
+
+ """
+
+ def __init__(
+ self,
+ arg,
+ *,
+ module=None,
+ owner=None,
+ is_class=False,
+ ):
+ self.__forward_arg__ = arg
+ self.__forward_is_class__ = is_class
+ self.__forward_module__ = module
+ self.__owner__ = owner
+
+ def __eq__(self, other):
+ if not isinstance(other, (EqualToForwardRef, annotationlib.ForwardRef)):
+ return NotImplemented
+ return (
+ self.__forward_arg__ == other.__forward_arg__
+ and self.__forward_module__ == other.__forward_module__
+ and self.__forward_is_class__ == other.__forward_is_class__
+ and self.__owner__ == other.__owner__
+ )
+
+ def __repr__(self):
+ extra = []
+ if self.__forward_module__ is not None:
+ extra.append(f", module={self.__forward_module__!r}")
+ if self.__forward_is_class__:
+ extra.append(", is_class=True")
+ if self.__owner__ is not None:
+ extra.append(f", owner={self.__owner__!r}")
+ return f"EqualToForwardRef({self.__forward_arg__!r}{''.join(extra)})"
+
+
_linked_to_musl = None
def linked_to_musl():
"""
anno = annotationlib.get_annotations(f, format=Format.FORWARDREF)
x_anno = anno["x"]
self.assertIsInstance(x_anno, ForwardRef)
- self.assertEqual(x_anno, ForwardRef("some.module"))
+ self.assertEqual(x_anno, support.EqualToForwardRef("some.module", owner=f))
y_anno = anno["y"]
self.assertIsInstance(y_anno, ForwardRef)
- self.assertEqual(y_anno, ForwardRef("some[module]"))
+ self.assertEqual(y_anno, support.EqualToForwardRef("some[module]", owner=f))
z_anno = anno["z"]
self.assertIsInstance(z_anno, ForwardRef)
- self.assertEqual(z_anno, ForwardRef("some(module)"))
+ self.assertEqual(z_anno, support.EqualToForwardRef("some(module)", owner=f))
alpha_anno = anno["alpha"]
self.assertIsInstance(alpha_anno, ForwardRef)
- self.assertEqual(alpha_anno, ForwardRef("some | obj"))
+ self.assertEqual(alpha_anno, support.EqualToForwardRef("some | obj", owner=f))
beta_anno = anno["beta"]
self.assertIsInstance(beta_anno, ForwardRef)
- self.assertEqual(beta_anno, ForwardRef("+some"))
+ self.assertEqual(beta_anno, support.EqualToForwardRef("+some", owner=f))
gamma_anno = anno["gamma"]
self.assertIsInstance(gamma_anno, ForwardRef)
- self.assertEqual(gamma_anno, ForwardRef("some < obj"))
+ self.assertEqual(gamma_anno, support.EqualToForwardRef("some < obj", owner=f))
class TestSourceFormat(unittest.TestCase):
obj = object()
self.assertIs(ForwardRef("int").evaluate(globals={"int": obj}), obj)
- def test_fwdref_value_is_cached(self):
+ def test_fwdref_value_is_not_cached(self):
fr = ForwardRef("hello")
with self.assertRaises(NameError):
fr.evaluate()
self.assertIs(fr.evaluate(globals={"hello": str}), str)
- self.assertIs(fr.evaluate(), str)
+ with self.assertRaises(NameError):
+ fr.evaluate()
def test_fwdref_with_owner(self):
self.assertEqual(
)
self.assertEqual(annotationlib.get_annotations(f1, format=1), {"a": int})
- fwd = annotationlib.ForwardRef("undefined")
+ fwd = support.EqualToForwardRef("undefined", owner=f2)
self.assertEqual(
annotationlib.get_annotations(f2, format=Format.FORWARDREF),
{"a": fwd},
annotationlib.call_evaluate_function(evaluate, Format.VALUE)
self.assertEqual(
annotationlib.call_evaluate_function(evaluate, Format.FORWARDREF),
- annotationlib.ForwardRef("undefined"),
+ support.EqualToForwardRef("undefined"),
)
self.assertEqual(
annotationlib.call_evaluate_function(evaluate, Format.STRING),
from test.support import cpython_only, import_helper
from test.support import MISSING_C_DOCSTRINGS, ALWAYS_EQ
-from test.support import run_no_yield_async_fn
+from test.support import run_no_yield_async_fn, EqualToForwardRef
from test.support.import_helper import DirsOnSysPath, ready_to_import
from test.support.os_helper import TESTFN, temp_cwd
from test.support.script_helper import assert_python_ok, assert_python_failure, kill_python
signature_func(ida.f, annotation_format=Format.STRING),
sig([par("x", PORK, annotation="undefined")])
)
+ s1 = signature_func(ida.f, annotation_format=Format.FORWARDREF)
+ s2 = sig([par("x", PORK, annotation=EqualToForwardRef("undefined", owner=ida.f))])
+ #breakpoint()
self.assertEqual(
signature_func(ida.f, annotation_format=Format.FORWARDREF),
- sig([par("x", PORK, annotation=ForwardRef("undefined"))])
+ sig([par("x", PORK, annotation=EqualToForwardRef("undefined", owner=ida.f))])
)
with self.assertRaisesRegex(NameError, "undefined"):
signature_func(ida.f, annotation_format=Format.VALUE)
from test.support import (
run_with_locale, cpython_only, no_rerun,
- MISSING_C_DOCSTRINGS,
+ MISSING_C_DOCSTRINGS, EqualToForwardRef,
)
import collections.abc
from collections import namedtuple, UserDict
self.assertIs(int, types.UnionType[int])
self.assertIs(int, types.UnionType[int, int])
self.assertEqual(int | str, types.UnionType[int, str])
- self.assertEqual(int | typing.ForwardRef("str"), types.UnionType[int, "str"])
+
+ for obj in (
+ int | typing.ForwardRef("str"),
+ typing.Union[int, "str"],
+ ):
+ self.assertIsInstance(obj, types.UnionType)
+ self.assertEqual(obj.__args__, (int, EqualToForwardRef("str")))
class MappingProxyTests(unittest.TestCase):
import warnings
import types
-from test.support import captured_stderr, cpython_only, infinite_recursion, requires_docstrings, import_helper, run_code
+from test.support import (
+ captured_stderr, cpython_only, infinite_recursion, requires_docstrings, import_helper, run_code,
+ EqualToForwardRef,
+)
from test.typinganndata import ann_module695, mod_generics_cache, _typed_dict_helper
self.assertEqual(X | "x", Union[X, "x"])
self.assertEqual("x" | X, Union["x", X])
# make sure the order is correct
- self.assertEqual(get_args(X | "x"), (X, ForwardRef("x")))
- self.assertEqual(get_args("x" | X), (ForwardRef("x"), X))
+ self.assertEqual(get_args(X | "x"), (X, EqualToForwardRef("x")))
+ self.assertEqual(get_args("x" | X), (EqualToForwardRef("x"), X))
def test_union_constrained(self):
A = TypeVar('A', str, bytes)
def f(x: X): ...
self.assertEqual(
get_type_hints(f, globals(), locals()),
- {'x': list[list[ForwardRef('X')]]}
+ {'x': list[list[EqualToForwardRef('X')]]}
)
def test_pep695_generic_class_with_future_annotations(self):
return a
self.assertEqual(namespace1(), namespace1())
- self.assertNotEqual(namespace1(), namespace2())
+ self.assertEqual(namespace1(), namespace2())
def test_forward_repr(self):
self.assertEqual(repr(List['int']), "typing.List[ForwardRef('int')]")
ret = get_type_hints(fun, globals(), locals())
return a
- def cmp(o1, o2):
- return o1 == o2
-
- with infinite_recursion(25):
- r1 = namespace1()
- r2 = namespace2()
- self.assertIsNot(r1, r2)
- self.assertRaises(RecursionError, cmp, r1, r2)
+ r1 = namespace1()
+ r2 = namespace2()
+ self.assertIsNot(r1, r2)
+ self.assertEqual(r1, r2)
def test_union_forward_recursion(self):
ValueList = List['Value']
# FORWARDREF
self.assertEqual(
get_type_hints(func, format=annotationlib.Format.FORWARDREF),
- {'x': ForwardRef('undefined'), 'return': ForwardRef('undefined')},
+ {'x': EqualToForwardRef('undefined', owner=func),
+ 'return': EqualToForwardRef('undefined', owner=func)},
)
# STRING
class Z(NamedTuple):
a: None
b: "str"
- annos = {'a': type(None), 'b': ForwardRef("str")}
+ annos = {'a': type(None), 'b': EqualToForwardRef("str")}
self.assertEqual(Z.__annotations__, annos)
self.assertEqual(Z.__annotate__(annotationlib.Format.VALUE), annos)
self.assertEqual(Z.__annotate__(annotationlib.Format.FORWARDREF), annos)
"""
ns = run_code(textwrap.dedent(code))
X = ns['X']
- self.assertEqual(X.__annotations__, {'a': ForwardRef("int"), 'b': ForwardRef("None")})
+ self.assertEqual(X.__annotations__, {'a': EqualToForwardRef("int"), 'b': EqualToForwardRef("None")})
def test_deferred_annotations(self):
class X(NamedTuple):
class Y(TypedDict):
a: None
b: "int"
- fwdref = ForwardRef('int', module=__name__)
+ fwdref = EqualToForwardRef('int', module=__name__)
self.assertEqual(Y.__annotations__, {'a': type(None), 'b': fwdref})
self.assertEqual(Y.__annotate__(annotationlib.Format.FORWARDREF), {'a': type(None), 'b': fwdref})
--- /dev/null
+:class:`annotationlib.ForwardRef` objects no longer cache their value when
+they are successfully evaluated. Successive calls to
+:meth:`annotationlib.ForwardRef.evaluate` may return different values.
--- /dev/null
+The implementations of equality and hashing for :class:`annotationlib.ForwardRef`
+now use all attributes on the object. Two :class:`!ForwardRef` objects
+are equal only if all attributes are equal.