diff --git a/Doc/library/logging.rst b/Doc/library/logging.rst index 425025931d9835a..0cf5b1c0d9bc3ee 100644 --- a/Doc/library/logging.rst +++ b/Doc/library/logging.rst @@ -1082,12 +1082,13 @@ LoggerAdapter Objects information into logging calls. For a usage example, see the section on :ref:`adding contextual information to your logging output `. -.. class:: LoggerAdapter(logger, extra, merge_extra=False) +.. class:: LoggerAdapter(logger, extra=None, merge_extra=False) Returns an instance of :class:`LoggerAdapter` initialized with an - underlying :class:`Logger` instance, a dict-like object (*extra*), and a - boolean (*merge_extra*) indicating whether or not the *extra* argument of - individual log calls should be merged with the :class:`LoggerAdapter` extra. + underlying :class:`Logger` instance, an optional dict-like object (*extra*), + and an optional boolean (*merge_extra*) indicating whether or not + the *extra* argument of individual log calls should be merged with + the :class:`LoggerAdapter` extra. The default behavior is to ignore the *extra* argument of individual log calls and only use the one of the :class:`LoggerAdapter` instance @@ -1127,9 +1128,13 @@ information into logging calls. For a usage example, see the section on Attribute :attr:`!manager` and method :meth:`!_log` were added, which delegate to the underlying logger and allow adapters to be nested. + .. versionchanged:: 3.10 + + The *extra* argument is now optional. + .. versionchanged:: 3.13 - The *merge_extra* argument was added. + The *merge_extra* parameter was added. Thread Safety diff --git a/Doc/library/unicodedata.rst b/Doc/library/unicodedata.rst index 0369cd99c47c181..fd5f56bd7eaaeb1 100644 --- a/Doc/library/unicodedata.rst +++ b/Doc/library/unicodedata.rst @@ -31,7 +31,7 @@ following functions: this module. -.. function:: lookup(name) +.. function:: lookup(name, /) Look up character by name. If a character with the given name is found, return the corresponding character. If not found, :exc:`KeyError` is raised. @@ -94,7 +94,7 @@ following functions: 0.5 -.. function:: category(chr) +.. function:: category(chr, /) Returns the general category assigned to the character *chr* as string. General category names consist of two letters. @@ -106,7 +106,7 @@ following functions: 'Lu' -.. function:: bidirectional(chr) +.. function:: bidirectional(chr, /) Returns the bidirectional class assigned to the character *chr* as string. If no such value is defined, an empty string is returned. @@ -118,7 +118,7 @@ following functions: 'AN' -.. function:: combining(chr) +.. function:: combining(chr, /) Returns the canonical combining class assigned to the character *chr* as integer. Returns ``0`` if no combining class is defined. @@ -127,14 +127,14 @@ following functions: for more information. -.. function:: east_asian_width(chr) +.. function:: east_asian_width(chr, /) Returns the east asian width assigned to the character *chr* as string. For a list of widths and or more information, see the `Unicode Standard Annex #11 `_. -.. function:: mirrored(chr) +.. function:: mirrored(chr, /) Returns the mirrored property assigned to the character *chr* as integer. Returns ``1`` if the character has been identified as a "mirrored" @@ -144,7 +144,37 @@ following functions: 1 -.. function:: decomposition(chr) +.. function:: isxidstart(chr, /) + + Return ``True`` if *chr* is a valid identifier start per the + `Unicode Standard Annex #31 `_, + that is, it has the ``XID_Start`` property. Return ``False`` otherwise. + For example:: + + >>> unicodedata.isxidstart('S') + True + >>> unicodedata.isxidstart('0') + False + + .. versionadded:: next + + +.. function:: isxidcontinue(chr, /) + + Return ``True`` if *chr* is a valid identifier character per the + `Unicode Standard Annex #31 `_, + that is, it has the ``XID_Continue`` property. Return ``False`` otherwise. + For example:: + + >>> unicodedata.isxidcontinue('S') + True + >>> unicodedata.isxidcontinue(' ') + False + + .. versionadded:: next + + +.. function:: decomposition(chr, /) Returns the character decomposition mapping assigned to the character *chr* as string. An empty string is returned in case no such mapping is @@ -154,7 +184,7 @@ following functions: '0041 0303' -.. function:: normalize(form, unistr) +.. function:: normalize(form, unistr, /) Return the normal form *form* for the Unicode string *unistr*. Valid values for *form* are 'NFC', 'NFKC', 'NFD', and 'NFKD'. @@ -187,7 +217,7 @@ following functions: doesn't, they may not compare equal. -.. function:: is_normalized(form, unistr) +.. function:: is_normalized(form, unistr, /) Return whether the Unicode string *unistr* is in the normal form *form*. Valid values for *form* are 'NFC', 'NFKC', 'NFD', and 'NFKD'. diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst index 85b4c12544a0c9c..fe9adfe9f730ecc 100644 --- a/Doc/whatsnew/3.15.rst +++ b/Doc/whatsnew/3.15.rst @@ -794,6 +794,11 @@ unicodedata * The Unicode database has been updated to Unicode 17.0.0. +* Add :func:`unicodedata.isxidstart` and :func:`unicodedata.isxidcontinue` + functions to check whether a character can start or continue a + `Unicode Standard Annex #31 `_ identifier. + (Contributed by Stan Ulbrych in :gh:`129117`.) + wave ---- diff --git a/Include/internal/pycore_compile.h b/Include/internal/pycore_compile.h index c18e04bf67a5df4..1c60834fa2058c8 100644 --- a/Include/internal/pycore_compile.h +++ b/Include/internal/pycore_compile.h @@ -49,7 +49,8 @@ extern int _PyAST_Preprocess( PyObject *filename, int optimize, int ff_features, - int syntax_check_only); + int syntax_check_only, + int enable_warnings); typedef struct { diff --git a/Include/internal/pycore_unicodectype.h b/Include/internal/pycore_unicodectype.h new file mode 100644 index 000000000000000..523bdb56b09cde4 --- /dev/null +++ b/Include/internal/pycore_unicodectype.h @@ -0,0 +1,25 @@ +#ifndef Py_INTERNAL_UNICODECTYPE_H +#define Py_INTERNAL_UNICODECTYPE_H +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef Py_BUILD_CORE +# error "this header requires Py_BUILD_CORE define" +#endif + +extern int _PyUnicode_ToLowerFull(Py_UCS4 ch, Py_UCS4 *res); +extern int _PyUnicode_ToTitleFull(Py_UCS4 ch, Py_UCS4 *res); +extern int _PyUnicode_ToUpperFull(Py_UCS4 ch, Py_UCS4 *res); +extern int _PyUnicode_ToFoldedFull(Py_UCS4 ch, Py_UCS4 *res); +extern int _PyUnicode_IsCaseIgnorable(Py_UCS4 ch); +extern int _PyUnicode_IsCased(Py_UCS4 ch); + +// Export for 'unicodedata' shared extension. +PyAPI_FUNC(int) _PyUnicode_IsXidStart(Py_UCS4 ch); +PyAPI_FUNC(int) _PyUnicode_IsXidContinue(Py_UCS4 ch); + +#ifdef __cplusplus +} +#endif +#endif /* !Py_INTERNAL_UNICODECTYPE_H */ diff --git a/Include/internal/pycore_unicodeobject.h b/Include/internal/pycore_unicodeobject.h index b83039c1869f234..f384fad8713adcc 100644 --- a/Include/internal/pycore_unicodeobject.h +++ b/Include/internal/pycore_unicodeobject.h @@ -74,18 +74,6 @@ _PyUnicodeWriter_WriteCharInline(_PyUnicodeWriter *writer, Py_UCS4 ch) return 0; } - -/* --- Characters Type APIs ----------------------------------------------- */ - -extern int _PyUnicode_IsXidStart(Py_UCS4 ch); -extern int _PyUnicode_IsXidContinue(Py_UCS4 ch); -extern int _PyUnicode_ToLowerFull(Py_UCS4 ch, Py_UCS4 *res); -extern int _PyUnicode_ToTitleFull(Py_UCS4 ch, Py_UCS4 *res); -extern int _PyUnicode_ToUpperFull(Py_UCS4 ch, Py_UCS4 *res); -extern int _PyUnicode_ToFoldedFull(Py_UCS4 ch, Py_UCS4 *res); -extern int _PyUnicode_IsCaseIgnorable(Py_UCS4 ch); -extern int _PyUnicode_IsCased(Py_UCS4 ch); - /* --- Unicode API -------------------------------------------------------- */ // Export for '_json' shared extension diff --git a/Lib/logging/__init__.py b/Lib/logging/__init__.py index 431ff41b352048b..39689a57e6ecd60 100644 --- a/Lib/logging/__init__.py +++ b/Lib/logging/__init__.py @@ -1849,9 +1849,9 @@ class LoggerAdapter(object): def __init__(self, logger, extra=None, merge_extra=False): """ - Initialize the adapter with a logger and a dict-like object which - provides contextual information. This constructor signature allows - easy stacking of LoggerAdapters, if so desired. + Initialize the adapter with a logger and an optional dict-like object + which provides contextual information. This constructor signature + allows easy stacking of LoggerAdapters, if so desired. You can effectively pass keyword arguments as shown in the following example: @@ -1882,8 +1882,9 @@ def process(self, msg, kwargs): Normally, you'll only need to override this one method in a LoggerAdapter subclass for your specific needs. """ - if self.merge_extra and "extra" in kwargs: - kwargs["extra"] = {**self.extra, **kwargs["extra"]} + if self.merge_extra and kwargs.get("extra") is not None: + if self.extra is not None: + kwargs["extra"] = {**self.extra, **kwargs["extra"]} else: kwargs["extra"] = self.extra return msg, kwargs diff --git a/Lib/test/support/socket_helper.py b/Lib/test/support/socket_helper.py index 87941ee1791b4e7..a41e487f3e4bc52 100644 --- a/Lib/test/support/socket_helper.py +++ b/Lib/test/support/socket_helper.py @@ -259,6 +259,10 @@ def filter_error(err): # raise OSError('socket error', msg) from msg elif len(a) >= 2 and isinstance(a[1], OSError): err = a[1] + # The error can also be wrapped as __cause__: + # raise URLError(f"ftp error: {exp}") from exp + elif isinstance(err, urllib.error.URLError) and err.__cause__: + err = err.__cause__ else: break filter_error(err) diff --git a/Lib/test/test_ast/test_ast.py b/Lib/test/test_ast/test_ast.py index 5fdb3a458ae9996..a979a4b1da1ad18 100644 --- a/Lib/test/test_ast/test_ast.py +++ b/Lib/test/test_ast/test_ast.py @@ -1057,61 +1057,6 @@ def test_repr_large_input_crash(self): r"Exceeds the limit \(\d+ digits\)"): repr(ast.Constant(value=eval(source))) - def test_pep_765_warnings(self): - srcs = [ - textwrap.dedent(""" - def f(): - try: - pass - finally: - return 42 - """), - textwrap.dedent(""" - for x in y: - try: - pass - finally: - break - """), - textwrap.dedent(""" - for x in y: - try: - pass - finally: - continue - """), - ] - for src in srcs: - with self.assertWarnsRegex(SyntaxWarning, 'finally'): - ast.parse(src) - - def test_pep_765_no_warnings(self): - srcs = [ - textwrap.dedent(""" - try: - pass - finally: - def f(): - return 42 - """), - textwrap.dedent(""" - try: - pass - finally: - for x in y: - break - """), - textwrap.dedent(""" - try: - pass - finally: - for x in y: - continue - """), - ] - for src in srcs: - ast.parse(src) - def test_tstring(self): # Test AST structure for simple t-string tree = ast.parse('t"Hello"') diff --git a/Lib/test/test_compile.py b/Lib/test/test_compile.py index bc8ef93cb8f9de9..846d38ae561fc54 100644 --- a/Lib/test/test_compile.py +++ b/Lib/test/test_compile.py @@ -1745,6 +1745,66 @@ def test_compile_warning_in_finally(self): self.assertEqual(wm.category, SyntaxWarning) self.assertIn("\"is\" with 'int' literal", str(wm.message)) + @support.subTests('src', [ + textwrap.dedent(""" + def f(): + try: + pass + finally: + return 42 + """), + textwrap.dedent(""" + for x in y: + try: + pass + finally: + break + """), + textwrap.dedent(""" + for x in y: + try: + pass + finally: + continue + """), + ]) + def test_pep_765_warnings(self, src): + with self.assertWarnsRegex(SyntaxWarning, 'finally'): + compile(src, '', 'exec') + with warnings.catch_warnings(): + warnings.simplefilter("error") + tree = ast.parse(src) + with self.assertWarnsRegex(SyntaxWarning, 'finally'): + compile(tree, '', 'exec') + + @support.subTests('src', [ + textwrap.dedent(""" + try: + pass + finally: + def f(): + return 42 + """), + textwrap.dedent(""" + try: + pass + finally: + for x in y: + break + """), + textwrap.dedent(""" + try: + pass + finally: + for x in y: + continue + """), + ]) + def test_pep_765_no_warnings(self, src): + with warnings.catch_warnings(): + warnings.simplefilter("error") + compile(src, '', 'exec') + class TestBooleanExpression(unittest.TestCase): class Value: diff --git a/Lib/test/test_json/test_scanstring.py b/Lib/test/test_json/test_scanstring.py index cca556a3b95bab7..9a6cdfe12d266c0 100644 --- a/Lib/test/test_json/test_scanstring.py +++ b/Lib/test/test_json/test_scanstring.py @@ -144,7 +144,7 @@ def test_bad_escapes(self): def test_overflow(self): with self.assertRaises(OverflowError): - self.json.decoder.scanstring(b"xxx", sys.maxsize+1) + self.json.decoder.scanstring("xxx", sys.maxsize+1) class TestPyScanstring(TestScanstring, PyTest): pass diff --git a/Lib/test/test_logging.py b/Lib/test/test_logging.py index 1f7a4d9e197f9c4..8815426fc99c396 100644 --- a/Lib/test/test_logging.py +++ b/Lib/test/test_logging.py @@ -5826,7 +5826,7 @@ def cleanup(): self.addCleanup(cleanup) self.addCleanup(logging.shutdown) - self.adapter = logging.LoggerAdapter(logger=self.logger, extra=None) + self.adapter = logging.LoggerAdapter(logger=self.logger) def test_exception(self): msg = 'testing exception: %r' @@ -5997,6 +5997,18 @@ def test_extra_merged(self): self.assertEqual(record.foo, '1') self.assertEqual(record.bar, '2') + self.adapter.critical('no extra') # should not fail + self.assertEqual(len(self.recording.records), 2) + record = self.recording.records[-1] + self.assertEqual(record.foo, '1') + self.assertNotHasAttr(record, 'bar') + + self.adapter.critical('none extra', extra=None) # should not fail + self.assertEqual(len(self.recording.records), 3) + record = self.recording.records[-1] + self.assertEqual(record.foo, '1') + self.assertNotHasAttr(record, 'bar') + def test_extra_merged_log_call_has_precedence(self): self.adapter = logging.LoggerAdapter(logger=self.logger, extra={'foo': '1'}, @@ -6008,6 +6020,25 @@ def test_extra_merged_log_call_has_precedence(self): self.assertHasAttr(record, 'foo') self.assertEqual(record.foo, '2') + def test_extra_merged_without_extra(self): + self.adapter = logging.LoggerAdapter(logger=self.logger, + merge_extra=True) + + self.adapter.critical('foo should be here', extra={'foo': '1'}) + self.assertEqual(len(self.recording.records), 1) + record = self.recording.records[-1] + self.assertEqual(record.foo, '1') + + self.adapter.critical('no extra') # should not fail + self.assertEqual(len(self.recording.records), 2) + record = self.recording.records[-1] + self.assertNotHasAttr(record, 'foo') + + self.adapter.critical('none extra', extra=None) # should not fail + self.assertEqual(len(self.recording.records), 3) + record = self.recording.records[-1] + self.assertNotHasAttr(record, 'foo') + class PrefixAdapter(logging.LoggerAdapter): prefix = 'Adapter' diff --git a/Lib/test/test_pyrepl/test_interact.py b/Lib/test/test_pyrepl/test_interact.py index 1a3146da8eadc80..fd4530ebc004aa7 100644 --- a/Lib/test/test_pyrepl/test_interact.py +++ b/Lib/test/test_pyrepl/test_interact.py @@ -1,5 +1,6 @@ import contextlib import io +import warnings import unittest from unittest.mock import patch from textwrap import dedent @@ -273,3 +274,28 @@ def test_incomplete_statement(self): code = "if foo:" console = InteractiveColoredConsole(namespace, filename="") self.assertTrue(_more_lines(console, code)) + + +class TestWarnings(unittest.TestCase): + def test_pep_765_warning(self): + """ + Test that a SyntaxWarning emitted from the + AST optimizer is only shown once in the REPL. + """ + # gh-131927 + console = InteractiveColoredConsole() + code = dedent("""\ + def f(): + try: + return 1 + finally: + return 2 + """) + + with warnings.catch_warnings(record=True) as caught: + warnings.simplefilter("always") + console.runsource(code) + + count = sum("'return' in a 'finally' block" in str(w.message) + for w in caught) + self.assertEqual(count, 1) diff --git a/Lib/test/test_tkinter/test_font.py b/Lib/test/test_tkinter/test_font.py index 3616da54cf7075d..fc50f9fdbb588c3 100644 --- a/Lib/test/test_tkinter/test_font.py +++ b/Lib/test/test_tkinter/test_font.py @@ -1,3 +1,4 @@ +import collections.abc import unittest import tkinter from tkinter import font @@ -118,6 +119,16 @@ def test_repr(self): repr(self.font), f'' ) + def test_iterable_protocol(self): + self.assertNotIsSubclass(font.Font, collections.abc.Iterable) + self.assertNotIsSubclass(font.Font, collections.abc.Container) + self.assertNotIsInstance(self.font, collections.abc.Iterable) + self.assertNotIsInstance(self.font, collections.abc.Container) + with self.assertRaisesRegex(TypeError, 'is not iterable'): + iter(self.font) + with self.assertRaisesRegex(TypeError, 'is not a container or iterable'): + self.font in self.font + class DefaultRootTest(AbstractDefaultRootTest, unittest.TestCase): diff --git a/Lib/test/test_tkinter/test_images.py b/Lib/test/test_tkinter/test_images.py index 38371fe00d6eb5b..358a18beee25710 100644 --- a/Lib/test/test_tkinter/test_images.py +++ b/Lib/test/test_tkinter/test_images.py @@ -1,3 +1,4 @@ +import collections.abc import unittest import tkinter from test import support @@ -61,7 +62,33 @@ def test_image_create_photo(self): self.assertRaises(RuntimeError, tkinter.PhotoImage) -class BitmapImageTest(AbstractTkTest, unittest.TestCase): +class BaseImageTest: + def create(self): + return self.image_class('::img::test', master=self.root, + file=self.testfile) + + def test_bug_100814(self): + # gh-100814: Passing a callable option value causes AttributeError. + with self.assertRaises(tkinter.TclError): + self.image_class('::img::test', master=self.root, spam=print) + image = self.image_class('::img::test', master=self.root) + with self.assertRaises(tkinter.TclError): + image.configure(spam=print) + + def test_iterable_protocol(self): + image = self.create() + self.assertNotIsSubclass(self.image_class, collections.abc.Iterable) + self.assertNotIsSubclass(self.image_class, collections.abc.Container) + self.assertNotIsInstance(image, collections.abc.Iterable) + self.assertNotIsInstance(image, collections.abc.Container) + with self.assertRaisesRegex(TypeError, 'is not iterable'): + iter(image) + with self.assertRaisesRegex(TypeError, 'is not a container or iterable'): + image in image + + +class BitmapImageTest(BaseImageTest, AbstractTkTest, unittest.TestCase): + image_class = tkinter.BitmapImage @classmethod def setUpClass(cls): @@ -144,26 +171,15 @@ def test_configure_foreground(self): self.assertEqual(image['foreground'], '-foreground {} {} #000000 yellow') - def test_bug_100814(self): - # gh-100814: Passing a callable option value causes AttributeError. - with self.assertRaises(tkinter.TclError): - tkinter.BitmapImage('::img::test', master=self.root, spam=print) - image = tkinter.BitmapImage('::img::test', master=self.root) - with self.assertRaises(tkinter.TclError): - image.configure(spam=print) - -class PhotoImageTest(AbstractTkTest, unittest.TestCase): +class PhotoImageTest(BaseImageTest, AbstractTkTest, unittest.TestCase): + image_class = tkinter.PhotoImage @classmethod def setUpClass(cls): AbstractTkTest.setUpClass.__func__(cls) cls.testfile = support.findfile('python.gif', subdir='tkinterdata') - def create(self): - return tkinter.PhotoImage('::img::test', master=self.root, - file=self.testfile) - def colorlist(self, *args): if tkinter.TkVersion >= 8.6 and self.wantobjects: return args @@ -282,14 +298,6 @@ def test_configure_palette(self): image.configure(palette='3/4/2') self.assertEqual(image['palette'], '3/4/2') - def test_bug_100814(self): - # gh-100814: Passing a callable option value causes AttributeError. - with self.assertRaises(tkinter.TclError): - tkinter.PhotoImage('::img::test', master=self.root, spam=print) - image = tkinter.PhotoImage('::img::test', master=self.root) - with self.assertRaises(tkinter.TclError): - image.configure(spam=print) - def test_blank(self): image = self.create() image.blank() diff --git a/Lib/test/test_tkinter/test_misc.py b/Lib/test/test_tkinter/test_misc.py index 0c76e07066f8a87..32e2329506e7ff7 100644 --- a/Lib/test/test_tkinter/test_misc.py +++ b/Lib/test/test_tkinter/test_misc.py @@ -1,3 +1,4 @@ +import collections.abc import functools import unittest import tkinter @@ -508,6 +509,17 @@ def test_embedded_null(self): widget.selection_range(0, 'end') self.assertEqual(widget.selection_get(), '\u20ac\0abc\x00def') + def test_iterable_protocol(self): + widget = tkinter.Entry(self.root) + self.assertNotIsSubclass(tkinter.Entry, collections.abc.Iterable) + self.assertNotIsSubclass(tkinter.Entry, collections.abc.Container) + self.assertNotIsInstance(widget, collections.abc.Iterable) + self.assertNotIsInstance(widget, collections.abc.Container) + with self.assertRaisesRegex(TypeError, 'is not iterable'): + iter(widget) + with self.assertRaisesRegex(TypeError, 'is not a container or iterable'): + widget in widget + class WmTest(AbstractTkTest, unittest.TestCase): diff --git a/Lib/test/test_unicodedata.py b/Lib/test/test_unicodedata.py index 8013eaf6e9d8519..a3c22a4f27ee77e 100644 --- a/Lib/test/test_unicodedata.py +++ b/Lib/test/test_unicodedata.py @@ -276,6 +276,33 @@ def test_east_asian_width_9_0_changes(self): self.assertEqual(self.db.ucd_3_2_0.east_asian_width('\u231a'), 'N') self.assertEqual(self.db.east_asian_width('\u231a'), 'W') + def test_isxidstart(self): + self.assertTrue(self.db.isxidstart('S')) + self.assertTrue(self.db.isxidstart('\u0AD0')) # GUJARATI OM + self.assertTrue(self.db.isxidstart('\u0EC6')) # LAO KO LA + self.assertTrue(self.db.isxidstart('\u17DC')) # KHMER SIGN AVAKRAHASANYA + self.assertTrue(self.db.isxidstart('\uA015')) # YI SYLLABLE WU + self.assertTrue(self.db.isxidstart('\uFE7B')) # ARABIC KASRA MEDIAL FORM + + self.assertFalse(self.db.isxidstart(' ')) + self.assertFalse(self.db.isxidstart('0')) + self.assertRaises(TypeError, self.db.isxidstart) + self.assertRaises(TypeError, self.db.isxidstart, 'xx') + + def test_isxidcontinue(self): + self.assertTrue(self.db.isxidcontinue('S')) + self.assertTrue(self.db.isxidcontinue('_')) + self.assertTrue(self.db.isxidcontinue('0')) + self.assertTrue(self.db.isxidcontinue('\u00BA')) # MASCULINE ORDINAL INDICATOR + self.assertTrue(self.db.isxidcontinue('\u0640')) # ARABIC TATWEEL + self.assertTrue(self.db.isxidcontinue('\u0710')) # SYRIAC LETTER ALAPH + self.assertTrue(self.db.isxidcontinue('\u0B3E')) # ORIYA VOWEL SIGN AA + self.assertTrue(self.db.isxidcontinue('\u17D7')) # KHMER SIGN LEK TOO + + self.assertFalse(self.db.isxidcontinue(' ')) + self.assertRaises(TypeError, self.db.isxidcontinue) + self.assertRaises(TypeError, self.db.isxidcontinue, 'xx') + class UnicodeMiscTest(UnicodeDatabaseTest): @cpython_only diff --git a/Lib/tkinter/__init__.py b/Lib/tkinter/__init__.py index 9526d8b949fa3b8..c54530740395f75 100644 --- a/Lib/tkinter/__init__.py +++ b/Lib/tkinter/__init__.py @@ -1848,6 +1848,7 @@ def cget(self, key): return self.tk.call(self._w, 'cget', '-' + key) __getitem__ = cget + __iter__ = None # prevent using __getitem__ for iteration def __setitem__(self, key, value): self.configure({key: value}) @@ -4280,6 +4281,8 @@ def __setitem__(self, key, value): def __getitem__(self, key): return self.tk.call(self.name, 'configure', '-'+key) + __iter__ = None # prevent using __getitem__ for iteration + def configure(self, **kw): """Configure the image.""" res = () diff --git a/Lib/tkinter/font.py b/Lib/tkinter/font.py index 7aed523cce3784b..896e910d69f6f33 100644 --- a/Lib/tkinter/font.py +++ b/Lib/tkinter/font.py @@ -114,6 +114,8 @@ def __getitem__(self, key): def __setitem__(self, key, value): self.configure(**{key: value}) + __iter__ = None # prevent using __getitem__ for iteration + def __del__(self): try: if self.delete_font: diff --git a/Makefile.pre.in b/Makefile.pre.in index 19423c11545c198..0a1b8d028addad5 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -1433,6 +1433,7 @@ PYTHON_HEADERS= \ $(srcdir)/Include/internal/pycore_typeobject.h \ $(srcdir)/Include/internal/pycore_typevarobject.h \ $(srcdir)/Include/internal/pycore_ucnhash.h \ + $(srcdir)/Include/internal/pycore_unicodectype.h \ $(srcdir)/Include/internal/pycore_unicodeobject.h \ $(srcdir)/Include/internal/pycore_unicodeobject_generated.h \ $(srcdir)/Include/internal/pycore_unionobject.h \ diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-10-06-10-03-37.gh-issue-139640.gY5oTb2.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-10-06-10-03-37.gh-issue-139640.gY5oTb2.rst new file mode 100644 index 000000000000000..b147b430ccccf59 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-10-06-10-03-37.gh-issue-139640.gY5oTb2.rst @@ -0,0 +1,3 @@ +:func:`ast.parse` no longer emits syntax warnings for +``return``/``break``/``continue`` in ``finally`` (see :pep:`765`) -- they are +only emitted during compilation. diff --git a/Misc/NEWS.d/next/Library/2025-10-17-20-42-38.gh-issue-129117.X9jr4p.rst b/Misc/NEWS.d/next/Library/2025-10-17-20-42-38.gh-issue-129117.X9jr4p.rst new file mode 100644 index 000000000000000..8767b1bb4837adf --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-10-17-20-42-38.gh-issue-129117.X9jr4p.rst @@ -0,0 +1,3 @@ +:mod:`unicodedata`: Add :func:`~unicodedata.isxidstart` and +:func:`~unicodedata.isxidcontinue` functions to check whether a character can +start or continue a `Unicode Standard Annex #31 `_ identifier. diff --git a/Misc/NEWS.d/next/Library/2025-10-23-13-42-15.gh-issue-140481.XKxWpq.rst b/Misc/NEWS.d/next/Library/2025-10-23-13-42-15.gh-issue-140481.XKxWpq.rst new file mode 100644 index 000000000000000..1f511c3b9d05831 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-10-23-13-42-15.gh-issue-140481.XKxWpq.rst @@ -0,0 +1 @@ +Improve error message when trying to iterate a Tk widget, image or font. diff --git a/Misc/NEWS.d/next/Library/2025-10-23-19-39-16.gh-issue-138162.Znw5DN.rst b/Misc/NEWS.d/next/Library/2025-10-23-19-39-16.gh-issue-138162.Znw5DN.rst new file mode 100644 index 000000000000000..ef7a90bc37e6504 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-10-23-19-39-16.gh-issue-138162.Znw5DN.rst @@ -0,0 +1,2 @@ +Fix :class:`logging.LoggerAdapter` with ``merge_extra=True`` and without the +*extra* argument. diff --git a/Misc/NEWS.d/next/Library/2025-10-30-15-33-07.gh-issue-137821.8_Iavt.rst b/Misc/NEWS.d/next/Library/2025-10-30-15-33-07.gh-issue-137821.8_Iavt.rst new file mode 100644 index 000000000000000..7ccbfc3cb950bf2 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-10-30-15-33-07.gh-issue-137821.8_Iavt.rst @@ -0,0 +1,2 @@ +Convert ``_json`` module to use Argument Clinic. +Patched by Yoonho Hann. diff --git a/Misc/NEWS.d/next/Tools-Demos/2025-09-20-20-31-54.gh-issue-139188.zfcxkW.rst b/Misc/NEWS.d/next/Tools-Demos/2025-09-20-20-31-54.gh-issue-139188.zfcxkW.rst new file mode 100644 index 000000000000000..9f52d0163ab0387 --- /dev/null +++ b/Misc/NEWS.d/next/Tools-Demos/2025-09-20-20-31-54.gh-issue-139188.zfcxkW.rst @@ -0,0 +1 @@ +Remove ``Tools/tz/zdump.py`` script. diff --git a/Misc/NEWS.d/next/Tools-Demos/2025-09-21-10-30-08.gh-issue-139198.Fm7NfU.rst b/Misc/NEWS.d/next/Tools-Demos/2025-09-21-10-30-08.gh-issue-139198.Fm7NfU.rst new file mode 100644 index 000000000000000..0dc589c3986ad63 --- /dev/null +++ b/Misc/NEWS.d/next/Tools-Demos/2025-09-21-10-30-08.gh-issue-139198.Fm7NfU.rst @@ -0,0 +1 @@ +Remove ``Tools/scripts/checkpip.py`` script. diff --git a/Modules/_json.c b/Modules/_json.c index 9a1fc3aba361167..14714d4b346546d 100644 --- a/Modules/_json.c +++ b/Modules/_json.c @@ -18,6 +18,12 @@ #include // bool +#include "clinic/_json.c.h" + +/*[clinic input] +module _json +[clinic start generated code]*/ +/*[clinic end generated code: output=da39a3ee5e6b4b0d input=549fa53592c925b2]*/ typedef struct _PyScannerObject { PyObject_HEAD @@ -637,89 +643,62 @@ scanstring_unicode(PyObject *pystr, Py_ssize_t end, int strict, Py_ssize_t *next return NULL; } -PyDoc_STRVAR(pydoc_scanstring, - "scanstring(string, end, strict=True) -> (string, end)\n" - "\n" - "Scan the string s for a JSON string. End is the index of the\n" - "character in s after the quote that started the JSON string.\n" - "Unescapes all valid JSON string escape sequences and raises ValueError\n" - "on attempt to decode an invalid string. If strict is False then literal\n" - "control characters are allowed in the string.\n" - "\n" - "Returns a tuple of the decoded string and the index of the character in s\n" - "after the end quote." -); +/*[clinic input] +_json.scanstring as py_scanstring + pystr: unicode + end: Py_ssize_t + strict: bool = True + / + +Scan the string s for a JSON string. + +End is the index of the character in s after the quote that started the +JSON string. Unescapes all valid JSON string escape sequences and raises +ValueError on attempt to decode an invalid string. If strict is False +then literal control characters are allowed in the string. + +Returns a tuple of the decoded string and the index of the character in s +after the end quote. +[clinic start generated code]*/ static PyObject * -py_scanstring(PyObject* Py_UNUSED(self), PyObject *args) +py_scanstring_impl(PyObject *module, PyObject *pystr, Py_ssize_t end, + int strict) +/*[clinic end generated code: output=961740cfae07cdb3 input=cff59e47498f4d8e]*/ { - PyObject *pystr; - PyObject *rval; - Py_ssize_t end; Py_ssize_t next_end = -1; - int strict = 1; - if (!PyArg_ParseTuple(args, "On|p:scanstring", &pystr, &end, &strict)) { - return NULL; - } - if (PyUnicode_Check(pystr)) { - rval = scanstring_unicode(pystr, end, strict, &next_end); - } - else { - PyErr_Format(PyExc_TypeError, - "first argument must be a string, not %.80s", - Py_TYPE(pystr)->tp_name); - return NULL; - } + PyObject *rval = scanstring_unicode(pystr, end, strict, &next_end); return _build_rval_index_tuple(rval, next_end); } -PyDoc_STRVAR(pydoc_encode_basestring_ascii, - "encode_basestring_ascii(string) -> string\n" - "\n" - "Return an ASCII-only JSON representation of a Python string" -); +/*[clinic input] +_json.encode_basestring_ascii as py_encode_basestring_ascii + pystr: unicode + / + +Return an ASCII-only JSON representation of a Python string +[clinic start generated code]*/ static PyObject * -py_encode_basestring_ascii(PyObject* Py_UNUSED(self), PyObject *pystr) +py_encode_basestring_ascii_impl(PyObject *module, PyObject *pystr) +/*[clinic end generated code: output=7b3841287cf211df input=4f3609498aff2de5]*/ { - PyObject *rval; - /* Return an ASCII-only JSON representation of a Python string */ - /* METH_O */ - if (PyUnicode_Check(pystr)) { - rval = ascii_escape_unicode(pystr); - } - else { - PyErr_Format(PyExc_TypeError, - "first argument must be a string, not %.80s", - Py_TYPE(pystr)->tp_name); - return NULL; - } - return rval; + return ascii_escape_unicode(pystr); } +/*[clinic input] +_json.encode_basestring as py_encode_basestring + pystr: unicode + / -PyDoc_STRVAR(pydoc_encode_basestring, - "encode_basestring(string) -> string\n" - "\n" - "Return a JSON representation of a Python string" -); +Return a JSON representation of a Python string +[clinic start generated code]*/ static PyObject * -py_encode_basestring(PyObject* Py_UNUSED(self), PyObject *pystr) +py_encode_basestring_impl(PyObject *module, PyObject *pystr) +/*[clinic end generated code: output=900950f95df3f1c9 input=d42ef714b2c07386]*/ { - PyObject *rval; - /* Return a JSON representation of a Python string */ - /* METH_O */ - if (PyUnicode_Check(pystr)) { - rval = escape_unicode(pystr); - } - else { - PyErr_Format(PyExc_TypeError, - "first argument must be a string, not %.80s", - Py_TYPE(pystr)->tp_name); - return NULL; - } - return rval; + return escape_unicode(pystr); } static void @@ -2080,18 +2059,9 @@ static PyType_Spec PyEncoderType_spec = { }; static PyMethodDef speedups_methods[] = { - {"encode_basestring_ascii", - py_encode_basestring_ascii, - METH_O, - pydoc_encode_basestring_ascii}, - {"encode_basestring", - py_encode_basestring, - METH_O, - pydoc_encode_basestring}, - {"scanstring", - py_scanstring, - METH_VARARGS, - pydoc_scanstring}, + PY_ENCODE_BASESTRING_ASCII_METHODDEF + PY_ENCODE_BASESTRING_METHODDEF + PY_SCANSTRING_METHODDEF {NULL, NULL, 0, NULL} }; diff --git a/Modules/clinic/_json.c.h b/Modules/clinic/_json.c.h new file mode 100644 index 000000000000000..cd37a236c7611a1 --- /dev/null +++ b/Modules/clinic/_json.c.h @@ -0,0 +1,128 @@ +/*[clinic input] +preserve +[clinic start generated code]*/ + +#include "pycore_abstract.h" // _PyNumber_Index() +#include "pycore_modsupport.h" // _PyArg_CheckPositional() + +PyDoc_STRVAR(py_scanstring__doc__, +"scanstring($module, pystr, end, strict=True, /)\n" +"--\n" +"\n" +"Scan the string s for a JSON string.\n" +"\n" +"End is the index of the character in s after the quote that started the\n" +"JSON string. Unescapes all valid JSON string escape sequences and raises\n" +"ValueError on attempt to decode an invalid string. If strict is False\n" +"then literal control characters are allowed in the string.\n" +"\n" +"Returns a tuple of the decoded string and the index of the character in s\n" +"after the end quote."); + +#define PY_SCANSTRING_METHODDEF \ + {"scanstring", _PyCFunction_CAST(py_scanstring), METH_FASTCALL, py_scanstring__doc__}, + +static PyObject * +py_scanstring_impl(PyObject *module, PyObject *pystr, Py_ssize_t end, + int strict); + +static PyObject * +py_scanstring(PyObject *module, PyObject *const *args, Py_ssize_t nargs) +{ + PyObject *return_value = NULL; + PyObject *pystr; + Py_ssize_t end; + int strict = 1; + + if (!_PyArg_CheckPositional("scanstring", nargs, 2, 3)) { + goto exit; + } + if (!PyUnicode_Check(args[0])) { + _PyArg_BadArgument("scanstring", "argument 1", "str", args[0]); + goto exit; + } + pystr = args[0]; + { + Py_ssize_t ival = -1; + PyObject *iobj = _PyNumber_Index(args[1]); + if (iobj != NULL) { + ival = PyLong_AsSsize_t(iobj); + Py_DECREF(iobj); + } + if (ival == -1 && PyErr_Occurred()) { + goto exit; + } + end = ival; + } + if (nargs < 3) { + goto skip_optional; + } + strict = PyObject_IsTrue(args[2]); + if (strict < 0) { + goto exit; + } +skip_optional: + return_value = py_scanstring_impl(module, pystr, end, strict); + +exit: + return return_value; +} + +PyDoc_STRVAR(py_encode_basestring_ascii__doc__, +"encode_basestring_ascii($module, pystr, /)\n" +"--\n" +"\n" +"Return an ASCII-only JSON representation of a Python string"); + +#define PY_ENCODE_BASESTRING_ASCII_METHODDEF \ + {"encode_basestring_ascii", (PyCFunction)py_encode_basestring_ascii, METH_O, py_encode_basestring_ascii__doc__}, + +static PyObject * +py_encode_basestring_ascii_impl(PyObject *module, PyObject *pystr); + +static PyObject * +py_encode_basestring_ascii(PyObject *module, PyObject *arg) +{ + PyObject *return_value = NULL; + PyObject *pystr; + + if (!PyUnicode_Check(arg)) { + _PyArg_BadArgument("encode_basestring_ascii", "argument", "str", arg); + goto exit; + } + pystr = arg; + return_value = py_encode_basestring_ascii_impl(module, pystr); + +exit: + return return_value; +} + +PyDoc_STRVAR(py_encode_basestring__doc__, +"encode_basestring($module, pystr, /)\n" +"--\n" +"\n" +"Return a JSON representation of a Python string"); + +#define PY_ENCODE_BASESTRING_METHODDEF \ + {"encode_basestring", (PyCFunction)py_encode_basestring, METH_O, py_encode_basestring__doc__}, + +static PyObject * +py_encode_basestring_impl(PyObject *module, PyObject *pystr); + +static PyObject * +py_encode_basestring(PyObject *module, PyObject *arg) +{ + PyObject *return_value = NULL; + PyObject *pystr; + + if (!PyUnicode_Check(arg)) { + _PyArg_BadArgument("encode_basestring", "argument", "str", arg); + goto exit; + } + pystr = arg; + return_value = py_encode_basestring_impl(module, pystr); + +exit: + return return_value; +} +/*[clinic end generated code: output=5bdd16375c95a4d9 input=a9049054013a1b77]*/ diff --git a/Modules/clinic/unicodedata.c.h b/Modules/clinic/unicodedata.c.h index 345440eeee89a68..5fcba083c2f4ce4 100644 --- a/Modules/clinic/unicodedata.c.h +++ b/Modules/clinic/unicodedata.c.h @@ -518,6 +518,78 @@ unicodedata_UCD_name(PyObject *self, PyObject *const *args, Py_ssize_t nargs) return return_value; } +PyDoc_STRVAR(unicodedata_UCD_isxidstart__doc__, +"isxidstart($self, chr, /)\n" +"--\n" +"\n" +"Return True if the character has the XID_Start property, else False."); + +#define UNICODEDATA_UCD_ISXIDSTART_METHODDEF \ + {"isxidstart", (PyCFunction)unicodedata_UCD_isxidstart, METH_O, unicodedata_UCD_isxidstart__doc__}, + +static PyObject * +unicodedata_UCD_isxidstart_impl(PyObject *self, int chr); + +static PyObject * +unicodedata_UCD_isxidstart(PyObject *self, PyObject *arg) +{ + PyObject *return_value = NULL; + int chr; + + if (!PyUnicode_Check(arg)) { + _PyArg_BadArgument("isxidstart", "argument", "a unicode character", arg); + goto exit; + } + if (PyUnicode_GET_LENGTH(arg) != 1) { + PyErr_Format(PyExc_TypeError, + "isxidstart(): argument must be a unicode character, " + "not a string of length %zd", + PyUnicode_GET_LENGTH(arg)); + goto exit; + } + chr = PyUnicode_READ_CHAR(arg, 0); + return_value = unicodedata_UCD_isxidstart_impl(self, chr); + +exit: + return return_value; +} + +PyDoc_STRVAR(unicodedata_UCD_isxidcontinue__doc__, +"isxidcontinue($self, chr, /)\n" +"--\n" +"\n" +"Return True if the character has the XID_Continue property, else False."); + +#define UNICODEDATA_UCD_ISXIDCONTINUE_METHODDEF \ + {"isxidcontinue", (PyCFunction)unicodedata_UCD_isxidcontinue, METH_O, unicodedata_UCD_isxidcontinue__doc__}, + +static PyObject * +unicodedata_UCD_isxidcontinue_impl(PyObject *self, int chr); + +static PyObject * +unicodedata_UCD_isxidcontinue(PyObject *self, PyObject *arg) +{ + PyObject *return_value = NULL; + int chr; + + if (!PyUnicode_Check(arg)) { + _PyArg_BadArgument("isxidcontinue", "argument", "a unicode character", arg); + goto exit; + } + if (PyUnicode_GET_LENGTH(arg) != 1) { + PyErr_Format(PyExc_TypeError, + "isxidcontinue(): argument must be a unicode character, " + "not a string of length %zd", + PyUnicode_GET_LENGTH(arg)); + goto exit; + } + chr = PyUnicode_READ_CHAR(arg, 0); + return_value = unicodedata_UCD_isxidcontinue_impl(self, chr); + +exit: + return return_value; +} + PyDoc_STRVAR(unicodedata_UCD_lookup__doc__, "lookup($self, name, /)\n" "--\n" @@ -549,4 +621,4 @@ unicodedata_UCD_lookup(PyObject *self, PyObject *arg) exit: return return_value; } -/*[clinic end generated code: output=8a59d430cee41058 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=c5e56c8f6bb80f93 input=a9049054013a1b77]*/ diff --git a/Modules/unicodedata.c b/Modules/unicodedata.c index a3699beff7da017..a6094676d4194cc 100644 --- a/Modules/unicodedata.c +++ b/Modules/unicodedata.c @@ -19,6 +19,7 @@ #include "Python.h" #include "pycore_object.h" // _PyObject_VisitType() #include "pycore_ucnhash.h" // _PyUnicode_Name_CAPI +#include "pycore_unicodectype.h" // _PyUnicode_IsXidStart() #include #include // offsetof() @@ -1525,6 +1526,58 @@ unicodedata_UCD_name_impl(PyObject *self, int chr, PyObject *default_value) return PyUnicode_FromString(name); } +/*[clinic input] +unicodedata.UCD.isxidstart + + self: self + chr: int(accept={str}) + / + +Return True if the character has the XID_Start property, else False. + +[clinic start generated code]*/ + +static PyObject * +unicodedata_UCD_isxidstart_impl(PyObject *self, int chr) +/*[clinic end generated code: output=944005823c72c3ef input=9353f88d709c21fb]*/ +{ + if (UCD_Check(self)) { + const change_record *old = get_old_record(self, chr); + if (old->category_changed == 0) { + /* unassigned */ + Py_RETURN_FALSE; + } + } + + return PyBool_FromLong(_PyUnicode_IsXidStart(chr)); +} + +/*[clinic input] +unicodedata.UCD.isxidcontinue + + self: self + chr: int(accept={str}) + / + +Return True if the character has the XID_Continue property, else False. + +[clinic start generated code]*/ + +static PyObject * +unicodedata_UCD_isxidcontinue_impl(PyObject *self, int chr) +/*[clinic end generated code: output=9438dcbff5ca3e41 input=bbb8dd3ac0d2d709]*/ +{ + if (UCD_Check(self)) { + const change_record *old = get_old_record(self, chr); + if (old->category_changed == 0) { + /* unassigned */ + Py_RETURN_FALSE; + } + } + + return PyBool_FromLong(_PyUnicode_IsXidContinue(chr)); +} + /*[clinic input] unicodedata.UCD.lookup @@ -1590,6 +1643,8 @@ static PyMethodDef unicodedata_functions[] = { UNICODEDATA_UCD_EAST_ASIAN_WIDTH_METHODDEF UNICODEDATA_UCD_DECOMPOSITION_METHODDEF UNICODEDATA_UCD_NAME_METHODDEF + UNICODEDATA_UCD_ISXIDSTART_METHODDEF + UNICODEDATA_UCD_ISXIDCONTINUE_METHODDEF UNICODEDATA_UCD_LOOKUP_METHODDEF UNICODEDATA_UCD_IS_NORMALIZED_METHODDEF UNICODEDATA_UCD_NORMALIZE_METHODDEF diff --git a/Objects/unicodectype.c b/Objects/unicodectype.c index 7cd0dca3d135455..fdd380190ac1ecc 100644 --- a/Objects/unicodectype.c +++ b/Objects/unicodectype.c @@ -9,6 +9,7 @@ */ #include "Python.h" +#include "pycore_unicodectype.h" // export _PyUnicode_IsXidStart(), _PyUnicode_IsXidContinue() #define ALPHA_MASK 0x01 #define DECIMAL_MASK 0x02 diff --git a/Objects/unicodeobject.c b/Objects/unicodeobject.c index f60f7dd2d136049..8a5638ac1406ab6 100644 --- a/Objects/unicodeobject.c +++ b/Objects/unicodeobject.c @@ -57,6 +57,7 @@ OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. #include "pycore_pylifecycle.h" // _Py_SetFileSystemEncoding() #include "pycore_pystate.h" // _PyInterpreterState_GET() #include "pycore_ucnhash.h" // _PyUnicode_Name_CAPI +#include "pycore_unicodectype.h" // _PyUnicode_IsXidStart #include "pycore_unicodeobject.h" // struct _Py_unicode_state #include "pycore_unicodeobject_generated.h" // _PyUnicode_InitStaticStrings() diff --git a/PCbuild/pythoncore.vcxproj b/PCbuild/pythoncore.vcxproj index 2657ee5c444e607..a101c1b45cf25c7 100644 --- a/PCbuild/pythoncore.vcxproj +++ b/PCbuild/pythoncore.vcxproj @@ -328,6 +328,7 @@ + diff --git a/PCbuild/pythoncore.vcxproj.filters b/PCbuild/pythoncore.vcxproj.filters index 9c12be6e9356a62..e3f261c2b92ab97 100644 --- a/PCbuild/pythoncore.vcxproj.filters +++ b/PCbuild/pythoncore.vcxproj.filters @@ -528,6 +528,9 @@ Include\cpython + + Include\internal + Include\internal diff --git a/Python/ast_preprocess.c b/Python/ast_preprocess.c index 44d3075098be752..fe6fd9479d1531f 100644 --- a/Python/ast_preprocess.c +++ b/Python/ast_preprocess.c @@ -19,6 +19,7 @@ typedef struct { int optimize; int ff_features; int syntax_check_only; + int enable_warnings; _Py_c_array_t cf_finally; /* context for PEP 765 check */ int cf_finally_used; @@ -78,7 +79,7 @@ control_flow_in_finally_warning(const char *kw, stmt_ty n, _PyASTPreprocessState static int before_return(_PyASTPreprocessState *state, stmt_ty node_) { - if (state->cf_finally_used > 0) { + if (state->enable_warnings && state->cf_finally_used > 0) { ControlFlowInFinallyContext *ctx = get_cf_finally_top(state); if (ctx->in_finally && ! ctx->in_funcdef) { if (!control_flow_in_finally_warning("return", node_, state)) { @@ -92,7 +93,7 @@ before_return(_PyASTPreprocessState *state, stmt_ty node_) static int before_loop_exit(_PyASTPreprocessState *state, stmt_ty node_, const char *kw) { - if (state->cf_finally_used > 0) { + if (state->enable_warnings && state->cf_finally_used > 0) { ControlFlowInFinallyContext *ctx = get_cf_finally_top(state); if (ctx->in_finally && ! ctx->in_loop) { if (!control_flow_in_finally_warning(kw, node_, state)) { @@ -968,7 +969,7 @@ astfold_type_param(type_param_ty node_, PyArena *ctx_, _PyASTPreprocessState *st int _PyAST_Preprocess(mod_ty mod, PyArena *arena, PyObject *filename, int optimize, - int ff_features, int syntax_check_only) + int ff_features, int syntax_check_only, int enable_warnings) { _PyASTPreprocessState state; memset(&state, 0, sizeof(_PyASTPreprocessState)); @@ -976,6 +977,7 @@ _PyAST_Preprocess(mod_ty mod, PyArena *arena, PyObject *filename, int optimize, state.optimize = optimize; state.ff_features = ff_features; state.syntax_check_only = syntax_check_only; + state.enable_warnings = enable_warnings; if (_Py_CArray_Init(&state.cf_finally, sizeof(ControlFlowInFinallyContext), 20) < 0) { return -1; } diff --git a/Python/compile.c b/Python/compile.c index 8070d3f03760efc..e2f1c7e8eb5bce1 100644 --- a/Python/compile.c +++ b/Python/compile.c @@ -136,7 +136,7 @@ compiler_setup(compiler *c, mod_ty mod, PyObject *filename, c->c_optimize = (optimize == -1) ? _Py_GetConfig()->optimization_level : optimize; c->c_save_nested_seqs = false; - if (!_PyAST_Preprocess(mod, arena, filename, c->c_optimize, merged, 0)) { + if (!_PyAST_Preprocess(mod, arena, filename, c->c_optimize, merged, 0, 1)) { return ERROR; } c->c_st = _PySymtable_Build(mod, filename, &c->c_future); @@ -1502,7 +1502,7 @@ _PyCompile_AstPreprocess(mod_ty mod, PyObject *filename, PyCompilerFlags *cf, if (optimize == -1) { optimize = _Py_GetConfig()->optimization_level; } - if (!_PyAST_Preprocess(mod, arena, filename, optimize, flags, no_const_folding)) { + if (!_PyAST_Preprocess(mod, arena, filename, optimize, flags, no_const_folding, 0)) { return -1; } return 0; diff --git a/Tools/README b/Tools/README index c8a34d82206672e..22d76dfdbcf4a4b 100644 --- a/Tools/README +++ b/Tools/README @@ -51,8 +51,6 @@ ssl Scripts to generate ssl_data.h from OpenSSL sources, and run tsan Utilities for building CPython with thread-sanitizer. -tz A script to dump timezone from /usr/share/zoneinfo. - unicode Tools for generating unicodedata and codecs from unicode.org and other mapping files (by Fredrik Lundh, Marc-Andre Lemburg and Martin von Loewis). diff --git a/Tools/scripts/README b/Tools/scripts/README index a078bfbf662a375..4e52cda38e8d882 100644 --- a/Tools/scripts/README +++ b/Tools/scripts/README @@ -1,8 +1,6 @@ This directory contains a collection of executable Python scripts that are useful while building, extending or managing Python. -checkpip.py Checks the version of the projects bundled in ensurepip - are the latest available combinerefs.py A helper for analyzing PYTHONDUMPREFS output divmod_threshold.py Determine threshold for switching from longobject.c divmod to _pylong.int_divmod() diff --git a/Tools/scripts/checkpip.py b/Tools/scripts/checkpip.py deleted file mode 100755 index a4a9ddfa6f324a9..000000000000000 --- a/Tools/scripts/checkpip.py +++ /dev/null @@ -1,32 +0,0 @@ -#!/usr/bin/env python3 -""" -Checks that the version of the projects bundled in ensurepip are the latest -versions available. -""" -import ensurepip -import json -import urllib.request -import sys - - -def main(): - outofdate = False - - for project, version in ensurepip._PROJECTS: - data = json.loads(urllib.request.urlopen( - "https://pypi.org/pypi/{}/json".format(project), - cadefault=True, - ).read().decode("utf8")) - upstream_version = data["info"]["version"] - - if version != upstream_version: - outofdate = True - print("The latest version of {} on PyPI is {}, but ensurepip " - "has {}".format(project, upstream_version, version)) - - if outofdate: - sys.exit(1) - - -if __name__ == "__main__": - main() diff --git a/Tools/tz/zdump.py b/Tools/tz/zdump.py deleted file mode 100644 index 39de0a416d02517..000000000000000 --- a/Tools/tz/zdump.py +++ /dev/null @@ -1,81 +0,0 @@ -import sys -import os -import struct -from array import array -from collections import namedtuple -from datetime import datetime - -ttinfo = namedtuple('ttinfo', ['tt_gmtoff', 'tt_isdst', 'tt_abbrind']) - -class TZInfo: - def __init__(self, transitions, type_indices, ttis, abbrs): - self.transitions = transitions - self.type_indices = type_indices - self.ttis = ttis - self.abbrs = abbrs - - @classmethod - def fromfile(cls, fileobj): - if fileobj.read(4).decode() != "TZif": - raise ValueError("not a zoneinfo file") - fileobj.seek(20) - header = fileobj.read(24) - tzh = (tzh_ttisgmtcnt, tzh_ttisstdcnt, tzh_leapcnt, - tzh_timecnt, tzh_typecnt, tzh_charcnt) = struct.unpack(">6l", header) - transitions = array('i') - transitions.fromfile(fileobj, tzh_timecnt) - if sys.byteorder != 'big': - transitions.byteswap() - - type_indices = array('B') - type_indices.fromfile(fileobj, tzh_timecnt) - - ttis = [] - for i in range(tzh_typecnt): - ttis.append(ttinfo._make(struct.unpack(">lbb", fileobj.read(6)))) - - abbrs = fileobj.read(tzh_charcnt) - - self = cls(transitions, type_indices, ttis, abbrs) - self.tzh = tzh - - return self - - def dump(self, stream, start=None, end=None): - for j, (trans, i) in enumerate(zip(self.transitions, self.type_indices)): - utc = datetime.utcfromtimestamp(trans) - tti = self.ttis[i] - lmt = datetime.utcfromtimestamp(trans + tti.tt_gmtoff) - abbrind = tti.tt_abbrind - abbr = self.abbrs[abbrind:self.abbrs.find(0, abbrind)].decode() - if j > 0: - prev_tti = self.ttis[self.type_indices[j - 1]] - shift = " %+g" % ((tti.tt_gmtoff - prev_tti.tt_gmtoff) / 3600) - else: - shift = '' - print("%s UTC = %s %-5s isdst=%d" % (utc, lmt, abbr, tti[1]) + shift, file=stream) - - @classmethod - def zonelist(cls, zonedir='/usr/share/zoneinfo'): - zones = [] - for root, _, files in os.walk(zonedir): - for f in files: - p = os.path.join(root, f) - with open(p, 'rb') as o: - magic = o.read(4) - if magic == b'TZif': - zones.append(p[len(zonedir) + 1:]) - return zones - -if __name__ == '__main__': - if len(sys.argv) < 2: - zones = TZInfo.zonelist() - for z in zones: - print(z) - sys.exit() - filepath = sys.argv[1] - if not filepath.startswith('/'): - filepath = os.path.join('/usr/share/zoneinfo', filepath) - with open(filepath, 'rb') as fileobj: - tzi = TZInfo.fromfile(fileobj) - tzi.dump(sys.stdout)