Skip to content

Commit 7875a14

Browse files
authored
Merge branch 'main' into shutil_unpack_archive_false_positives
2 parents c8a6b24 + 70afb8d commit 7875a14

32 files changed

+583
-201
lines changed

Doc/library/symtable.rst

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,3 +207,21 @@ Examining Symbol Tables
207207

208208
Return the namespace bound to this name. If more than one or no namespace
209209
is bound to this name, a :exc:`ValueError` is raised.
210+
211+
212+
.. _symtable-cli:
213+
214+
Command-Line Usage
215+
------------------
216+
217+
.. versionadded:: 3.13
218+
219+
The :mod:`symtable` module can be executed as a script from the command line.
220+
221+
.. code-block:: sh
222+
223+
python -m symtable [infile...]
224+
225+
Symbol tables are generated for the specified Python source files and
226+
dumped to stdout.
227+
If no input file is specified, the content is read from stdin.

Doc/library/typing.rst

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1145,16 +1145,13 @@ These can be used as types in annotations. They all support subscription using
11451145

11461146
from collections.abc import Callable
11471147
from threading import Lock
1148-
from typing import Concatenate, ParamSpec, TypeVar
1149-
1150-
P = ParamSpec('P')
1151-
R = TypeVar('R')
1148+
from typing import Concatenate
11521149

11531150
# Use this lock to ensure that only one thread is executing a function
11541151
# at any time.
11551152
my_lock = Lock()
11561153

1157-
def with_lock(f: Callable[Concatenate[Lock, P], R]) -> Callable[P, R]:
1154+
def with_lock[**P, R](f: Callable[Concatenate[Lock, P], R]) -> Callable[P, R]:
11581155
'''A type-safe decorator which provides a lock.'''
11591156
def inner(*args: P.args, **kwargs: P.kwargs) -> R:
11601157
# Provide the lock as the first argument.

Doc/reference/datamodel.rst

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1214,10 +1214,15 @@ Frame objects support one method:
12141214
objects (for example when catching an exception and storing its
12151215
traceback for later use).
12161216

1217-
:exc:`RuntimeError` is raised if the frame is currently executing.
1217+
:exc:`RuntimeError` is raised if the frame is currently executing
1218+
or suspended.
12181219

12191220
.. versionadded:: 3.4
12201221

1222+
.. versionchanged:: 3.13
1223+
Attempting to clear a suspended frame raises :exc:`RuntimeError`
1224+
(as has always been the case for executing frames).
1225+
12211226

12221227
.. _traceback-objects:
12231228

Doc/whatsnew/3.13.rst

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,10 @@ Deprecated
397397
and methods that consider plural forms even if the translation was not found.
398398
(Contributed by Serhiy Storchaka in :gh:`88434`.)
399399

400+
* Calling :meth:`frame.clear` on a suspended frame raises :exc:`RuntimeError`
401+
(as has always been the case for an executing frame).
402+
(Contributed by Irit Katriel in :gh:`79932`.)
403+
400404

401405
Pending Removal in Python 3.14
402406
------------------------------
@@ -976,6 +980,15 @@ Changes in the Python API
976980
The result is now the same if ``wantobjects`` is set to ``0``.
977981
(Contributed by Serhiy Storchaka in :gh:`97928`.)
978982

983+
* Functions :c:func:`PyDict_GetItem`, :c:func:`PyDict_GetItemString`,
984+
:c:func:`PyMapping_HasKey`, :c:func:`PyMapping_HasKeyString`,
985+
:c:func:`PyObject_HasAttr`, :c:func:`PyObject_HasAttrString`, and
986+
:c:func:`PySys_GetObject`, which clear all errors occurred during calling
987+
the function, report now them using :func:`sys.unraisablehook`.
988+
You can consider to replace these functions with other functions as
989+
recomended in the documentation.
990+
(Contributed by Serhiy Storchaka in :gh:`106672`.)
991+
979992

980993
Build Changes
981994
=============

Include/internal/pycore_opcode_metadata.h

Lines changed: 10 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Lib/symtable.py

Lines changed: 49 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,16 @@ def __init__(self, name, flags, namespaces=None, *, module_scope=False):
233233
self.__module_scope = module_scope
234234

235235
def __repr__(self):
236-
return "<symbol {0!r}>".format(self.__name)
236+
flags_str = '|'.join(self._flags_str())
237+
return f'<symbol {self.__name!r}: {self._scope_str()}, {flags_str}>'
238+
239+
def _scope_str(self):
240+
return _scopes_value_to_name.get(self.__scope) or str(self.__scope)
241+
242+
def _flags_str(self):
243+
for flagname, flagvalue in _flags:
244+
if self.__flags & flagvalue == flagvalue:
245+
yield flagname
237246

238247
def get_name(self):
239248
"""Return a name of a symbol.
@@ -323,11 +332,43 @@ def get_namespace(self):
323332
else:
324333
return self.__namespaces[0]
325334

335+
336+
_flags = [('USE', USE)]
337+
_flags.extend(kv for kv in globals().items() if kv[0].startswith('DEF_'))
338+
_scopes_names = ('FREE', 'LOCAL', 'GLOBAL_IMPLICIT', 'GLOBAL_EXPLICIT', 'CELL')
339+
_scopes_value_to_name = {globals()[n]: n for n in _scopes_names}
340+
341+
342+
def main(args):
343+
import sys
344+
def print_symbols(table, level=0):
345+
indent = ' ' * level
346+
nested = "nested " if table.is_nested() else ""
347+
if table.get_type() == 'module':
348+
what = f'from file {table._filename!r}'
349+
else:
350+
what = f'{table.get_name()!r}'
351+
print(f'{indent}symbol table for {nested}{table.get_type()} {what}:')
352+
for ident in table.get_identifiers():
353+
symbol = table.lookup(ident)
354+
flags = ', '.join(symbol._flags_str()).lower()
355+
print(f' {indent}{symbol._scope_str().lower()} symbol {symbol.get_name()!r}: {flags}')
356+
print()
357+
358+
for table2 in table.get_children():
359+
print_symbols(table2, level + 1)
360+
361+
for filename in args or ['-']:
362+
if filename == '-':
363+
src = sys.stdin.read()
364+
filename = '<stdin>'
365+
else:
366+
with open(filename, 'rb') as f:
367+
src = f.read()
368+
mod = symtable(src, filename, 'exec')
369+
print_symbols(mod)
370+
371+
326372
if __name__ == "__main__":
327-
import os, sys
328-
with open(sys.argv[0]) as f:
329-
src = f.read()
330-
mod = symtable(src, os.path.split(sys.argv[0])[1], "exec")
331-
for ident in mod.get_identifiers():
332-
info = mod.lookup(ident)
333-
print(info, info.is_local(), info.is_namespace())
373+
import sys
374+
main(sys.argv[1:])

Lib/test/test_capi/test_abstract.py

Lines changed: 89 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import unittest
22
from collections import OrderedDict
3+
from test import support
34
from test.support import import_helper
45

56
_testcapi = import_helper.import_module('_testcapi')
@@ -109,8 +110,18 @@ def test_object_hasattr(self):
109110
self.assertFalse(xhasattr(obj, 'b'))
110111
self.assertTrue(xhasattr(obj, '\U0001f40d'))
111112

112-
self.assertFalse(xhasattr(obj, 'evil'))
113-
self.assertFalse(xhasattr(obj, 1))
113+
with support.catch_unraisable_exception() as cm:
114+
self.assertFalse(xhasattr(obj, 'evil'))
115+
self.assertEqual(cm.unraisable.exc_type, RuntimeError)
116+
self.assertEqual(str(cm.unraisable.exc_value),
117+
'do not get evil')
118+
119+
with support.catch_unraisable_exception() as cm:
120+
self.assertFalse(xhasattr(obj, 1))
121+
self.assertEqual(cm.unraisable.exc_type, TypeError)
122+
self.assertEqual(str(cm.unraisable.exc_value),
123+
"attribute name must be string, not 'int'")
124+
114125
# CRASHES xhasattr(obj, NULL)
115126
# CRASHES xhasattr(NULL, 'a')
116127

@@ -123,8 +134,18 @@ def test_object_hasattrstring(self):
123134
self.assertFalse(hasattrstring(obj, b'b'))
124135
self.assertTrue(hasattrstring(obj, '\U0001f40d'.encode()))
125136

126-
self.assertFalse(hasattrstring(obj, b'evil'))
127-
self.assertFalse(hasattrstring(obj, b'\xff'))
137+
with support.catch_unraisable_exception() as cm:
138+
self.assertFalse(hasattrstring(obj, b'evil'))
139+
self.assertEqual(cm.unraisable.exc_type, RuntimeError)
140+
self.assertEqual(str(cm.unraisable.exc_value),
141+
'do not get evil')
142+
143+
with support.catch_unraisable_exception() as cm:
144+
self.assertFalse(hasattrstring(obj, b'\xff'))
145+
self.assertEqual(cm.unraisable.exc_type, UnicodeDecodeError)
146+
self.assertRegex(str(cm.unraisable.exc_value),
147+
"'utf-8' codec can't decode")
148+
128149
# CRASHES hasattrstring(obj, NULL)
129150
# CRASHES hasattrstring(NULL, b'a')
130151

@@ -342,12 +363,41 @@ def test_mapping_haskey(self):
342363

343364
self.assertTrue(haskey(['a', 'b', 'c'], 1))
344365

345-
self.assertFalse(haskey(42, 'a'))
346-
self.assertFalse(haskey({}, [])) # unhashable
347-
self.assertFalse(haskey({}, NULL))
348-
self.assertFalse(haskey([], 1))
349-
self.assertFalse(haskey([], 'a'))
350-
self.assertFalse(haskey(NULL, 'a'))
366+
with support.catch_unraisable_exception() as cm:
367+
self.assertFalse(haskey(42, 'a'))
368+
self.assertEqual(cm.unraisable.exc_type, TypeError)
369+
self.assertEqual(str(cm.unraisable.exc_value),
370+
"'int' object is not subscriptable")
371+
372+
with support.catch_unraisable_exception() as cm:
373+
self.assertFalse(haskey({}, []))
374+
self.assertEqual(cm.unraisable.exc_type, TypeError)
375+
self.assertEqual(str(cm.unraisable.exc_value),
376+
"unhashable type: 'list'")
377+
378+
with support.catch_unraisable_exception() as cm:
379+
self.assertFalse(haskey([], 1))
380+
self.assertEqual(cm.unraisable.exc_type, IndexError)
381+
self.assertEqual(str(cm.unraisable.exc_value),
382+
'list index out of range')
383+
384+
with support.catch_unraisable_exception() as cm:
385+
self.assertFalse(haskey([], 'a'))
386+
self.assertEqual(cm.unraisable.exc_type, TypeError)
387+
self.assertEqual(str(cm.unraisable.exc_value),
388+
'list indices must be integers or slices, not str')
389+
390+
with support.catch_unraisable_exception() as cm:
391+
self.assertFalse(haskey({}, NULL))
392+
self.assertEqual(cm.unraisable.exc_type, SystemError)
393+
self.assertEqual(str(cm.unraisable.exc_value),
394+
'null argument to internal routine')
395+
396+
with support.catch_unraisable_exception() as cm:
397+
self.assertFalse(haskey(NULL, 'a'))
398+
self.assertEqual(cm.unraisable.exc_type, SystemError)
399+
self.assertEqual(str(cm.unraisable.exc_value),
400+
'null argument to internal routine')
351401

352402
def test_mapping_haskeystring(self):
353403
haskeystring = _testcapi.mapping_haskeystring
@@ -360,11 +410,35 @@ def test_mapping_haskeystring(self):
360410
self.assertTrue(haskeystring(dct2, b'a'))
361411
self.assertFalse(haskeystring(dct2, b'b'))
362412

363-
self.assertFalse(haskeystring(42, b'a'))
364-
self.assertFalse(haskeystring({}, b'\xff'))
365-
self.assertFalse(haskeystring({}, NULL))
366-
self.assertFalse(haskeystring([], b'a'))
367-
self.assertFalse(haskeystring(NULL, b'a'))
413+
with support.catch_unraisable_exception() as cm:
414+
self.assertFalse(haskeystring(42, b'a'))
415+
self.assertEqual(cm.unraisable.exc_type, TypeError)
416+
self.assertEqual(str(cm.unraisable.exc_value),
417+
"'int' object is not subscriptable")
418+
419+
with support.catch_unraisable_exception() as cm:
420+
self.assertFalse(haskeystring({}, b'\xff'))
421+
self.assertEqual(cm.unraisable.exc_type, UnicodeDecodeError)
422+
self.assertRegex(str(cm.unraisable.exc_value),
423+
"'utf-8' codec can't decode")
424+
425+
with support.catch_unraisable_exception() as cm:
426+
self.assertFalse(haskeystring({}, NULL))
427+
self.assertEqual(cm.unraisable.exc_type, SystemError)
428+
self.assertEqual(str(cm.unraisable.exc_value),
429+
"null argument to internal routine")
430+
431+
with support.catch_unraisable_exception() as cm:
432+
self.assertFalse(haskeystring([], b'a'))
433+
self.assertEqual(cm.unraisable.exc_type, TypeError)
434+
self.assertEqual(str(cm.unraisable.exc_value),
435+
'list indices must be integers or slices, not str')
436+
437+
with support.catch_unraisable_exception() as cm:
438+
self.assertFalse(haskeystring(NULL, b'a'))
439+
self.assertEqual(cm.unraisable.exc_type, SystemError)
440+
self.assertEqual(str(cm.unraisable.exc_value),
441+
"null argument to internal routine")
368442

369443
def test_mapping_haskeywitherror(self):
370444
haskey = _testcapi.mapping_haskeywitherror

0 commit comments

Comments
 (0)