Skip to content

Commit 073f989

Browse files
authored
Merge branch 'main' into bugfix/type-race
2 parents 9836fd7 + 63e01d6 commit 073f989

File tree

8 files changed

+58
-10
lines changed

8 files changed

+58
-10
lines changed

Doc/library/argparse.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -638,6 +638,11 @@ by setting ``color`` to ``False``::
638638
... help='an integer for the accumulator')
639639
>>> parser.parse_args(['--help'])
640640

641+
Note that when ``color=True``, colored output depends on both environment
642+
variables and terminal capabilities. However, if ``color=False``, colored
643+
output is always disabled, even if environment variables like ``FORCE_COLOR``
644+
are set.
645+
641646
.. versionadded:: 3.14
642647

643648

Lib/annotationlib.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -159,19 +159,21 @@ def evaluate(
159159
type_params = getattr(owner, "__type_params__", None)
160160

161161
# Type parameters exist in their own scope, which is logically
162-
# between the locals and the globals. We simulate this by adding
163-
# them to the globals.
162+
# between the locals and the globals.
163+
type_param_scope = {}
164164
if type_params is not None:
165-
globals = dict(globals)
166165
for param in type_params:
167-
globals[param.__name__] = param
166+
type_param_scope[param.__name__] = param
167+
168168
if self.__extra_names__:
169169
locals = {**locals, **self.__extra_names__}
170170

171171
arg = self.__forward_arg__
172172
if arg.isidentifier() and not keyword.iskeyword(arg):
173173
if arg in locals:
174174
return locals[arg]
175+
elif arg in type_param_scope:
176+
return type_param_scope[arg]
175177
elif arg in globals:
176178
return globals[arg]
177179
elif hasattr(builtins, arg):
@@ -183,12 +185,15 @@ def evaluate(
183185
else:
184186
code = self.__forward_code__
185187
try:
186-
return eval(code, globals=globals, locals=locals)
188+
return eval(code, globals=globals, locals={**type_param_scope, **locals})
187189
except Exception:
188190
if not is_forwardref_format:
189191
raise
192+
193+
# All variables, in scoping order, should be checked before
194+
# triggering __missing__ to create a _Stringifier.
190195
new_locals = _StringifierDict(
191-
{**builtins.__dict__, **locals},
196+
{**builtins.__dict__, **globals, **type_param_scope, **locals},
192197
globals=globals,
193198
owner=owner,
194199
is_class=self.__forward_is_class__,

Lib/mailbox.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2090,8 +2090,6 @@ def closed(self):
20902090
return False
20912091
return self._file.closed
20922092

2093-
__class_getitem__ = classmethod(GenericAlias)
2094-
20952093

20962094
class _PartialFile(_ProxyFile):
20972095
"""A read-only wrapper of part of a file."""

Lib/test/test_annotationlib.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1877,6 +1877,32 @@ def test_name_lookup_without_eval(self):
18771877

18781878
self.assertEqual(exc.exception.name, "doesntexist")
18791879

1880+
def test_evaluate_undefined_generic(self):
1881+
# Test the codepath where have to eval() with undefined variables.
1882+
class C:
1883+
x: alias[int, undef]
1884+
1885+
generic = get_annotations(C, format=Format.FORWARDREF)["x"].evaluate(
1886+
format=Format.FORWARDREF,
1887+
globals={"alias": dict}
1888+
)
1889+
self.assertNotIsInstance(generic, ForwardRef)
1890+
self.assertIs(generic.__origin__, dict)
1891+
self.assertEqual(len(generic.__args__), 2)
1892+
self.assertIs(generic.__args__[0], int)
1893+
self.assertIsInstance(generic.__args__[1], ForwardRef)
1894+
1895+
generic = get_annotations(C, format=Format.FORWARDREF)["x"].evaluate(
1896+
format=Format.FORWARDREF,
1897+
globals={"alias": Union},
1898+
locals={"alias": dict}
1899+
)
1900+
self.assertNotIsInstance(generic, ForwardRef)
1901+
self.assertIs(generic.__origin__, dict)
1902+
self.assertEqual(len(generic.__args__), 2)
1903+
self.assertIs(generic.__args__[0], int)
1904+
self.assertIsInstance(generic.__args__[1], ForwardRef)
1905+
18801906
def test_fwdref_invalid_syntax(self):
18811907
fr = ForwardRef("if")
18821908
with self.assertRaises(SyntaxError):
@@ -1885,6 +1911,15 @@ def test_fwdref_invalid_syntax(self):
18851911
with self.assertRaises(SyntaxError):
18861912
fr.evaluate()
18871913

1914+
def test_re_evaluate_generics(self):
1915+
global alias
1916+
class C:
1917+
x: alias[int]
1918+
1919+
evaluated = get_annotations(C, format=Format.FORWARDREF)["x"].evaluate(format=Format.FORWARDREF)
1920+
alias = list
1921+
self.assertEqual(evaluated.evaluate(), list[int])
1922+
18881923

18891924
class TestAnnotationLib(unittest.TestCase):
18901925
def test__all__(self):

Lib/test/test_genericalias.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
from functools import partial, partialmethod, cached_property
1818
from graphlib import TopologicalSorter
1919
from logging import LoggerAdapter, StreamHandler
20-
from mailbox import Mailbox, _PartialFile
20+
from mailbox import Mailbox
2121
try:
2222
import ctypes
2323
except ImportError:
@@ -117,7 +117,7 @@ class BaseTest(unittest.TestCase):
117117
Iterable, Iterator,
118118
Reversible,
119119
Container, Collection,
120-
Mailbox, _PartialFile,
120+
Mailbox,
121121
ContextVar, Token,
122122
Field,
123123
Set, MutableSet,
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix :meth:`annotationlib.ForwardRef.evaluate` returning :class:`annotationlib.ForwardRef`
2+
objects which do not update in new contexts.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix partial evaluation of :class:`annotationlib.ForwardRef` objects which rely
2+
on names defined as globals.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
The internal class ``mailbox._ProxyFile`` is no longer a parameterized generic.

0 commit comments

Comments
 (0)