From 1e26ec55e058f9d8cabcb27f4f13844788461e76 Mon Sep 17 00:00:00 2001 From: Thomas Wouters Date: Tue, 1 Oct 2024 07:12:34 +0200 Subject: [PATCH 001/269] Post 3.12.7 --- Include/patchlevel.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Include/patchlevel.h b/Include/patchlevel.h index 4f0c8fb1aa4e48..a1bbc45127b601 100644 --- a/Include/patchlevel.h +++ b/Include/patchlevel.h @@ -23,7 +23,7 @@ #define PY_RELEASE_SERIAL 0 /* Version as a string */ -#define PY_VERSION "3.12.7" +#define PY_VERSION "3.12.7+" /*--end constants--*/ /* Version as a single 4-byte hex number, e.g. 0x010502B2 == 1.5.2b2. From 9547d2a36616525e7e57ef2064cabcfbf5f9b61b Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Tue, 1 Oct 2024 11:50:59 +0200 Subject: [PATCH 002/269] [3.12] Doc: Fix archive filenames for standard builds (GH-124826) (#124837) Doc: Fix archive filenames for standard builds (GH-124826) (cherry picked from commit 91e64be731fe42e6b252b95d79d900251388bfc6) Co-authored-by: Adam Turner <9087854+AA-Turner@users.noreply.github.com> --- Doc/Makefile | 6 ++++-- Doc/tools/extensions/patchlevel.py | 6 +++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/Doc/Makefile b/Doc/Makefile index 70ad703ac77e82..a090ee5ba92705 100644 --- a/Doc/Makefile +++ b/Doc/Makefile @@ -305,13 +305,15 @@ serve: # for development releases: always build .PHONY: autobuild-dev +autobuild-dev: DISTVERSION = $(shell $(PYTHON) tools/extensions/patchlevel.py --short) autobuild-dev: - $(MAKE) dist-no-html SPHINXOPTS='$(SPHINXOPTS) -Ea -A daily=1' + $(MAKE) dist-no-html SPHINXOPTS='$(SPHINXOPTS) -Ea -A daily=1' DISTVERSION=$(DISTVERSION) # for HTML-only rebuilds .PHONY: autobuild-dev-html +autobuild-dev-html: DISTVERSION = $(shell $(PYTHON) tools/extensions/patchlevel.py --short) autobuild-dev-html: - $(MAKE) dist-html SPHINXOPTS='$(SPHINXOPTS) -Ea -A daily=1' + $(MAKE) dist-html SPHINXOPTS='$(SPHINXOPTS) -Ea -A daily=1' DISTVERSION=$(DISTVERSION) # for stable releases: only build if not in pre-release stage (alpha, beta) # release candidate downloads are okay, since the stable tree can be in that stage diff --git a/Doc/tools/extensions/patchlevel.py b/Doc/tools/extensions/patchlevel.py index 53ea1bf47b8fd3..9ccaec3dd5ce0f 100644 --- a/Doc/tools/extensions/patchlevel.py +++ b/Doc/tools/extensions/patchlevel.py @@ -74,4 +74,8 @@ def get_version_info(): if __name__ == "__main__": - print(format_version_info(get_header_version_info())[0]) + short_ver, full_ver = format_version_info(get_header_version_info()) + if sys.argv[1:2] == ["--short"]: + print(short_ver) + else: + print(full_ver) From 79746b31ff1c2658d5f3fb3ce68e12733cf837d4 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Tue, 1 Oct 2024 18:22:05 +0200 Subject: [PATCH 003/269] [3.12] gh-124842: Fix test.support.import_helper.make_legacy_pyc() (GH-124843) (GH-124854) For source file "path/to/file.py" it created file with incorrect path "/absolute/path/to/path/to/file.pyc" instead of "path/to/file.pyc". (cherry picked from commit 60ff67d010078eca15a74b1429caf779ac4f9c74) Co-authored-by: Serhiy Storchaka --- Lib/test/support/import_helper.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/test/support/import_helper.py b/Lib/test/support/import_helper.py index 29c6f535b40342..bb957046364bd4 100644 --- a/Lib/test/support/import_helper.py +++ b/Lib/test/support/import_helper.py @@ -58,8 +58,8 @@ def make_legacy_pyc(source): :return: The file system path to the legacy pyc file. """ pyc_file = importlib.util.cache_from_source(source) - up_one = os.path.dirname(os.path.abspath(source)) - legacy_pyc = os.path.join(up_one, source + 'c') + assert source.endswith('.py') + legacy_pyc = source + 'c' shutil.move(pyc_file, legacy_pyc) return legacy_pyc From b338a6122bd4f9a8dfb294981fd4c0b6cfc1b744 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Wed, 2 Oct 2024 06:57:07 +0200 Subject: [PATCH 004/269] [3.12] Highlight `datetime.timedelta.seconds` vs `.total_seconds()` in docs. (GH-124811) (GH-124863) Highlight `datetime.timedelta.seconds` vs `.total_seconds()` in docs. (GH-124811) Thanks to the reviewers for suggesting the use of a "caution" section instead of "warning" or "note". (cherry picked from commit d150e4abcfc13770c2d239878ed337fb53e51de5) Co-authored-by: Gregory P. Smith --- Doc/library/datetime.rst | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/Doc/library/datetime.rst b/Doc/library/datetime.rst index e3ddd8ca82edb6..8f5ea47da0db25 100644 --- a/Doc/library/datetime.rst +++ b/Doc/library/datetime.rst @@ -295,6 +295,20 @@ Instance attributes (read-only): Between 0 and 86,399 inclusive. + .. caution:: + + It is a somewhat common bug for code to unintentionally use this attribute + when it is actually intended to get a :meth:`~timedelta.total_seconds` + value instead: + + .. doctest:: + + >>> from datetime import timedelta + >>> duration = timedelta(seconds=11235813) + >>> duration.days, duration.seconds + (130, 3813) + >>> duration.total_seconds() + 11235813.0 .. attribute:: timedelta.microseconds @@ -351,7 +365,7 @@ Supported operations: | | same value. (2) | +--------------------------------+-----------------------------------------------+ | ``-t1`` | Equivalent to ``timedelta(-t1.days, | -| | -t1.seconds*, -t1.microseconds)``, | +| | -t1.seconds, -t1.microseconds)``, | | | and to ``t1 * -1``. (1)(4) | +--------------------------------+-----------------------------------------------+ | ``abs(t)`` | Equivalent to ``+t`` when ``t.days >= 0``, | From 72bbebdfd9263dcf11423518aaeb61128dc4b43f Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Wed, 2 Oct 2024 11:07:38 +0200 Subject: [PATCH 005/269] [3.12] gh-58282: Fix support of tuple metavar for positional arguments in argparse (GH-124782) (GH-124881) Previously, formatting help output or error message for positional argument with a tuple metavar raised exception. (cherry picked from commit 9b31a2d83fa7cb0fe4d75ce7cf6a2c9ea2ce0728) Co-authored-by: Serhiy Storchaka Co-authored-by: Cyker Way --- Lib/argparse.py | 13 ++- Lib/test/test_argparse.py | 98 ++++++++++++++++++- .../2018-12-04-07-36-27.bpo-14074.fMLKCu.rst | 2 + 3 files changed, 109 insertions(+), 4 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2018-12-04-07-36-27.bpo-14074.fMLKCu.rst diff --git a/Lib/argparse.py b/Lib/argparse.py index 3d01415fcf2742..bd5c757197fcf8 100644 --- a/Lib/argparse.py +++ b/Lib/argparse.py @@ -564,8 +564,7 @@ def _format_action(self, action): def _format_action_invocation(self, action): if not action.option_strings: default = self._get_default_metavar_for_positional(action) - metavar, = self._metavar_formatter(action, default)(1) - return metavar + return ' '.join(self._metavar_formatter(action, default)(1)) else: parts = [] @@ -752,7 +751,15 @@ def _get_action_name(argument): elif argument.option_strings: return '/'.join(argument.option_strings) elif argument.metavar not in (None, SUPPRESS): - return argument.metavar + metavar = argument.metavar + if not isinstance(metavar, tuple): + return metavar + if argument.nargs == ZERO_OR_MORE and len(metavar) == 2: + return '%s[, %s]' % metavar + elif argument.nargs == ONE_OR_MORE: + return '%s[, %s]' % metavar + else: + return ', '.join(metavar) elif argument.dest not in (None, SUPPRESS): return argument.dest elif argument.choices: diff --git a/Lib/test/test_argparse.py b/Lib/test/test_argparse.py index cecba6872d2907..1ba958c10addfa 100644 --- a/Lib/test/test_argparse.py +++ b/Lib/test/test_argparse.py @@ -4674,7 +4674,7 @@ class TestHelpNone(HelpTestCase): version = '' -class TestHelpTupleMetavar(HelpTestCase): +class TestHelpTupleMetavarOptional(HelpTestCase): """Test specifying metavar as a tuple""" parser_signature = Sig(prog='PROG') @@ -4701,6 +4701,34 @@ class TestHelpTupleMetavar(HelpTestCase): version = '' +class TestHelpTupleMetavarPositional(HelpTestCase): + """Test specifying metavar on a Positional as a tuple""" + + parser_signature = Sig(prog='PROG') + argument_signatures = [ + Sig('w', help='w help', nargs='+', metavar=('W1', 'W2')), + Sig('x', help='x help', nargs='*', metavar=('X1', 'X2')), + Sig('y', help='y help', nargs=3, metavar=('Y1', 'Y2', 'Y3')), + Sig('z', help='z help', nargs='?', metavar=('Z1',)), + ] + argument_group_signatures = [] + usage = '''\ + usage: PROG [-h] W1 [W2 ...] [X1 [X2 ...]] Y1 Y2 Y3 [Z1] + ''' + help = usage + '''\ + + positional arguments: + W1 W2 w help + X1 X2 x help + Y1 Y2 Y3 y help + Z1 z help + + options: + -h, --help show this help message and exit + ''' + version = '' + + class TestHelpRawText(HelpTestCase): """Test the RawTextHelpFormatter""" @@ -6146,6 +6174,27 @@ def test_required_args(self): 'the following arguments are required: bar, baz$', self.parser.parse_args, []) + def test_required_args_with_metavar(self): + self.parser.add_argument('bar') + self.parser.add_argument('baz', metavar='BaZ') + self.assertRaisesRegex(argparse.ArgumentError, + 'the following arguments are required: bar, BaZ$', + self.parser.parse_args, []) + + def test_required_args_n(self): + self.parser.add_argument('bar') + self.parser.add_argument('baz', nargs=3) + self.assertRaisesRegex(argparse.ArgumentError, + 'the following arguments are required: bar, baz$', + self.parser.parse_args, []) + + def test_required_args_n_with_metavar(self): + self.parser.add_argument('bar') + self.parser.add_argument('baz', nargs=3, metavar=('B', 'A', 'Z')) + self.assertRaisesRegex(argparse.ArgumentError, + 'the following arguments are required: bar, B, A, Z$', + self.parser.parse_args, []) + def test_required_args_optional(self): self.parser.add_argument('bar') self.parser.add_argument('baz', nargs='?') @@ -6160,6 +6209,20 @@ def test_required_args_zero_or_more(self): 'the following arguments are required: bar$', self.parser.parse_args, []) + def test_required_args_one_or_more(self): + self.parser.add_argument('bar') + self.parser.add_argument('baz', nargs='+') + self.assertRaisesRegex(argparse.ArgumentError, + 'the following arguments are required: bar, baz$', + self.parser.parse_args, []) + + def test_required_args_one_or_more_with_metavar(self): + self.parser.add_argument('bar') + self.parser.add_argument('baz', nargs='+', metavar=('BaZ1', 'BaZ2')) + self.assertRaisesRegex(argparse.ArgumentError, + r'the following arguments are required: bar, BaZ1\[, BaZ2]$', + self.parser.parse_args, []) + def test_required_args_remainder(self): self.parser.add_argument('bar') self.parser.add_argument('baz', nargs='...') @@ -6175,6 +6238,39 @@ def test_required_mutually_exclusive_args(self): 'one of the arguments --bar --baz is required', self.parser.parse_args, []) + def test_conflicting_mutually_exclusive_args_optional_with_metavar(self): + group = self.parser.add_mutually_exclusive_group() + group.add_argument('--bar') + group.add_argument('baz', nargs='?', metavar='BaZ') + self.assertRaisesRegex(argparse.ArgumentError, + 'argument BaZ: not allowed with argument --bar$', + self.parser.parse_args, ['--bar', 'a', 'b']) + self.assertRaisesRegex(argparse.ArgumentError, + 'argument --bar: not allowed with argument BaZ$', + self.parser.parse_args, ['a', '--bar', 'b']) + + def test_conflicting_mutually_exclusive_args_zero_or_more_with_metavar1(self): + group = self.parser.add_mutually_exclusive_group() + group.add_argument('--bar') + group.add_argument('baz', nargs='*', metavar=('BAZ1',)) + self.assertRaisesRegex(argparse.ArgumentError, + 'argument BAZ1: not allowed with argument --bar$', + self.parser.parse_args, ['--bar', 'a', 'b']) + self.assertRaisesRegex(argparse.ArgumentError, + 'argument --bar: not allowed with argument BAZ1$', + self.parser.parse_args, ['a', '--bar', 'b']) + + def test_conflicting_mutually_exclusive_args_zero_or_more_with_metavar2(self): + group = self.parser.add_mutually_exclusive_group() + group.add_argument('--bar') + group.add_argument('baz', nargs='*', metavar=('BAZ1', 'BAZ2')) + self.assertRaisesRegex(argparse.ArgumentError, + r'argument BAZ1\[, BAZ2]: not allowed with argument --bar$', + self.parser.parse_args, ['--bar', 'a', 'b']) + self.assertRaisesRegex(argparse.ArgumentError, + r'argument --bar: not allowed with argument BAZ1\[, BAZ2]$', + self.parser.parse_args, ['a', '--bar', 'b']) + def test_ambiguous_option(self): self.parser.add_argument('--foobaz') self.parser.add_argument('--fooble', action='store_true') diff --git a/Misc/NEWS.d/next/Library/2018-12-04-07-36-27.bpo-14074.fMLKCu.rst b/Misc/NEWS.d/next/Library/2018-12-04-07-36-27.bpo-14074.fMLKCu.rst new file mode 100644 index 00000000000000..221c8e05fa98aa --- /dev/null +++ b/Misc/NEWS.d/next/Library/2018-12-04-07-36-27.bpo-14074.fMLKCu.rst @@ -0,0 +1,2 @@ +Fix :mod:`argparse` metavar processing to allow positional arguments to have a +tuple metavar. From cceb25c5033bce5d19311d63f6943215a1a27283 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Wed, 2 Oct 2024 12:36:54 +0200 Subject: [PATCH 006/269] [3.12] gh-122864: Fix a ``test_funcattrs.test___builtins__`` when executing directly (GH-124845) (#124885) gh-122864: Fix a ``test_funcattrs.test___builtins__`` when executing directly (GH-124845) Previously when executing ``test_functattrs.test___builtins__`` directly, it failed because the fact, that ``__builtins__`` is refers to the built-in module ``builtins`` while it's expects a ``__builtins__.__dict__``. But when this test is being run from another module, then ``__builtins__`` is refers to ``builtins.__dict__``. Now this part of the behaviour is covered. --------- (cherry picked from commit 8fbf10d6cfd9c69ffcc1f80fa0c5f33785197af7) Co-authored-by: Mark Byrne <31762852+mbyrnepr2@users.noreply.github.com> Co-authored-by: Victor Stinner --- Lib/test/test_funcattrs.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_funcattrs.py b/Lib/test/test_funcattrs.py index 35b473d5e9a0b4..7ab1b0b5f7014c 100644 --- a/Lib/test/test_funcattrs.py +++ b/Lib/test/test_funcattrs.py @@ -76,7 +76,12 @@ def test___globals__(self): (AttributeError, TypeError)) def test___builtins__(self): - self.assertIs(self.b.__builtins__, __builtins__) + if __name__ == "__main__": + builtins_dict = __builtins__.__dict__ + else: + builtins_dict = __builtins__ + + self.assertIs(self.b.__builtins__, builtins_dict) self.cannot_set_attr(self.b, '__builtins__', 2, (AttributeError, TypeError)) @@ -86,7 +91,7 @@ def func(s): return len(s) ns = {} func2 = type(func)(func.__code__, ns) self.assertIs(func2.__globals__, ns) - self.assertIs(func2.__builtins__, __builtins__) + self.assertIs(func2.__builtins__, builtins_dict) # Make sure that the function actually works. self.assertEqual(func2("abc"), 3) From 6660d2974558d2b01b611e8954f9013448d29f0c Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Wed, 2 Oct 2024 16:31:20 +0200 Subject: [PATCH 007/269] [3.12] gh-85935: Improve tests for invalid arguments in test_argparse (GH-124891) (GH-124898) Check also specific error messages. (cherry picked from commit 2c050d4bc28bffd2990b5a0bd03fb6fc56b13656) Co-authored-by: Serhiy Storchaka --- Lib/test/test_argparse.py | 77 +++++++++++++++++++++------------------ 1 file changed, 42 insertions(+), 35 deletions(-) diff --git a/Lib/test/test_argparse.py b/Lib/test/test_argparse.py index 1ba958c10addfa..13d15fbf075861 100644 --- a/Lib/test/test_argparse.py +++ b/Lib/test/test_argparse.py @@ -5069,15 +5069,15 @@ def custom_formatter(prog): class TestInvalidArgumentConstructors(TestCase): """Test a bunch of invalid Argument constructors""" - def assertTypeError(self, *args, **kwargs): + def assertTypeError(self, *args, errmsg=None, **kwargs): parser = argparse.ArgumentParser() - self.assertRaises(TypeError, parser.add_argument, - *args, **kwargs) + self.assertRaisesRegex(TypeError, errmsg, parser.add_argument, + *args, **kwargs) - def assertValueError(self, *args, **kwargs): + def assertValueError(self, *args, errmsg=None, **kwargs): parser = argparse.ArgumentParser() - self.assertRaises(ValueError, parser.add_argument, - *args, **kwargs) + self.assertRaisesRegex(ValueError, errmsg, parser.add_argument, + *args, **kwargs) def test_invalid_keyword_arguments(self): self.assertTypeError('-x', bar=None) @@ -5087,8 +5087,9 @@ def test_invalid_keyword_arguments(self): def test_missing_destination(self): self.assertTypeError() - for action in ['append', 'store']: - self.assertTypeError(action=action) + for action in ['store', 'append', 'extend']: + with self.subTest(action=action): + self.assertTypeError(action=action) def test_invalid_option_strings(self): self.assertValueError('--') @@ -5102,10 +5103,8 @@ def test_invalid_action(self): self.assertValueError('-x', action='foo') self.assertValueError('foo', action='baz') self.assertValueError('--foo', action=('store', 'append')) - parser = argparse.ArgumentParser() - with self.assertRaises(ValueError) as cm: - parser.add_argument("--foo", action="store-true") - self.assertIn('unknown action', str(cm.exception)) + self.assertValueError('--foo', action="store-true", + errmsg='unknown action') def test_multiple_dest(self): parser = argparse.ArgumentParser() @@ -5118,39 +5117,47 @@ def test_multiple_dest(self): def test_no_argument_actions(self): for action in ['store_const', 'store_true', 'store_false', 'append_const', 'count']: - for attrs in [dict(type=int), dict(nargs='+'), - dict(choices=['a', 'b'])]: - self.assertTypeError('-x', action=action, **attrs) + with self.subTest(action=action): + for attrs in [dict(type=int), dict(nargs='+'), + dict(choices=['a', 'b'])]: + with self.subTest(attrs=attrs): + self.assertTypeError('-x', action=action, **attrs) + self.assertTypeError('x', action=action, **attrs) + self.assertTypeError('-x', action=action, nargs=0) + self.assertTypeError('x', action=action, nargs=0) def test_no_argument_no_const_actions(self): # options with zero arguments for action in ['store_true', 'store_false', 'count']: + with self.subTest(action=action): + # const is always disallowed + self.assertTypeError('-x', const='foo', action=action) - # const is always disallowed - self.assertTypeError('-x', const='foo', action=action) - - # nargs is always disallowed - self.assertTypeError('-x', nargs='*', action=action) + # nargs is always disallowed + self.assertTypeError('-x', nargs='*', action=action) def test_more_than_one_argument_actions(self): - for action in ['store', 'append']: - - # nargs=0 is disallowed - self.assertValueError('-x', nargs=0, action=action) - self.assertValueError('spam', nargs=0, action=action) - - # const is disallowed with non-optional arguments - for nargs in [1, '*', '+']: - self.assertValueError('-x', const='foo', - nargs=nargs, action=action) - self.assertValueError('spam', const='foo', - nargs=nargs, action=action) + for action in ['store', 'append', 'extend']: + with self.subTest(action=action): + # nargs=0 is disallowed + action_name = 'append' if action == 'extend' else action + self.assertValueError('-x', nargs=0, action=action, + errmsg=f'nargs for {action_name} actions must be != 0') + self.assertValueError('spam', nargs=0, action=action, + errmsg=f'nargs for {action_name} actions must be != 0') + + # const is disallowed with non-optional arguments + for nargs in [1, '*', '+']: + self.assertValueError('-x', const='foo', + nargs=nargs, action=action) + self.assertValueError('spam', const='foo', + nargs=nargs, action=action) def test_required_const_actions(self): for action in ['store_const', 'append_const']: - - # nargs is always disallowed - self.assertTypeError('-x', nargs='+', action=action) + with self.subTest(action=action): + # nargs is always disallowed + self.assertTypeError('-x', nargs='+', action=action) def test_parsers_action_missing_params(self): self.assertTypeError('command', action='parsers') From 829cdf06d4f40b0774a5a13f9e6023f3e29bf44d Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Wed, 2 Oct 2024 21:02:14 +0300 Subject: [PATCH 008/269] [3.12] gh-123978: Remove broken time.thread_time() on NetBSD (GH-124116) (GH-124427) (cherry picked from commit e670a113b5e1fcc3ce3bb9b5b4b1f126264ae21b) --- ...-09-16-12-31-48.gh-issue-123978.z3smEu.rst | 1 + Modules/timemodule.c | 19 ++++++++++++++++--- 2 files changed, 17 insertions(+), 3 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-09-16-12-31-48.gh-issue-123978.z3smEu.rst diff --git a/Misc/NEWS.d/next/Library/2024-09-16-12-31-48.gh-issue-123978.z3smEu.rst b/Misc/NEWS.d/next/Library/2024-09-16-12-31-48.gh-issue-123978.z3smEu.rst new file mode 100644 index 00000000000000..e5b3229122b509 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-09-16-12-31-48.gh-issue-123978.z3smEu.rst @@ -0,0 +1 @@ +Remove broken :func:`time.thread_time` and :func:`time.thread_time_ns` on NetBSD. diff --git a/Modules/timemodule.c b/Modules/timemodule.c index 8613fccfe02249..b10f8c7c939550 100644 --- a/Modules/timemodule.c +++ b/Modules/timemodule.c @@ -1292,8 +1292,14 @@ _PyTime_GetProcessTimeWithInfo(_PyTime_t *tp, _Py_clock_info_t *info) #else /* clock_gettime */ +// gh-115714: Don't use CLOCK_PROCESS_CPUTIME_ID on WASI. +/* CLOCK_PROF is defined on NetBSD, but not supported. + * CLOCK_PROCESS_CPUTIME_ID is broken on NetBSD for the same reason as + * CLOCK_THREAD_CPUTIME_ID (see comment below). + */ #if defined(HAVE_CLOCK_GETTIME) \ - && (defined(CLOCK_PROCESS_CPUTIME_ID) || defined(CLOCK_PROF)) + && (defined(CLOCK_PROCESS_CPUTIME_ID) || defined(CLOCK_PROF)) \ + && !defined(__NetBSD__) struct timespec ts; if (HAVE_CLOCK_GETTIME_RUNTIME) { @@ -1499,9 +1505,16 @@ _PyTime_GetThreadTimeWithInfo(_PyTime_t *tp, _Py_clock_info_t *info) return 0; } +/* CLOCK_THREAD_CPUTIME_ID is broken on NetBSD: the result of clock_gettime() + * includes the sleeping time, that defeats the purpose of the clock. + * Also, clock_getres() does not support it. + * https://github.com/python/cpython/issues/123978 + * https://gnats.netbsd.org/57512 + */ #elif defined(HAVE_CLOCK_GETTIME) && \ - defined(CLOCK_PROCESS_CPUTIME_ID) && \ - !defined(__EMSCRIPTEN__) && !defined(__wasi__) + defined(CLOCK_THREAD_CPUTIME_ID) && \ + !defined(__EMSCRIPTEN__) && !defined(__wasi__) && \ + !defined(__NetBSD__) #define HAVE_THREAD_TIME #if defined(__APPLE__) && defined(__has_attribute) && __has_attribute(availability) From 8dce4918ab3ad6c6faa25f9e3fbbfe022b4882b5 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Wed, 2 Oct 2024 17:40:49 -0400 Subject: [PATCH 009/269] [3.12] gh-120378: Fix crash caused by integer overflow in `curses` (GH-124555) (#124911) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is actually an upstream problem in curses, and has been reported to them already: https://lists.gnu.org/archive/html/bug-ncurses/2024-09/msg00101.html This is a nice workaround in the meantime to prevent the segfault. (cherry picked from commit c2ba931318280796a6dcc33d1a5c5c02ad4d035b) Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> --- Lib/test/test_curses.py | 16 +++ ...-09-25-18-07-51.gh-issue-120378.NlBSz_.rst | 2 + Modules/_cursesmodule.c | 16 +-- Modules/clinic/_cursesmodule.c.h | 98 +++++++++++++++---- 4 files changed, 105 insertions(+), 27 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-09-25-18-07-51.gh-issue-120378.NlBSz_.rst diff --git a/Lib/test/test_curses.py b/Lib/test/test_curses.py index 83d10dd8579074..cc3aa561cd4c42 100644 --- a/Lib/test/test_curses.py +++ b/Lib/test/test_curses.py @@ -1081,6 +1081,14 @@ def test_resize_term(self): self.assertEqual(curses.LINES, lines) self.assertEqual(curses.COLS, cols) + with self.assertRaises(OverflowError): + curses.resize_term(35000, 1) + with self.assertRaises(OverflowError): + curses.resize_term(1, 35000) + # GH-120378: Overflow failure in resize_term() causes refresh to fail + tmp = curses.initscr() + tmp.erase() + @requires_curses_func('resizeterm') def test_resizeterm(self): curses.update_lines_cols() @@ -1095,6 +1103,14 @@ def test_resizeterm(self): self.assertEqual(curses.LINES, lines) self.assertEqual(curses.COLS, cols) + with self.assertRaises(OverflowError): + curses.resizeterm(35000, 1) + with self.assertRaises(OverflowError): + curses.resizeterm(1, 35000) + # GH-120378: Overflow failure in resizeterm() causes refresh to fail + tmp = curses.initscr() + tmp.erase() + def test_ungetch(self): curses.ungetch(b'A') self.assertEqual(self.stdscr.getkey(), 'A') diff --git a/Misc/NEWS.d/next/Library/2024-09-25-18-07-51.gh-issue-120378.NlBSz_.rst b/Misc/NEWS.d/next/Library/2024-09-25-18-07-51.gh-issue-120378.NlBSz_.rst new file mode 100644 index 00000000000000..1a8c1427b6b9b9 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-09-25-18-07-51.gh-issue-120378.NlBSz_.rst @@ -0,0 +1,2 @@ +Fix a crash related to an integer overflow in :func:`curses.resizeterm` +and :func:`curses.resize_term`. diff --git a/Modules/_cursesmodule.c b/Modules/_cursesmodule.c index 743b9e37fbcb04..b099959ea409b6 100644 --- a/Modules/_cursesmodule.c +++ b/Modules/_cursesmodule.c @@ -4071,9 +4071,9 @@ NoArgNoReturnFunctionBody(resetty) /*[clinic input] _curses.resizeterm - nlines: int + nlines: short Height. - ncols: int + ncols: short Width. / @@ -4084,8 +4084,8 @@ window dimensions (in particular the SIGWINCH handler). [clinic start generated code]*/ static PyObject * -_curses_resizeterm_impl(PyObject *module, int nlines, int ncols) -/*[clinic end generated code: output=56d6bcc5194ad055 input=0fca02ebad5ffa82]*/ +_curses_resizeterm_impl(PyObject *module, short nlines, short ncols) +/*[clinic end generated code: output=4de3abab50c67f02 input=414e92a63e3e9899]*/ { PyObject *result; @@ -4107,9 +4107,9 @@ _curses_resizeterm_impl(PyObject *module, int nlines, int ncols) /*[clinic input] _curses.resize_term - nlines: int + nlines: short Height. - ncols: int + ncols: short Width. / @@ -4123,8 +4123,8 @@ without additional interaction with the application. [clinic start generated code]*/ static PyObject * -_curses_resize_term_impl(PyObject *module, int nlines, int ncols) -/*[clinic end generated code: output=9e26d8b9ea311ed2 input=2197edd05b049ed4]*/ +_curses_resize_term_impl(PyObject *module, short nlines, short ncols) +/*[clinic end generated code: output=46c6d749fa291dbd input=276afa43d8ea7091]*/ { PyObject *result; diff --git a/Modules/clinic/_cursesmodule.c.h b/Modules/clinic/_cursesmodule.c.h index 9d99d41af5d2d9..1d716bec898d0a 100644 --- a/Modules/clinic/_cursesmodule.c.h +++ b/Modules/clinic/_cursesmodule.c.h @@ -3688,25 +3688,55 @@ PyDoc_STRVAR(_curses_resizeterm__doc__, {"resizeterm", _PyCFunction_CAST(_curses_resizeterm), METH_FASTCALL, _curses_resizeterm__doc__}, static PyObject * -_curses_resizeterm_impl(PyObject *module, int nlines, int ncols); +_curses_resizeterm_impl(PyObject *module, short nlines, short ncols); static PyObject * _curses_resizeterm(PyObject *module, PyObject *const *args, Py_ssize_t nargs) { PyObject *return_value = NULL; - int nlines; - int ncols; + short nlines; + short ncols; if (!_PyArg_CheckPositional("resizeterm", nargs, 2, 2)) { goto exit; } - nlines = _PyLong_AsInt(args[0]); - if (nlines == -1 && PyErr_Occurred()) { - goto exit; + { + long ival = PyLong_AsLong(args[0]); + if (ival == -1 && PyErr_Occurred()) { + goto exit; + } + else if (ival < SHRT_MIN) { + PyErr_SetString(PyExc_OverflowError, + "signed short integer is less than minimum"); + goto exit; + } + else if (ival > SHRT_MAX) { + PyErr_SetString(PyExc_OverflowError, + "signed short integer is greater than maximum"); + goto exit; + } + else { + nlines = (short) ival; + } } - ncols = _PyLong_AsInt(args[1]); - if (ncols == -1 && PyErr_Occurred()) { - goto exit; + { + long ival = PyLong_AsLong(args[1]); + if (ival == -1 && PyErr_Occurred()) { + goto exit; + } + else if (ival < SHRT_MIN) { + PyErr_SetString(PyExc_OverflowError, + "signed short integer is less than minimum"); + goto exit; + } + else if (ival > SHRT_MAX) { + PyErr_SetString(PyExc_OverflowError, + "signed short integer is greater than maximum"); + goto exit; + } + else { + ncols = (short) ival; + } } return_value = _curses_resizeterm_impl(module, nlines, ncols); @@ -3739,25 +3769,55 @@ PyDoc_STRVAR(_curses_resize_term__doc__, {"resize_term", _PyCFunction_CAST(_curses_resize_term), METH_FASTCALL, _curses_resize_term__doc__}, static PyObject * -_curses_resize_term_impl(PyObject *module, int nlines, int ncols); +_curses_resize_term_impl(PyObject *module, short nlines, short ncols); static PyObject * _curses_resize_term(PyObject *module, PyObject *const *args, Py_ssize_t nargs) { PyObject *return_value = NULL; - int nlines; - int ncols; + short nlines; + short ncols; if (!_PyArg_CheckPositional("resize_term", nargs, 2, 2)) { goto exit; } - nlines = _PyLong_AsInt(args[0]); - if (nlines == -1 && PyErr_Occurred()) { - goto exit; + { + long ival = PyLong_AsLong(args[0]); + if (ival == -1 && PyErr_Occurred()) { + goto exit; + } + else if (ival < SHRT_MIN) { + PyErr_SetString(PyExc_OverflowError, + "signed short integer is less than minimum"); + goto exit; + } + else if (ival > SHRT_MAX) { + PyErr_SetString(PyExc_OverflowError, + "signed short integer is greater than maximum"); + goto exit; + } + else { + nlines = (short) ival; + } } - ncols = _PyLong_AsInt(args[1]); - if (ncols == -1 && PyErr_Occurred()) { - goto exit; + { + long ival = PyLong_AsLong(args[1]); + if (ival == -1 && PyErr_Occurred()) { + goto exit; + } + else if (ival < SHRT_MIN) { + PyErr_SetString(PyExc_OverflowError, + "signed short integer is less than minimum"); + goto exit; + } + else if (ival > SHRT_MAX) { + PyErr_SetString(PyExc_OverflowError, + "signed short integer is greater than maximum"); + goto exit; + } + else { + ncols = (short) ival; + } } return_value = _curses_resize_term_impl(module, nlines, ncols); @@ -4313,4 +4373,4 @@ _curses_has_extended_color_support(PyObject *module, PyObject *Py_UNUSED(ignored #ifndef _CURSES_USE_DEFAULT_COLORS_METHODDEF #define _CURSES_USE_DEFAULT_COLORS_METHODDEF #endif /* !defined(_CURSES_USE_DEFAULT_COLORS_METHODDEF) */ -/*[clinic end generated code: output=27a2364193b503c1 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=764ee4c154c6d4a8 input=a9049054013a1b77]*/ From c13bb996f63f042ce00292e741e7a5a870a49da3 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Thu, 3 Oct 2024 10:31:54 +0200 Subject: [PATCH 010/269] [3.12] gh-121982: ``csv``: Add a test case for invalid ``quoting`` constant. (GH-121983) (#124925) gh-121982: ``csv``: Add a test case for invalid ``quoting`` constant. (GH-121983) Test invalid quoting constant (cherry picked from commit 656b7a3c83c79f99beac950b59c47575562ea729) Co-authored-by: Tomas R Co-authored-by: Kirill Podoprigora --- Lib/test/test_csv.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Lib/test/test_csv.py b/Lib/test/test_csv.py index 57db59185d97f7..47ff707ffe6230 100644 --- a/Lib/test/test_csv.py +++ b/Lib/test/test_csv.py @@ -1071,6 +1071,12 @@ class mydialect(csv.Dialect): mydialect.quoting = None self.assertRaises(csv.Error, mydialect) + mydialect.quoting = 42 + with self.assertRaises(csv.Error) as cm: + mydialect() + self.assertEqual(str(cm.exception), + 'bad "quoting" value') + mydialect.doublequote = True mydialect.quoting = csv.QUOTE_ALL mydialect.quotechar = '"' From 58f7763b88d4e81662b9d212f6beddcb42437fe3 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Thu, 3 Oct 2024 12:45:25 +0200 Subject: [PATCH 011/269] [3.12] gh-115145: Update documentation about ``PyThreadState_DeleteCurrent`` (gh-124920) (gh-124931) gh-115145: Update documentation about ``PyThreadState_DeleteCurrent`` (gh-124920) (cherry picked from commit 9eeb21bf761070649bf8d78976a62dabb6d67a99) Co-authored-by: Donghee Na --- Doc/c-api/init.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/c-api/init.rst b/Doc/c-api/init.rst index 8b7b28ae319200..bec075038f06e0 100644 --- a/Doc/c-api/init.rst +++ b/Doc/c-api/init.rst @@ -1269,7 +1269,7 @@ All of the following functions must be called after :c:func:`Py_Initialize`. .. c:function:: void PyThreadState_DeleteCurrent(void) Destroy the current thread state and release the global interpreter lock. - Like :c:func:`PyThreadState_Delete`, the global interpreter lock need not + Like :c:func:`PyThreadState_Delete`, the global interpreter lock must be held. The thread state must have been reset with a previous call to :c:func:`PyThreadState_Clear`. From b3e2c0291595edddc968680689bec7707d27d2d1 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Fri, 4 Oct 2024 16:50:34 +0200 Subject: [PATCH 012/269] [3.12] gh-113993: For string interning, do not rely on (or assert) _Py_IsImmortal (GH-121358) (GH-124938) gh-113993: For string interning, do not rely on (or assert) _Py_IsImmortal (GH-121358) Older stable ABI extensions are allowed to make immortal objects mortal. Instead, use `_PyUnicode_STATE` (`interned` and `statically_allocated`). (cherry picked from commit 956270d08d5c23f59937e2f29f8e0b7f63d68afd) Co-authored-by: Miss Islington (bot) <31488909+miss-islington@users.noreply.github.com> --- ...024-07-04-13-23-27.gh-issue-113601.K3RLqp.rst | 2 ++ Objects/unicodeobject.c | 16 ++++++++++------ 2 files changed, 12 insertions(+), 6 deletions(-) create mode 100644 Misc/NEWS.d/next/C API/2024-07-04-13-23-27.gh-issue-113601.K3RLqp.rst diff --git a/Misc/NEWS.d/next/C API/2024-07-04-13-23-27.gh-issue-113601.K3RLqp.rst b/Misc/NEWS.d/next/C API/2024-07-04-13-23-27.gh-issue-113601.K3RLqp.rst new file mode 100644 index 00000000000000..009cc2bf017180 --- /dev/null +++ b/Misc/NEWS.d/next/C API/2024-07-04-13-23-27.gh-issue-113601.K3RLqp.rst @@ -0,0 +1,2 @@ +Removed debug build assertions related to interning strings, which were +falsely triggered by stable ABI extensions. diff --git a/Objects/unicodeobject.c b/Objects/unicodeobject.c index 815747e1b1ed9c..7bd4b221c83cf7 100644 --- a/Objects/unicodeobject.c +++ b/Objects/unicodeobject.c @@ -257,7 +257,8 @@ _PyUnicode_InternedSize_Immortal(void) // value, to help detect bugs in optimizations. while (PyDict_Next(dict, &pos, &key, &value)) { - if (_Py_IsImmortal(key)) { + assert(PyUnicode_CHECK_INTERNED(key) != SSTATE_INTERNED_IMMORTAL_STATIC); + if (PyUnicode_CHECK_INTERNED(key) == SSTATE_INTERNED_IMMORTAL) { count++; } } @@ -691,10 +692,14 @@ _PyUnicode_CheckConsistency(PyObject *op, int check_content) /* Check interning state */ #ifdef Py_DEBUG + // Note that we do not check `_Py_IsImmortal(op)`, since stable ABI + // extensions can make immortal strings mortal (but with a high enough + // refcount). + // The other way is extremely unlikely (worth a potential failed assertion + // in a debug build), so we do check `!_Py_IsImmortal(op)`. switch (PyUnicode_CHECK_INTERNED(op)) { case SSTATE_NOT_INTERNED: if (ascii->state.statically_allocated) { - CHECK(_Py_IsImmortal(op)); // This state is for two exceptions: // - strings are currently checked before they're interned // - the 256 one-latin1-character strings @@ -710,11 +715,9 @@ _PyUnicode_CheckConsistency(PyObject *op, int check_content) break; case SSTATE_INTERNED_IMMORTAL: CHECK(!ascii->state.statically_allocated); - CHECK(_Py_IsImmortal(op)); break; case SSTATE_INTERNED_IMMORTAL_STATIC: CHECK(ascii->state.statically_allocated); - CHECK(_Py_IsImmortal(op)); break; default: Py_UNREACHABLE(); @@ -1899,7 +1902,8 @@ unicode_write_cstr(PyObject *unicode, Py_ssize_t index, static PyObject* get_latin1_char(Py_UCS1 ch) { - return Py_NewRef(LATIN1(ch)); + PyObject *o = LATIN1(ch); + return o; } static PyObject* @@ -15015,7 +15019,7 @@ intern_common(PyInterpreterState *interp, PyObject *s /* stolen */, { PyObject *r = (PyObject *)_Py_hashtable_get(INTERNED_STRINGS, s); if (r != NULL) { - assert(_Py_IsImmortal(r)); + assert(_PyUnicode_STATE(r).statically_allocated); assert(r != s); // r must be statically_allocated; s is not Py_DECREF(s); return Py_NewRef(r); From db4b382b1faad575a0f630d71462c610661235ea Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Sun, 6 Oct 2024 15:34:24 +0200 Subject: [PATCH 013/269] [3.12] gh-125008: Fix `tokenize.untokenize` roundtrip for `\n{{` (GH-125013) (#125021) --- Lib/test/test_tokenize.py | 20 +++++++++++++++++++ Lib/tokenize.py | 2 +- ...-10-05-23-53-06.gh-issue-125008.ETANpd.rst | 2 ++ 3 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2024-10-05-23-53-06.gh-issue-125008.ETANpd.rst diff --git a/Lib/test/test_tokenize.py b/Lib/test/test_tokenize.py index 84741e308bffae..2c4e7b960f7273 100644 --- a/Lib/test/test_tokenize.py +++ b/Lib/test/test_tokenize.py @@ -1916,6 +1916,26 @@ def test_roundtrip(self): self.check_roundtrip(r"f'\\\\N{{'") self.check_roundtrip(r"f'\\\\\\N{{'") self.check_roundtrip(r"f'\\\\\\\\N{{'") + + self.check_roundtrip(r"f'\n{{foo}}'") + self.check_roundtrip(r"f'\\n{{foo}}'") + self.check_roundtrip(r"f'\\\n{{foo}}'") + self.check_roundtrip(r"f'\\\\n{{foo}}'") + + self.check_roundtrip(r"f'\t{{foo}}'") + self.check_roundtrip(r"f'\\t{{foo}}'") + self.check_roundtrip(r"f'\\\t{{foo}}'") + self.check_roundtrip(r"f'\\\\t{{foo}}'") + + self.check_roundtrip(r"rf'\t{{foo}}'") + self.check_roundtrip(r"rf'\\t{{foo}}'") + self.check_roundtrip(r"rf'\\\t{{foo}}'") + self.check_roundtrip(r"rf'\\\\t{{foo}}'") + + self.check_roundtrip(r"rf'\{{foo}}'") + self.check_roundtrip(r"f'\\{{foo}}'") + self.check_roundtrip(r"rf'\\\{{foo}}'") + self.check_roundtrip(r"f'\\\\{{foo}}'") cases = [ """ if 1: diff --git a/Lib/tokenize.py b/Lib/tokenize.py index 7af7a5cc1cd680..b2dff8e6967094 100644 --- a/Lib/tokenize.py +++ b/Lib/tokenize.py @@ -202,7 +202,7 @@ def escape_brackets(self, token): characters[-2::-1] ) ) - if n_backslashes % 2 == 0: + if n_backslashes % 2 == 0 or characters[-1] != "N": characters.append(character) else: consume_until_next_bracket = True diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2024-10-05-23-53-06.gh-issue-125008.ETANpd.rst b/Misc/NEWS.d/next/Core_and_Builtins/2024-10-05-23-53-06.gh-issue-125008.ETANpd.rst new file mode 100644 index 00000000000000..8971e052860225 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2024-10-05-23-53-06.gh-issue-125008.ETANpd.rst @@ -0,0 +1,2 @@ +Fix :func:`tokenize.untokenize` producing invalid syntax for +double braces preceded by certain escape characters. From 73d7393780b6d92909782148f50ec03f0db5892d Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+AA-Turner@users.noreply.github.com> Date: Sun, 6 Oct 2024 23:42:11 +0100 Subject: [PATCH 014/269] [3.12] Doc: Simplify the definition of 'soft deprecated' (GH-124988) (#125030) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit (cherry picked from commit feca4cf64e9742b9c002d5533ced47e68b34a880) Co-authored-by: Andrés Delfino Co-authored-by: Sergey B Kirpichev Co-authored-by: Carol Willing --- Doc/glossary.rst | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Doc/glossary.rst b/Doc/glossary.rst index f7870155930793..b233967adc9e6a 100644 --- a/Doc/glossary.rst +++ b/Doc/glossary.rst @@ -1134,6 +1134,17 @@ Glossary when several are given, such as in ``variable_name[1:3:5]``. The bracket (subscript) notation uses :class:`slice` objects internally. + soft deprecated + A soft deprecated API should not be used in new code, + but it is safe for already existing code to use it. + The API remains documented and tested, but will not be enhanced further. + + Soft deprecation, unlike normal deprecation, does not plan on removing the API + and will not emit warnings. + + See `PEP 387: Soft Deprecation + `_. + special method .. index:: pair: special; method From 36f9bbbdab905563ce0ca5e4f2b6bab1af5cb729 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Mon, 7 Oct 2024 01:03:30 +0200 Subject: [PATCH 015/269] [3.12] gh-125025: `_thread` docs: fix/update the *caveats* list (GH-125026) (GH-125031) gh-125025: `_thread` docs: fix/update the *caveats* list (GH-125026) (cherry picked from commit 1e098dc766ba4f29a63da4f188fb214af7623365) Co-authored-by: Jan Kaliszewski --- Doc/library/_thread.rst | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/Doc/library/_thread.rst b/Doc/library/_thread.rst index 35053702e6db2b..d82f63834dd2d1 100644 --- a/Doc/library/_thread.rst +++ b/Doc/library/_thread.rst @@ -210,9 +210,8 @@ In addition to these methods, lock objects can also be used via the .. index:: pair: module; signal -* Threads interact strangely with interrupts: the :exc:`KeyboardInterrupt` - exception will be received by an arbitrary thread. (When the :mod:`signal` - module is available, interrupts always go to the main thread.) +* Interrupts always go to the main thread (the :exc:`KeyboardInterrupt` + exception will be received by that thread.) * Calling :func:`sys.exit` or raising the :exc:`SystemExit` exception is equivalent to calling :func:`_thread.exit`. @@ -226,7 +225,3 @@ In addition to these methods, lock objects can also be used via the :keyword:`try` ... :keyword:`finally` clauses or executing object destructors. -* When the main thread exits, it does not do any of its usual cleanup (except - that :keyword:`try` ... :keyword:`finally` clauses are honored), and the - standard I/O files are not flushed. - From fa32f007f0ee702fbad970961a0258e8a3e92de8 Mon Sep 17 00:00:00 2001 From: Alyssa Coghlan Date: Tue, 8 Oct 2024 00:24:45 +1000 Subject: [PATCH 016/269] [3.12] gh-125018: Add importlib.metadata semantic link targets (GH-125027) (#125048) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This allows direct intersphinx references to APIs via references like `` :func:`importlib.metadata.version` ``. --------- (cherry picked from commit cda3b5a576412a8671bbe4c68bb792ec14f1a4b1) Co-authored-by: Sviatoslav Sydorenko (Святослав Сидоренко) Co-authored-by: Adam Turner <9087854+AA-Turner@users.noreply.github.com> --- Doc/library/importlib.metadata.rst | 188 ++++++++++++++---- ...-10-07-00-31-17.gh-issue-125018.yKnymn.rst | 4 + 2 files changed, 148 insertions(+), 44 deletions(-) create mode 100644 Misc/NEWS.d/next/Documentation/2024-10-07-00-31-17.gh-issue-125018.yKnymn.rst diff --git a/Doc/library/importlib.metadata.rst b/Doc/library/importlib.metadata.rst index 3f407ba48a4d8e..d85b3c68626a89 100644 --- a/Doc/library/importlib.metadata.rst +++ b/Doc/library/importlib.metadata.rst @@ -100,6 +100,13 @@ You can also get a :ref:`distribution's version number `, list its :ref:`requirements`. +.. exception:: PackageNotFoundError + + Subclass of :class:`ModuleNotFoundError` raised by several functions in this + module when queried for a distribution package which is not installed in the + current Python environment. + + Functional API ============== @@ -111,31 +118,53 @@ This package provides the following functionality via its public API. Entry points ------------ -The ``entry_points()`` function returns a collection of entry points. -Entry points are represented by ``EntryPoint`` instances; -each ``EntryPoint`` has a ``.name``, ``.group``, and ``.value`` attributes and -a ``.load()`` method to resolve the value. There are also ``.module``, -``.attr``, and ``.extras`` attributes for getting the components of the -``.value`` attribute. +.. function:: entry_points(**select_params) + + Returns a :class:`EntryPoints` instance describing entry points for the + current environment. Any given keyword parameters are passed to the + :meth:`!~EntryPoints.select` method for comparison to the attributes of + the individual entry point definitions. + + Note: it is not currently possible to query for entry points based on + their :attr:`!EntryPoint.dist` attribute (as different :class:`!Distribution` + instances do not currently compare equal, even if they have the same attributes) + +.. class:: EntryPoints + + Details of a collection of installed entry points. + + Also provides a ``.groups`` attribute that reports all identifed entry + point groups, and a ``.names`` attribute that reports all identified entry + point names. + +.. class:: EntryPoint + + Details of an installed entry point. + + Each :class:`!EntryPoint` instance has ``.name``, ``.group``, and ``.value`` + attributes and a ``.load()`` method to resolve the value. There are also + ``.module``, ``.attr``, and ``.extras`` attributes for getting the + components of the ``.value`` attribute, and ``.dist`` for obtaining + information regarding the distribution package that provides the entry point. Query all entry points:: >>> eps = entry_points() # doctest: +SKIP -The ``entry_points()`` function returns an ``EntryPoints`` object, -a collection of all ``EntryPoint`` objects with ``names`` and ``groups`` +The :func:`!entry_points` function returns a :class:`!EntryPoints` object, +a collection of all :class:`!EntryPoint` objects with ``names`` and ``groups`` attributes for convenience:: >>> sorted(eps.groups) # doctest: +SKIP ['console_scripts', 'distutils.commands', 'distutils.setup_keywords', 'egg_info.writers', 'setuptools.installation'] -``EntryPoints`` has a ``select`` method to select entry points +:class:`!EntryPoints` has a :meth:`!~EntryPoints.select` method to select entry points matching specific properties. Select entry points in the ``console_scripts`` group:: >>> scripts = eps.select(group='console_scripts') # doctest: +SKIP -Equivalently, since ``entry_points`` passes keyword arguments +Equivalently, since :func:`!entry_points` passes keyword arguments through to select:: >>> scripts = entry_points(group='console_scripts') # doctest: +SKIP @@ -187,31 +216,41 @@ for compatibility options. Distribution metadata --------------------- -Every `Distribution Package `_ includes some metadata, -which you can extract using the -``metadata()`` function:: +.. function:: metadata(distribution_name) + + Return the distribution metadata corresponding to the named + distribution package as a :class:`PackageMetadata` instance. + + Raises :exc:`PackageNotFoundError` if the named distribution + package is not installed in the current Python environment. + +.. class:: PackageMetadata + + A concrete implementation of the + `PackageMetadata protocol `_. + + In addition to providing the defined protocol methods and attributes, subscripting + the instance is equivalent to calling the :meth:`!~PackageMetadata.get` method. + +Every `Distribution Package `_ +includes some metadata, which you can extract using the :func:`!metadata` function:: >>> wheel_metadata = metadata('wheel') # doctest: +SKIP -The keys of the returned data structure, a ``PackageMetadata``, -name the metadata keywords, and +The keys of the returned data structure name the metadata keywords, and the values are returned unparsed from the distribution metadata:: >>> wheel_metadata['Requires-Python'] # doctest: +SKIP '>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*' -``PackageMetadata`` also presents a ``json`` attribute that returns +:class:`PackageMetadata` also presents a :attr:`!~PackageMetadata.json` attribute that returns all the metadata in a JSON-compatible form per :PEP:`566`:: >>> wheel_metadata.json['requires_python'] '>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*' -.. note:: - - The actual type of the object returned by ``metadata()`` is an - implementation detail and should be accessed only through the interface - described by the - `PackageMetadata protocol `_. +The full set of available metadata is not described here. +See the PyPA `Core metadata specification `_ for additional details. .. versionchanged:: 3.10 The ``Description`` is now included in the metadata when presented @@ -225,7 +264,15 @@ all the metadata in a JSON-compatible form per :PEP:`566`:: Distribution versions --------------------- -The ``version()`` function is the quickest way to get a +.. function:: version(distribution_name) + + Return the installed distribution package version for the named + distribution package. + + Raises :exc:`PackageNotFoundError` if the named distribution + package is not installed in the current Python environment. + +The :func:`!version` function is the quickest way to get a `Distribution Package `_'s version number, as a string:: @@ -238,12 +285,28 @@ number, as a string:: Distribution files ------------------ -You can also get the full set of files contained within a distribution. The -``files()`` function takes a `Distribution Package `_ name -and returns all of the -files installed by this distribution. Each file object returned is a -``PackagePath``, a :class:`pathlib.PurePath` derived object with additional ``dist``, -``size``, and ``hash`` properties as indicated by the metadata. For example:: +.. function:: files(distribution_name) + + Return the full set of files contained within the named + distribution package. + + Raises :exc:`PackageNotFoundError` if the named distribution + package is not installed in the current Python environment. + + Returns :const:`None` if the distribution is found but the installation + database records reporting the files associated with the distribuion package + are missing. + +.. class:: PackagePath + + A :class:`pathlib.PurePath` derived object with additional ``dist``, + ``size``, and ``hash`` properties corresponding to the distribution + package's installation metadata for that file. + +The :func:`!files` function takes a +`Distribution Package `_ +name and returns all of the files installed by this distribution. Each file is reported +as a :class:`PackagePath` instance. For example:: >>> util = [p for p in files('wheel') if 'util.py' in str(p)][0] # doctest: +SKIP >>> util # doctest: +SKIP @@ -266,16 +329,16 @@ Once you have the file, you can also read its contents:: return s.encode('utf-8') return s -You can also use the ``locate`` method to get a the absolute path to the -file:: +You can also use the :meth:`!~PackagePath.locate` method to get the absolute +path to the file:: >>> util.locate() # doctest: +SKIP PosixPath('/home/gustav/example/lib/site-packages/wheel/util.py') In the case where the metadata file listing files -(RECORD or SOURCES.txt) is missing, ``files()`` will -return ``None``. The caller may wish to wrap calls to -``files()`` in `always_iterable +(``RECORD`` or ``SOURCES.txt``) is missing, :func:`!files` will +return :const:`None`. The caller may wish to wrap calls to +:func:`!files` in `always_iterable `_ or otherwise guard against this condition if the target distribution is not known to have the metadata present. @@ -285,8 +348,16 @@ distribution is not known to have the metadata present. Distribution requirements ------------------------- +.. function:: requires(distribution_name) + + Return the declared dependency specifiers for the named + distribution package. + + Raises :exc:`PackageNotFoundError` if the named distribution + package is not installed in the current Python environment. + To get the full set of requirements for a `Distribution Package `_, -use the ``requires()`` +use the :func:`!requires` function:: >>> requires('wheel') # doctest: +SKIP @@ -299,6 +370,16 @@ function:: Mapping import to distribution packages --------------------------------------- +.. function:: packages_distributions() + + Return a mapping from the top level module and import package + names found via :attr:`sys.meta_path` to the names of the distribution + packages (if any) that provide the corresponding files. + + To allow for namespace packages (which may have members provided by + multiple distribution packages), each top level import name maps to a + list of distribution names rather than mapping directly to a single name. + A convenience method to resolve the `Distribution Package `_ name (or names, in the case of a namespace package) that provide each importable top-level @@ -318,23 +399,42 @@ function is not reliable with such installs. Distributions ============= -While the above API is the most common and convenient usage, you can get all -of that information from the ``Distribution`` class. A ``Distribution`` is an -abstract object that represents the metadata for -a Python `Distribution Package `_. You can -get the ``Distribution`` instance:: +.. function:: distribution(distribution_name) + + Return a :class:`Distribution` instance describing the named + distribution package. + + Raises :exc:`PackageNotFoundError` if the named distribution + package is not installed in the current Python environment. + +.. class:: Distribution + + Details of an installed distribution package. + + Note: different :class:`!Distribution` instances do not currently compare + equal, even if they relate to the same installed distribution and + accordingly have the same attributes. + +While the module level API described above is the most common and convenient usage, +you can get all of that information from the :class:`!Distribution` class. +:class:`!Distribution` is an abstract object that represents the metadata for +a Python `Distribution Package `_. +You can get the concreate :class:`!Distribution` subclass instance for an installed +distribution package by calling the :func:`distribution` function:: >>> from importlib.metadata import distribution # doctest: +SKIP >>> dist = distribution('wheel') # doctest: +SKIP + >>> type(dist) # doctest: +SKIP + Thus, an alternative way to get the version number is through the -``Distribution`` instance:: +:class:`!Distribution` instance:: >>> dist.version # doctest: +SKIP '0.32.3' -There are all kinds of additional metadata available on the ``Distribution`` -instance:: +There are all kinds of additional metadata available on :class:`!Distribution` +instances:: >>> dist.metadata['Requires-Python'] # doctest: +SKIP '>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*' @@ -342,7 +442,7 @@ instance:: 'MIT' The full set of available metadata is not described here. -See the `Core metadata specifications `_ for additional details. +See the PyPA `Core metadata specification `_ for additional details. Distribution Discovery diff --git a/Misc/NEWS.d/next/Documentation/2024-10-07-00-31-17.gh-issue-125018.yKnymn.rst b/Misc/NEWS.d/next/Documentation/2024-10-07-00-31-17.gh-issue-125018.yKnymn.rst new file mode 100644 index 00000000000000..e910da5b879ba5 --- /dev/null +++ b/Misc/NEWS.d/next/Documentation/2024-10-07-00-31-17.gh-issue-125018.yKnymn.rst @@ -0,0 +1,4 @@ +The :mod:`importlib.metadata` documentation now includes semantic +cross-reference targets for the significant documented APIs. This means +intersphinx references like :func:`importlib.metadata.version` will +now work as expected. From d2b307171ce899f6bd17f6ba570f5172ff9a390a Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Mon, 7 Oct 2024 18:58:47 +0200 Subject: [PATCH 017/269] [3.12] gh-125018: Fix role syntax (GH-125050) (#125055) gh-125018: Fix role syntax (GH-125050) (cherry picked from commit 10094a533a947b72d01ed8195dcf540f2e7820ea) Co-authored-by: Adam Turner <9087854+AA-Turner@users.noreply.github.com> --- Doc/library/importlib.metadata.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Doc/library/importlib.metadata.rst b/Doc/library/importlib.metadata.rst index d85b3c68626a89..14414d973f20c1 100644 --- a/Doc/library/importlib.metadata.rst +++ b/Doc/library/importlib.metadata.rst @@ -122,7 +122,7 @@ Entry points Returns a :class:`EntryPoints` instance describing entry points for the current environment. Any given keyword parameters are passed to the - :meth:`!~EntryPoints.select` method for comparison to the attributes of + :meth:`!select` method for comparison to the attributes of the individual entry point definitions. Note: it is not currently possible to query for entry points based on @@ -158,7 +158,7 @@ attributes for convenience:: >>> sorted(eps.groups) # doctest: +SKIP ['console_scripts', 'distutils.commands', 'distutils.setup_keywords', 'egg_info.writers', 'setuptools.installation'] -:class:`!EntryPoints` has a :meth:`!~EntryPoints.select` method to select entry points +:class:`!EntryPoints` has a :meth:`!select` method to select entry points matching specific properties. Select entry points in the ``console_scripts`` group:: @@ -230,7 +230,7 @@ Distribution metadata `PackageMetadata protocol `_. In addition to providing the defined protocol methods and attributes, subscripting - the instance is equivalent to calling the :meth:`!~PackageMetadata.get` method. + the instance is equivalent to calling the :meth:`!get` method. Every `Distribution Package `_ includes some metadata, which you can extract using the :func:`!metadata` function:: @@ -243,7 +243,7 @@ the values are returned unparsed from the distribution metadata:: >>> wheel_metadata['Requires-Python'] # doctest: +SKIP '>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*' -:class:`PackageMetadata` also presents a :attr:`!~PackageMetadata.json` attribute that returns +:class:`PackageMetadata` also presents a :attr:`!json` attribute that returns all the metadata in a JSON-compatible form per :PEP:`566`:: >>> wheel_metadata.json['requires_python'] @@ -329,7 +329,7 @@ Once you have the file, you can also read its contents:: return s.encode('utf-8') return s -You can also use the :meth:`!~PackagePath.locate` method to get the absolute +You can also use the :meth:`!locate` method to get the absolute path to the file:: >>> util.locate() # doctest: +SKIP From a62d2d3318fcd5a9ac5c062360f73e511a4375e1 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Mon, 7 Oct 2024 19:59:31 +0200 Subject: [PATCH 018/269] [3.12] gh-124182: Explain naming rules for struct sequence types (GH-124335) (#125056) gh-124182: Explain naming rules for struct sequence types (GH-124335) (cherry picked from commit 3287c834e5370294e310450115290979aac06efa) Co-authored-by: ffelixg <142172984+ffelixg@users.noreply.github.com> --- Doc/c-api/tuple.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Doc/c-api/tuple.rst b/Doc/c-api/tuple.rst index 82ef4bcd14784d..e20c5a66189be0 100644 --- a/Doc/c-api/tuple.rst +++ b/Doc/c-api/tuple.rst @@ -158,7 +158,8 @@ type. .. c:member:: const char *name - Name of the struct sequence type. + Fully qualified name of the type; null-terminated UTF-8 encoded. + The name must contain the module name. .. c:member:: const char *doc From a01970d6a2add5a07af58b0aa3ca280bad62b9fd Mon Sep 17 00:00:00 2001 From: "T. Wouters" Date: Mon, 7 Oct 2024 11:51:07 -0700 Subject: [PATCH 019/269] [3.12] GH-109975: Announce final release in What's New in Python 3.13 (GH-125007) (#125034) Prepare What's New in Python 3.13 for final release (cherry picked from commit 31516c98dd7097047ba10da8dcf728c3d580f3d6) Co-authored-by: Adam Turner <9087854+AA-Turner@users.noreply.github.com> --- Doc/whatsnew/3.12.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/whatsnew/3.12.rst b/Doc/whatsnew/3.12.rst index 09f47b7041e9e8..5c36e2d4bcfb49 100644 --- a/Doc/whatsnew/3.12.rst +++ b/Doc/whatsnew/3.12.rst @@ -59,7 +59,7 @@ Summary -- Release highlights .. This section singles out the most important changes in Python 3.12. Brevity is key. -Python 3.12 is the latest stable release of the Python programming language, +Python 3.12 is a stable release of the Python programming language, with a mix of changes to the language and the standard library. The library changes focus on cleaning up deprecated APIs, usability, and correctness. Of note, the :mod:`!distutils` package has been removed from the standard library. From 79b4c9d99a070a6cdcb810148e514864d8958bc1 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Mon, 7 Oct 2024 21:23:08 +0200 Subject: [PATCH 020/269] [3.12] gh-122392: IDLE - Fix overlapping lines in browsers (GH-122392) (GH-124975) (#125062) gh-122392: IDLE - Fix overlapping lines in browsers (GH-122392) (GH-124975) Increase currently inadequate vertical spacing for the IDLE browsers (path, module, and stack) on high-resolution monitors. --------- (cherry picked from commit c5df1cb7bde7e86f046196b0e34a0b90f8fc11de) Co-authored-by: Zhikang Yan <2951256653@qq.com> Co-authored-by: Terry Jan Reedy --- Lib/idlelib/tree.py | 14 ++++++++++---- .../2024-10-04-15-34-34.gh-issue-122392.V8K3w2.rst | 2 ++ 2 files changed, 12 insertions(+), 4 deletions(-) create mode 100644 Misc/NEWS.d/next/IDLE/2024-10-04-15-34-34.gh-issue-122392.V8K3w2.rst diff --git a/Lib/idlelib/tree.py b/Lib/idlelib/tree.py index 0726d7e23660f6..182ce7189614da 100644 --- a/Lib/idlelib/tree.py +++ b/Lib/idlelib/tree.py @@ -83,6 +83,8 @@ def wheel_event(event, widget=None): class TreeNode: + dy = 0 + def __init__(self, canvas, parent, item): self.canvas = canvas self.parent = parent @@ -199,23 +201,22 @@ def update(self): def draw(self, x, y): # XXX This hard-codes too many geometry constants! - dy = 20 self.x, self.y = x, y self.drawicon() self.drawtext() if self.state != 'expanded': - return y + dy + return y + TreeNode.dy # draw children if not self.children: sublist = self.item._GetSubList() if not sublist: # _IsExpandable() was mistaken; that's allowed - return y+17 + return y + TreeNode.dy for item in sublist: child = self.__class__(self.canvas, self, item) self.children.append(child) cx = x+20 - cy = y + dy + cy = y + TreeNode.dy cylast = 0 for child in self.children: cylast = cy @@ -289,6 +290,11 @@ def drawtext(self): self.label.bind("", lambda e: wheel_event(e, self.canvas)) self.label.bind("", lambda e: wheel_event(e, self.canvas)) self.text_id = id + if TreeNode.dy == 0: + # The first row doesn't matter what the dy is, just measure its + # size to get the value of the subsequent dy + coords = self.canvas.bbox(id) + TreeNode.dy = max(20, coords[3] - coords[1] - 3) def select_or_edit(self, event=None): if self.selected and self.item.IsEditable(): diff --git a/Misc/NEWS.d/next/IDLE/2024-10-04-15-34-34.gh-issue-122392.V8K3w2.rst b/Misc/NEWS.d/next/IDLE/2024-10-04-15-34-34.gh-issue-122392.V8K3w2.rst new file mode 100644 index 00000000000000..541f6212794ef2 --- /dev/null +++ b/Misc/NEWS.d/next/IDLE/2024-10-04-15-34-34.gh-issue-122392.V8K3w2.rst @@ -0,0 +1,2 @@ +Increase currently inadequate vertical spacing for the IDLE browsers (path, +module, and stack) on high-resolution monitors. From bc237ed9a8d631675a4962d6627d0571dfa4c04f Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Tue, 8 Oct 2024 08:24:09 +0200 Subject: [PATCH 021/269] [3.12] gh-124653: Relax (again) detection of queue API for logging handlers (GH-124897) (GH-125060) (cherry picked from commit 7ffe94fb242fd51bb07c7f0d31e94efeea3619d4) --- Doc/library/logging.config.rst | 11 +- Lib/logging/config.py | 14 +-- Lib/test/test_logging.py | 108 +++++++++++------- ...-10-02-15-05-45.gh-issue-124653.tqsTu9.rst | 2 + 4 files changed, 79 insertions(+), 56 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-10-02-15-05-45.gh-issue-124653.tqsTu9.rst diff --git a/Doc/library/logging.config.rst b/Doc/library/logging.config.rst index 50ffff60250ee0..bc85f4301f9ba0 100644 --- a/Doc/library/logging.config.rst +++ b/Doc/library/logging.config.rst @@ -752,16 +752,17 @@ The ``queue`` and ``listener`` keys are optional. If the ``queue`` key is present, the corresponding value can be one of the following: -* An object implementing the :class:`queue.Queue` public API. For instance, - this may be an actual instance of :class:`queue.Queue` or a subclass thereof, - or a proxy obtained by :meth:`multiprocessing.managers.SyncManager.Queue`. +* An object implementing the :meth:`Queue.put_nowait ` + and :meth:`Queue.get ` public API. For instance, this may be + an actual instance of :class:`queue.Queue` or a subclass thereof, or a proxy + obtained by :meth:`multiprocessing.managers.SyncManager.Queue`. This is of course only possible if you are constructing or modifying the configuration dictionary in code. * A string that resolves to a callable which, when called with no arguments, returns - the :class:`queue.Queue` instance to use. That callable could be a - :class:`queue.Queue` subclass or a function which returns a suitable queue instance, + the queue instance to use. That callable could be a :class:`queue.Queue` subclass + or a function which returns a suitable queue instance, such as ``my.module.queue_factory()``. * A dict with a ``'()'`` key which is constructed in the usual way as discussed in diff --git a/Lib/logging/config.py b/Lib/logging/config.py index ac90b537d8a396..c128bc7a61e34e 100644 --- a/Lib/logging/config.py +++ b/Lib/logging/config.py @@ -502,7 +502,7 @@ def as_tuple(self, value): def _is_queue_like_object(obj): """Check that *obj* implements the Queue API.""" - if isinstance(obj, queue.Queue): + if isinstance(obj, (queue.Queue, queue.SimpleQueue)): return True # defer importing multiprocessing as much as possible from multiprocessing.queues import Queue as MPQueue @@ -519,13 +519,13 @@ def _is_queue_like_object(obj): # Ideally, we would have wanted to simply use strict type checking # instead of a protocol-based type checking since the latter does # not check the method signatures. - queue_interface = [ - 'empty', 'full', 'get', 'get_nowait', - 'put', 'put_nowait', 'join', 'qsize', - 'task_done', - ] + # + # Note that only 'put_nowait' and 'get' are required by the logging + # queue handler and queue listener (see gh-124653) and that other + # methods are either optional or unused. + minimal_queue_interface = ['put_nowait', 'get'] return all(callable(getattr(obj, method, None)) - for method in queue_interface) + for method in minimal_queue_interface) class DictConfigurator(BaseConfigurator): """ diff --git a/Lib/test/test_logging.py b/Lib/test/test_logging.py index 78bcd065ad5d72..5112c2e7d60f0b 100644 --- a/Lib/test/test_logging.py +++ b/Lib/test/test_logging.py @@ -2391,16 +2391,22 @@ def __getattr__(self, attribute): return getattr(queue, attribute) class CustomQueueFakeProtocol(CustomQueueProtocol): - # An object implementing the Queue API (incorrect signatures). + # An object implementing the minimial Queue API for + # the logging module but with incorrect signatures. + # # The object will be considered a valid queue class since we # do not check the signatures (only callability of methods) # but will NOT be usable in production since a TypeError will - # be raised due to a missing argument. - def empty(self, x): + # be raised due to the extra argument in 'put_nowait'. + def put_nowait(self): pass class CustomQueueWrongProtocol(CustomQueueProtocol): - empty = None + put_nowait = None + +class MinimalQueueProtocol: + def put_nowait(self, x): pass + def get(self): pass def queueMaker(): return queue.Queue() @@ -3914,56 +3920,70 @@ def test_config_queue_handler(self): msg = str(ctx.exception) self.assertEqual(msg, "Unable to configure handler 'ah'") + def _apply_simple_queue_listener_configuration(self, qspec): + self.apply_config({ + "version": 1, + "handlers": { + "queue_listener": { + "class": "logging.handlers.QueueHandler", + "queue": qspec, + }, + }, + }) + @threading_helper.requires_working_threading() @support.requires_subprocess() @patch("multiprocessing.Manager") def test_config_queue_handler_does_not_create_multiprocessing_manager(self, manager): - # gh-120868, gh-121723 - - from multiprocessing import Queue as MQ - - q1 = {"()": "queue.Queue", "maxsize": -1} - q2 = MQ() - q3 = queue.Queue() - # CustomQueueFakeProtocol passes the checks but will not be usable - # since the signatures are incompatible. Checking the Queue API - # without testing the type of the actual queue is a trade-off - # between usability and the work we need to do in order to safely - # check that the queue object correctly implements the API. - q4 = CustomQueueFakeProtocol() - - for qspec in (q1, q2, q3, q4): - self.apply_config( - { - "version": 1, - "handlers": { - "queue_listener": { - "class": "logging.handlers.QueueHandler", - "queue": qspec, - }, - }, - } - ) - manager.assert_not_called() + # gh-120868, gh-121723, gh-124653 + + for qspec in [ + {"()": "queue.Queue", "maxsize": -1}, + queue.Queue(), + # queue.SimpleQueue does not inherit from queue.Queue + queue.SimpleQueue(), + # CustomQueueFakeProtocol passes the checks but will not be usable + # since the signatures are incompatible. Checking the Queue API + # without testing the type of the actual queue is a trade-off + # between usability and the work we need to do in order to safely + # check that the queue object correctly implements the API. + CustomQueueFakeProtocol(), + MinimalQueueProtocol(), + ]: + with self.subTest(qspec=qspec): + self._apply_simple_queue_listener_configuration(qspec) + manager.assert_not_called() @patch("multiprocessing.Manager") def test_config_queue_handler_invalid_config_does_not_create_multiprocessing_manager(self, manager): # gh-120868, gh-121723 for qspec in [object(), CustomQueueWrongProtocol()]: - with self.assertRaises(ValueError): - self.apply_config( - { - "version": 1, - "handlers": { - "queue_listener": { - "class": "logging.handlers.QueueHandler", - "queue": qspec, - }, - }, - } - ) - manager.assert_not_called() + with self.subTest(qspec=qspec), self.assertRaises(ValueError): + self._apply_simple_queue_listener_configuration(qspec) + manager.assert_not_called() + + @skip_if_tsan_fork + @support.requires_subprocess() + @unittest.skipUnless(support.Py_DEBUG, "requires a debug build for testing" + " assertions in multiprocessing") + def test_config_reject_simple_queue_handler_multiprocessing_context(self): + # multiprocessing.SimpleQueue does not implement 'put_nowait' + # and thus cannot be used as a queue-like object (gh-124653) + + import multiprocessing + + if support.MS_WINDOWS: + start_methods = ['spawn'] + else: + start_methods = ['spawn', 'fork', 'forkserver'] + + for start_method in start_methods: + with self.subTest(start_method=start_method): + ctx = multiprocessing.get_context(start_method) + qspec = ctx.SimpleQueue() + with self.assertRaises(ValueError): + self._apply_simple_queue_listener_configuration(qspec) @skip_if_tsan_fork @support.requires_subprocess() diff --git a/Misc/NEWS.d/next/Library/2024-10-02-15-05-45.gh-issue-124653.tqsTu9.rst b/Misc/NEWS.d/next/Library/2024-10-02-15-05-45.gh-issue-124653.tqsTu9.rst new file mode 100644 index 00000000000000..6f5ad12d2c2981 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-10-02-15-05-45.gh-issue-124653.tqsTu9.rst @@ -0,0 +1,2 @@ +Fix detection of the minimal Queue API needed by the :mod:`logging` module. +Patch by Bénédikt Tran. From 82257374b9d10bc3df2a466fe4450a5dc576842d Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Tue, 8 Oct 2024 12:47:37 +0300 Subject: [PATCH 022/269] [3.12] gh-53203: Improve tests for strptime() (GH-125090) (GH-125093) Run them with different locales and different date and time. Add the @run_with_locales() decorator to run the test with multiple locales. Improve the run_with_locale() context manager/decorator -- it now catches only expected exceptions and reports the test as skipped if no appropriate locale is available. (cherry picked from commit 19984fe024bfd90649f1c36b78c9abf3ed72b27d) --- Lib/test/pickletester.py | 4 +- Lib/test/support/__init__.py | 53 +++++++++- Lib/test/test_codecs.py | 9 +- Lib/test/test_decimal.py | 2 +- Lib/test/test_float.py | 2 +- Lib/test/test_imaplib.py | 2 +- Lib/test/test_strptime.py | 197 +++++++++++++++++++++++++---------- Lib/test/test_time.py | 12 +-- Lib/test/test_types.py | 4 +- Lib/test/test_unicode.py | 2 +- 10 files changed, 201 insertions(+), 86 deletions(-) diff --git a/Lib/test/pickletester.py b/Lib/test/pickletester.py index b0968afcf683f3..dd7466e2c150d6 100644 --- a/Lib/test/pickletester.py +++ b/Lib/test/pickletester.py @@ -26,7 +26,7 @@ from test import support from test.support import os_helper from test.support import ( - TestFailed, run_with_locale, no_tracing, + TestFailed, run_with_locales, no_tracing, _2G, _4G, bigmemtest ) from test.support.import_helper import forget @@ -2591,7 +2591,7 @@ def test_float(self): got = self.loads(pickle) self.assert_is_copy(value, got) - @run_with_locale('LC_ALL', 'de_DE', 'fr_FR') + @run_with_locales('LC_ALL', 'de_DE', 'fr_FR', '') def test_float_format(self): # make sure that floats are formatted locale independent with proto 0 self.assertEqual(self.dumps(1.2, 0)[0:3], b'F1.') diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py index 8519fedf8dbc9d..9c3dcbc1d2bc29 100644 --- a/Lib/test/support/__init__.py +++ b/Lib/test/support/__init__.py @@ -854,8 +854,8 @@ def check_sizeof(test, o, size): test.assertEqual(result, size, msg) #======================================================================= -# Decorator for running a function in a different locale, correctly resetting -# it afterwards. +# Decorator/context manager for running a code in a different locale, +# correctly resetting it afterwards. @contextlib.contextmanager def run_with_locale(catstr, *locales): @@ -866,16 +866,21 @@ def run_with_locale(catstr, *locales): except AttributeError: # if the test author gives us an invalid category string raise - except: + except Exception: # cannot retrieve original locale, so do nothing locale = orig_locale = None + if '' not in locales: + raise unittest.SkipTest('no locales') else: for loc in locales: try: locale.setlocale(category, loc) break - except: + except locale.Error: pass + else: + if '' not in locales: + raise unittest.SkipTest(f'no locales {locales}') try: yield @@ -883,6 +888,46 @@ def run_with_locale(catstr, *locales): if locale and orig_locale: locale.setlocale(category, orig_locale) +#======================================================================= +# Decorator for running a function in multiple locales (if they are +# availasble) and resetting the original locale afterwards. + +def run_with_locales(catstr, *locales): + def deco(func): + @functools.wraps(func) + def wrapper(self, /, *args, **kwargs): + dry_run = '' in locales + try: + import locale + category = getattr(locale, catstr) + orig_locale = locale.setlocale(category) + except AttributeError: + # if the test author gives us an invalid category string + raise + except Exception: + # cannot retrieve original locale, so do nothing + pass + else: + try: + for loc in locales: + with self.subTest(locale=loc): + try: + locale.setlocale(category, loc) + except locale.Error: + self.skipTest(f'no locale {loc!r}') + else: + dry_run = False + func(self, *args, **kwargs) + finally: + locale.setlocale(category, orig_locale) + if dry_run: + # no locales available, so just run the test + # with the current locale + with self.subTest(locale=None): + func(self, *args, **kwargs) + return wrapper + return deco + #======================================================================= # Decorator for running a function in a specific timezone, correctly # resetting it afterwards. diff --git a/Lib/test/test_codecs.py b/Lib/test/test_codecs.py index 5dc5b1acf10f74..f683f069ae1397 100644 --- a/Lib/test/test_codecs.py +++ b/Lib/test/test_codecs.py @@ -2,7 +2,6 @@ import contextlib import copy import io -import locale import pickle import sys import unittest @@ -1700,16 +1699,10 @@ def test_getwriter(self): self.assertRaises(TypeError, codecs.getwriter) self.assertRaises(LookupError, codecs.getwriter, "__spam__") + @support.run_with_locale('LC_CTYPE', 'tr_TR') def test_lookup_issue1813(self): # Issue #1813: under Turkish locales, lookup of some codecs failed # because 'I' is lowercased as "ı" (dotless i) - oldlocale = locale.setlocale(locale.LC_CTYPE) - self.addCleanup(locale.setlocale, locale.LC_CTYPE, oldlocale) - try: - locale.setlocale(locale.LC_CTYPE, 'tr_TR') - except locale.Error: - # Unsupported locale on this system - self.skipTest('test needs Turkish locale') c = codecs.lookup('ASCII') self.assertEqual(c.name, 'ascii') diff --git a/Lib/test/test_decimal.py b/Lib/test/test_decimal.py index eab4881e2cba62..2ec12439d7fee4 100644 --- a/Lib/test/test_decimal.py +++ b/Lib/test/test_decimal.py @@ -1242,7 +1242,7 @@ def get_fmt(x, override=None, fmt='n'): self.assertEqual(get_fmt(Decimal('-1.5'), dotsep_wide, '020n'), '-0\u00b4000\u00b4000\u00b4000\u00b4001\u00bf5') - @run_with_locale('LC_ALL', 'ps_AF') + @run_with_locale('LC_ALL', 'ps_AF', '') def test_wide_char_separator_decimal_point(self): # locale with wide char separator and decimal point Decimal = self.decimal.Decimal diff --git a/Lib/test/test_float.py b/Lib/test/test_float.py index 9afaa55765e708..0c1adabdd58852 100644 --- a/Lib/test/test_float.py +++ b/Lib/test/test_float.py @@ -153,7 +153,7 @@ def check(s): # non-UTF-8 byte string check(b'123\xa0') - @support.run_with_locale('LC_NUMERIC', 'fr_FR', 'de_DE') + @support.run_with_locale('LC_NUMERIC', 'fr_FR', 'de_DE', '') def test_float_with_comma(self): # set locale to something that doesn't use '.' for the decimal point # float must not accept the locale specific decimal point but diff --git a/Lib/test/test_imaplib.py b/Lib/test/test_imaplib.py index 69f995f483c354..1d701281b839e8 100644 --- a/Lib/test/test_imaplib.py +++ b/Lib/test/test_imaplib.py @@ -57,7 +57,7 @@ def timevalues(self): timezone(timedelta(0, 2 * 60 * 60))), '"18-May-2033 05:33:20 +0200"'] - @run_with_locale('LC_ALL', 'de_DE', 'fr_FR') + @run_with_locale('LC_ALL', 'de_DE', 'fr_FR', '') # DST rules included to work around quirk where the Gnu C library may not # otherwise restore the previous time zone @run_with_tz('STD-1DST,M3.2.0,M11.1.0') diff --git a/Lib/test/test_strptime.py b/Lib/test/test_strptime.py index 05c8afc907ad3c..66a9815e69b9c2 100644 --- a/Lib/test/test_strptime.py +++ b/Lib/test/test_strptime.py @@ -7,7 +7,7 @@ import os import sys from test import support -from test.support import skip_if_buggy_ucrt_strfptime +from test.support import skip_if_buggy_ucrt_strfptime, run_with_locales from datetime import date as datetime_date import _strptime @@ -206,8 +206,8 @@ class StrptimeTests(unittest.TestCase): """Tests for _strptime.strptime.""" def setUp(self): - """Create testing time tuple.""" - self.time_tuple = time.gmtime() + """Create testing time tuples.""" + self.time_tuple = time.localtime() def test_ValueError(self): # Make sure ValueError is raised when match fails or format is bad @@ -288,53 +288,67 @@ def test_unconverteddata(self): # Check ValueError is raised when there is unconverted data self.assertRaises(ValueError, _strptime._strptime_time, "10 12", "%m") - def helper(self, directive, position): + def roundtrip(self, fmt, position, time_tuple=None): """Helper fxn in testing.""" - strf_output = time.strftime("%" + directive, self.time_tuple) - strp_output = _strptime._strptime_time(strf_output, "%" + directive) - self.assertTrue(strp_output[position] == self.time_tuple[position], - "testing of '%s' directive failed; '%s' -> %s != %s" % - (directive, strf_output, strp_output[position], - self.time_tuple[position])) + if time_tuple is None: + time_tuple = self.time_tuple + strf_output = time.strftime(fmt, time_tuple) + strp_output = _strptime._strptime_time(strf_output, fmt) + self.assertEqual(strp_output[position], time_tuple[position], + "testing of %r format failed; %r -> %r != %r" % + (fmt, strf_output, strp_output[position], + time_tuple[position])) + if support.verbose >= 3: + print("testing of %r format: %r -> %r" % + (fmt, strf_output, strp_output[position])) def test_year(self): # Test that the year is handled properly - for directive in ('y', 'Y'): - self.helper(directive, 0) + self.roundtrip('%Y', 0) + self.roundtrip('%y', 0) + self.roundtrip('%Y', 0, (1900, 1, 1, 0, 0, 0, 0, 1, 0)) + # Must also make sure %y values are correct for bounds set by Open Group - for century, bounds in ((1900, ('69', '99')), (2000, ('00', '68'))): - for bound in bounds: - strp_output = _strptime._strptime_time(bound, '%y') - expected_result = century + int(bound) - self.assertTrue(strp_output[0] == expected_result, - "'y' test failed; passed in '%s' " - "and returned '%s'" % (bound, strp_output[0])) + strptime = _strptime._strptime_time + self.assertEqual(strptime('00', '%y')[0], 2000) + self.assertEqual(strptime('68', '%y')[0], 2068) + self.assertEqual(strptime('69', '%y')[0], 1969) + self.assertEqual(strptime('99', '%y')[0], 1999) def test_month(self): # Test for month directives - for directive in ('B', 'b', 'm'): - self.helper(directive, 1) + self.roundtrip('%m', 1) + + @run_with_locales('LC_TIME', 'C', 'en_US', 'fr_FR', 'de_DE', 'ja_JP', 'he_IL', '') + def test_month_locale(self): + # Test for month directives + self.roundtrip('%B', 1) + self.roundtrip('%b', 1) + for m in range(1, 13): + self.roundtrip('%B', 1, (1900, m, 1, 0, 0, 0, 0, 1, 0)) + self.roundtrip('%b', 1, (1900, m, 1, 0, 0, 0, 0, 1, 0)) def test_day(self): # Test for day directives - self.helper('d', 2) + self.roundtrip('%d %Y', 2) def test_hour(self): # Test hour directives - self.helper('H', 3) - strf_output = time.strftime("%I %p", self.time_tuple) - strp_output = _strptime._strptime_time(strf_output, "%I %p") - self.assertTrue(strp_output[3] == self.time_tuple[3], - "testing of '%%I %%p' directive failed; '%s' -> %s != %s" % - (strf_output, strp_output[3], self.time_tuple[3])) + self.roundtrip('%H', 3) + + # NB: Only works on locales with AM/PM + @run_with_locales('LC_TIME', 'C', 'en_US', 'ja_JP') + def test_hour_locale(self): + # Test hour directives + self.roundtrip('%I %p', 3) def test_minute(self): # Test minute directives - self.helper('M', 4) + self.roundtrip('%M', 4) def test_second(self): # Test second directives - self.helper('S', 5) + self.roundtrip('%S', 5) def test_fraction(self): # Test microseconds @@ -345,12 +359,18 @@ def test_fraction(self): def test_weekday(self): # Test weekday directives - for directive in ('A', 'a', 'w', 'u'): - self.helper(directive,6) + self.roundtrip('%w', 6) + self.roundtrip('%u', 6) + + @run_with_locales('LC_TIME', 'C', 'en_US', 'fr_FR', 'de_DE', 'ja_JP', '') + def test_weekday_locale(self): + # Test weekday directives + self.roundtrip('%A', 6) + self.roundtrip('%a', 6) def test_julian(self): # Test julian directives - self.helper('j', 7) + self.roundtrip('%j', 7) def test_offset(self): one_hour = 60 * 60 @@ -447,20 +467,96 @@ def test_bad_timezone(self): "time.daylight set to %s and passing in %s" % (time.tzname, tz_value, time.daylight, tz_name)) - def test_date_time(self): + # NB: Does not roundtrip in some locales due to the ambiguity of + # the date and time representation (bugs in locales?): + # * Seconds are not included: bem_ZM, bokmal, ff_SN, nb_NO, nn_NO, + # no_NO, norwegian, nynorsk. + # * Hours are in 12-hour notation without AM/PM indication: hy_AM, + # id_ID, ms_MY. + # * Year is not included: ha_NG. + # * Use non-Gregorian calendar: lo_LA, thai, th_TH. + # + # BUG: Generates invalid regexp for br_FR, csb_PL, Arabic. + # BUG: Generates regexp that does not match the current date and time + # for fa_IR, gez_ER, gez_ET, lzh_TW, my_MM, or_IN, shn_MM, yo_NG. + # BUG: Generates regexp that does not match the current date and time + # for fa_IR, gez_ER, gez_ET, lzh_TW, my_MM, or_IN, shn_MM, yo_NG, + # fr_FR, ja_JP, he_IL, ko_KR, zh_CN, etc. + @run_with_locales('LC_TIME', 'C', 'en_US', 'de_DE', + 'eu_ES', 'mfe_MU') + def test_date_time_locale(self): # Test %c directive - for position in range(6): - self.helper('c', position) - - def test_date(self): + now = time.time() + self.roundtrip('%c', slice(0, 6), time.localtime(now)) + # 1 hour 20 minutes 30 seconds ago + self.roundtrip('%c', slice(0, 6), time.localtime(now - 4830)) + # 12 hours ago + self.roundtrip('%c', slice(0, 6), time.localtime(now - 12*3600)) + # different days of the week + for i in range(1, 7): + self.roundtrip('%c', slice(0, 6), time.localtime(now - i*24*3600)) + # different months + for i in range(1, 12): + self.roundtrip('%c', slice(0, 6), time.localtime(now - i*30*24*3600)) + # different year + self.roundtrip('%c', slice(0, 6), time.localtime(now - 366*24*3600)) + + # NB: Dates before 1969 do not roundtrip on some locales: + # bo_CN, bo_IN, dz_BT, eu_ES, eu_FR. + @run_with_locales('LC_TIME', 'C', 'en_US', 'de_DE', 'ja_JP') + def test_date_time_locale2(self): + # Test %c directive + self.roundtrip('%c', slice(0, 6), (1900, 1, 1, 0, 0, 0, 0, 1, 0)) + + # NB: Does not roundtrip because use non-Gregorian calendar: + # lo_LA, thai, th_TH. + # BUG: Generates regexp that does not match the current date + # for az_IR, fa_IR, lzh_TW, my_MM, or_IN, shn_MM, + # Arabic, ja_JP, ko_KR, zh_CN, etc. + @run_with_locales('LC_TIME', 'C', 'en_US', 'fr_FR', 'de_DE', + 'he_IL', 'eu_ES') + def test_date_locale(self): # Test %x directive - for position in range(0,3): - self.helper('x', position) - - def test_time(self): + now = time.time() + self.roundtrip('%x', slice(0, 3), time.localtime(now)) + # different days of the week + for i in range(1, 7): + self.roundtrip('%x', slice(0, 3), time.localtime(now - i*24*3600)) + # different months + for i in range(1, 12): + self.roundtrip('%x', slice(0, 3), time.localtime(now - i*30*24*3600)) + # different year + self.roundtrip('%x', slice(0, 3), time.localtime(now - 366*24*3600)) + + # NB: Dates before 1969 do not roundtrip on many locales, including C. + @unittest.skipIf( + support.is_emscripten or support.is_wasi, + "musl libc issue on Emscripten, bpo-46390" + ) + @run_with_locales('LC_TIME', 'en_US', 'fr_FR', 'de_DE', 'ja_JP') + def test_date_locale2(self): + # Test %x directive + self.roundtrip('%x', slice(0, 3), (1900, 1, 1, 0, 0, 0, 0, 1, 0)) + + # NB: Does not roundtrip in some locales due to the ambiguity of + # the time representation (bugs in locales?): + # * Seconds are not included: bokmal, ff_SN, nb_NO, nn_NO, no_NO, + # norwegian, nynorsk. + # * Hours are in 12-hour notation without AM/PM indication: hy_AM, + # ms_MY, sm_WS. + # BUG: Generates regexp that does not match the current time for + # aa_DJ, aa_ER, aa_ET, am_ET, az_IR, byn_ER, fa_IR, gez_ER, gez_ET, + # lzh_TW, my_MM, om_ET, om_KE, or_IN, shn_MM, sid_ET, so_DJ, so_ET, + # so_SO, ti_ER, ti_ET, tig_ER, wal_ET. + @run_with_locales('LC_TIME', 'C', 'en_US', 'fr_FR', 'de_DE', 'ja_JP') + def test_time_locale(self): # Test %X directive - for position in range(3,6): - self.helper('X', position) + now = time.time() + self.roundtrip('%X', slice(3, 6), time.localtime(now)) + # 1 hour 20 minutes 30 seconds ago + self.roundtrip('%X', slice(3, 6), time.localtime(now - 4830)) + # 12 hours ago + self.roundtrip('%X', slice(3, 6), time.localtime(now - 12*3600)) def test_percent(self): # Make sure % signs are handled properly @@ -710,13 +806,8 @@ def test_new_localetime(self): def test_TimeRE_recreation_locale(self): # The TimeRE instance should be recreated upon changing the locale. - locale_info = locale.getlocale(locale.LC_TIME) - try: - locale.setlocale(locale.LC_TIME, ('en_US', 'UTF8')) - except locale.Error: - self.skipTest('test needs en_US.UTF8 locale') - try: - _strptime._strptime_time('10', '%d') + with support.run_with_locale('LC_TIME', 'en_US.UTF8'): + _strptime._strptime_time('10 2004', '%d %Y') # Get id of current cache object. first_time_re = _strptime._TimeRE_cache try: @@ -732,10 +823,6 @@ def test_TimeRE_recreation_locale(self): # to the resetting to the original locale. except locale.Error: self.skipTest('test needs de_DE.UTF8 locale') - # Make sure we don't trample on the locale setting once we leave the - # test. - finally: - locale.setlocale(locale.LC_TIME, locale_info) @support.run_with_tz('STD-1DST,M4.1.0,M10.1.0') def test_TimeRE_recreation_timezone(self): diff --git a/Lib/test/test_time.py b/Lib/test/test_time.py index 02cc3f43a66a67..921e4eea649d6b 100644 --- a/Lib/test/test_time.py +++ b/Lib/test/test_time.py @@ -2,7 +2,6 @@ from test.support import warnings_helper import decimal import enum -import locale import math import platform import sys @@ -588,17 +587,8 @@ def test_get_clock_info(self): class TestLocale(unittest.TestCase): - def setUp(self): - self.oldloc = locale.setlocale(locale.LC_ALL) - - def tearDown(self): - locale.setlocale(locale.LC_ALL, self.oldloc) - + @support.run_with_locale('LC_ALL', 'fr_FR', '') def test_bug_3061(self): - try: - tmp = locale.setlocale(locale.LC_ALL, "fr_FR") - except locale.Error: - self.skipTest('could not set locale.LC_ALL to fr_FR') # This should not cause an exception time.strftime("%B", (2009,2,1,0,0,0,0,0,0)) diff --git a/Lib/test/test_types.py b/Lib/test/test_types.py index 43949770b16637..9b2a5728e9f534 100644 --- a/Lib/test/test_types.py +++ b/Lib/test/test_types.py @@ -397,7 +397,7 @@ def test(i, format_spec, result): test(123456, "1=20", '11111111111111123456') test(123456, "*=20", '**************123456') - @run_with_locale('LC_NUMERIC', 'en_US.UTF8') + @run_with_locale('LC_NUMERIC', 'en_US.UTF8', '') def test_float__format__locale(self): # test locale support for __format__ code 'n' @@ -406,7 +406,7 @@ def test_float__format__locale(self): self.assertEqual(locale.format_string('%g', x, grouping=True), format(x, 'n')) self.assertEqual(locale.format_string('%.10g', x, grouping=True), format(x, '.10n')) - @run_with_locale('LC_NUMERIC', 'en_US.UTF8') + @run_with_locale('LC_NUMERIC', 'en_US.UTF8', '') def test_int__format__locale(self): # test locale support for __format__ code 'n' for integers diff --git a/Lib/test/test_unicode.py b/Lib/test/test_unicode.py index e43be3578836ef..19556eecc3c646 100644 --- a/Lib/test/test_unicode.py +++ b/Lib/test/test_unicode.py @@ -1676,7 +1676,7 @@ def test_startswith_endswith_errors(self): self.assertIn('str', exc) self.assertIn('tuple', exc) - @support.run_with_locale('LC_ALL', 'de_DE', 'fr_FR') + @support.run_with_locale('LC_ALL', 'de_DE', 'fr_FR', '') def test_format_float(self): # should not format with a comma, but always with C locale self.assertEqual('1.0', '%.1f' % 1.0) From d3b437cb7816d0163dbc3495889e87ebc22e56a0 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Tue, 8 Oct 2024 13:56:18 +0200 Subject: [PATCH 023/269] [3.12] gh-123378: fix a crash in `UnicodeError.__str__` (GH-124935) (#125098) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit gh-123378: fix a crash in `UnicodeError.__str__` (GH-124935) (cherry picked from commit ba14dfafd97d1fd03938ac8ddec4ca5b2f12985d) Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> --- Lib/test/test_exceptions.py | 24 ++++ ...-10-03-14-39-41.gh-issue-123378.dCxANf.rst | 3 + Objects/exceptions.c | 111 +++++++++++------- 3 files changed, 93 insertions(+), 45 deletions(-) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2024-10-03-14-39-41.gh-issue-123378.dCxANf.rst diff --git a/Lib/test/test_exceptions.py b/Lib/test/test_exceptions.py index 5b0334f34652d2..c5f4b892efb50f 100644 --- a/Lib/test/test_exceptions.py +++ b/Lib/test/test_exceptions.py @@ -8,6 +8,7 @@ import weakref import errno from codecs import BOM_UTF8 +from itertools import product from textwrap import dedent from test.support import (captured_stderr, check_impl_detail, @@ -1333,6 +1334,29 @@ def test_unicode_errors_no_object(self): for klass in klasses: self.assertEqual(str(klass.__new__(klass)), "") + def test_unicode_error_str_does_not_crash(self): + # Test that str(UnicodeError(...)) does not crash. + # See https://github.com/python/cpython/issues/123378. + + for start, end, objlen in product( + range(-5, 5), + range(-5, 5), + range(7), + ): + obj = 'a' * objlen + with self.subTest('encode', objlen=objlen, start=start, end=end): + exc = UnicodeEncodeError('utf-8', obj, start, end, '') + self.assertIsInstance(str(exc), str) + + with self.subTest('translate', objlen=objlen, start=start, end=end): + exc = UnicodeTranslateError(obj, start, end, '') + self.assertIsInstance(str(exc), str) + + encoded = obj.encode() + with self.subTest('decode', objlen=objlen, start=start, end=end): + exc = UnicodeDecodeError('utf-8', encoded, start, end, '') + self.assertIsInstance(str(exc), str) + @no_tracing def test_badisinstance(self): # Bug #2542: if issubclass(e, MyException) raises an exception, diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2024-10-03-14-39-41.gh-issue-123378.dCxANf.rst b/Misc/NEWS.d/next/Core_and_Builtins/2024-10-03-14-39-41.gh-issue-123378.dCxANf.rst new file mode 100644 index 00000000000000..5cd34535d674d3 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2024-10-03-14-39-41.gh-issue-123378.dCxANf.rst @@ -0,0 +1,3 @@ +Fix a crash in the :meth:`~object.__str__` method of :exc:`UnicodeError` +objects when the :attr:`UnicodeError.start` and :attr:`UnicodeError.end` +values are invalid or out-of-range. Patch by Bénédikt Tran. diff --git a/Objects/exceptions.c b/Objects/exceptions.c index 4f2153b19358d2..c579563db75275 100644 --- a/Objects/exceptions.c +++ b/Objects/exceptions.c @@ -2961,46 +2961,55 @@ UnicodeEncodeError_init(PyObject *self, PyObject *args, PyObject *kwds) static PyObject * UnicodeEncodeError_str(PyObject *self) { - PyUnicodeErrorObject *uself = (PyUnicodeErrorObject *)self; + PyUnicodeErrorObject *exc = (PyUnicodeErrorObject *)self; PyObject *result = NULL; PyObject *reason_str = NULL; PyObject *encoding_str = NULL; - if (!uself->object) + if (exc->object == NULL) { /* Not properly initialized. */ return PyUnicode_FromString(""); + } /* Get reason and encoding as strings, which they might not be if they've been modified after we were constructed. */ - reason_str = PyObject_Str(uself->reason); - if (reason_str == NULL) + reason_str = PyObject_Str(exc->reason); + if (reason_str == NULL) { goto done; - encoding_str = PyObject_Str(uself->encoding); - if (encoding_str == NULL) + } + encoding_str = PyObject_Str(exc->encoding); + if (encoding_str == NULL) { goto done; + } + + Py_ssize_t len = PyUnicode_GET_LENGTH(exc->object); + Py_ssize_t start = exc->start, end = exc->end; - if (uself->start < PyUnicode_GET_LENGTH(uself->object) && uself->end == uself->start+1) { - Py_UCS4 badchar = PyUnicode_ReadChar(uself->object, uself->start); + if ((start >= 0 && start < len) && (end >= 0 && end <= len) && end == start + 1) { + Py_UCS4 badchar = PyUnicode_ReadChar(exc->object, start); const char *fmt; - if (badchar <= 0xff) + if (badchar <= 0xff) { fmt = "'%U' codec can't encode character '\\x%02x' in position %zd: %U"; - else if (badchar <= 0xffff) + } + else if (badchar <= 0xffff) { fmt = "'%U' codec can't encode character '\\u%04x' in position %zd: %U"; - else + } + else { fmt = "'%U' codec can't encode character '\\U%08x' in position %zd: %U"; + } result = PyUnicode_FromFormat( fmt, encoding_str, (int)badchar, - uself->start, + start, reason_str); } else { result = PyUnicode_FromFormat( "'%U' codec can't encode characters in position %zd-%zd: %U", encoding_str, - uself->start, - uself->end-1, + start, + end - 1, reason_str); } done: @@ -3074,41 +3083,46 @@ UnicodeDecodeError_init(PyObject *self, PyObject *args, PyObject *kwds) static PyObject * UnicodeDecodeError_str(PyObject *self) { - PyUnicodeErrorObject *uself = (PyUnicodeErrorObject *)self; + PyUnicodeErrorObject *exc = (PyUnicodeErrorObject *)self; PyObject *result = NULL; PyObject *reason_str = NULL; PyObject *encoding_str = NULL; - if (!uself->object) + if (exc->object == NULL) { /* Not properly initialized. */ return PyUnicode_FromString(""); + } /* Get reason and encoding as strings, which they might not be if they've been modified after we were constructed. */ - reason_str = PyObject_Str(uself->reason); - if (reason_str == NULL) + reason_str = PyObject_Str(exc->reason); + if (reason_str == NULL) { goto done; - encoding_str = PyObject_Str(uself->encoding); - if (encoding_str == NULL) + } + encoding_str = PyObject_Str(exc->encoding); + if (encoding_str == NULL) { goto done; + } + + Py_ssize_t len = PyBytes_GET_SIZE(exc->object); + Py_ssize_t start = exc->start, end = exc->end; - if (uself->start < PyBytes_GET_SIZE(uself->object) && uself->end == uself->start+1) { - int byte = (int)(PyBytes_AS_STRING(((PyUnicodeErrorObject *)self)->object)[uself->start]&0xff); + if ((start >= 0 && start < len) && (end >= 0 && end <= len) && end == start + 1) { + int badbyte = (int)(PyBytes_AS_STRING(exc->object)[start] & 0xff); result = PyUnicode_FromFormat( "'%U' codec can't decode byte 0x%02x in position %zd: %U", encoding_str, - byte, - uself->start, + badbyte, + start, reason_str); } else { result = PyUnicode_FromFormat( "'%U' codec can't decode bytes in position %zd-%zd: %U", encoding_str, - uself->start, - uself->end-1, - reason_str - ); + start, + end - 1, + reason_str); } done: Py_XDECREF(reason_str); @@ -3171,42 +3185,49 @@ UnicodeTranslateError_init(PyUnicodeErrorObject *self, PyObject *args, static PyObject * UnicodeTranslateError_str(PyObject *self) { - PyUnicodeErrorObject *uself = (PyUnicodeErrorObject *)self; + PyUnicodeErrorObject *exc = (PyUnicodeErrorObject *)self; PyObject *result = NULL; PyObject *reason_str = NULL; - if (!uself->object) + if (exc->object == NULL) { /* Not properly initialized. */ return PyUnicode_FromString(""); + } /* Get reason as a string, which it might not be if it's been modified after we were constructed. */ - reason_str = PyObject_Str(uself->reason); - if (reason_str == NULL) + reason_str = PyObject_Str(exc->reason); + if (reason_str == NULL) { goto done; + } + + Py_ssize_t len = PyUnicode_GET_LENGTH(exc->object); + Py_ssize_t start = exc->start, end = exc->end; - if (uself->start < PyUnicode_GET_LENGTH(uself->object) && uself->end == uself->start+1) { - Py_UCS4 badchar = PyUnicode_ReadChar(uself->object, uself->start); + if ((start >= 0 && start < len) && (end >= 0 && end <= len) && end == start + 1) { + Py_UCS4 badchar = PyUnicode_ReadChar(exc->object, start); const char *fmt; - if (badchar <= 0xff) + if (badchar <= 0xff) { fmt = "can't translate character '\\x%02x' in position %zd: %U"; - else if (badchar <= 0xffff) + } + else if (badchar <= 0xffff) { fmt = "can't translate character '\\u%04x' in position %zd: %U"; - else + } + else { fmt = "can't translate character '\\U%08x' in position %zd: %U"; + } result = PyUnicode_FromFormat( fmt, (int)badchar, - uself->start, - reason_str - ); - } else { + start, + reason_str); + } + else { result = PyUnicode_FromFormat( "can't translate characters in position %zd-%zd: %U", - uself->start, - uself->end-1, - reason_str - ); + start, + end - 1, + reason_str); } done: Py_XDECREF(reason_str); From 65272a382b9536e1c53484a58f0f9c392e2ca872 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Tue, 8 Oct 2024 14:32:04 +0200 Subject: [PATCH 024/269] [3.12] Doc: Improve description of ``GET_LEN`` opcode (GH-114583) (#125103) Doc: Improve description of ``GET_LEN`` opcode (GH-114583) (cherry picked from commit e8773e59a835d23b9648271e0eb79c1651581564) Co-authored-by: Kirill Podoprigora --- Doc/library/dis.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Doc/library/dis.rst b/Doc/library/dis.rst index 82b4aa28857f41..f9f82d25b874e8 100644 --- a/Doc/library/dis.rst +++ b/Doc/library/dis.rst @@ -850,7 +850,8 @@ iterations of the loop. .. opcode:: GET_LEN - Perform ``STACK.append(len(STACK[-1]))``. + Perform ``STACK.append(len(STACK[-1]))``. Used in :keyword:`match` statements where + comparison with structure of pattern is needed. .. versionadded:: 3.10 From 382ee1c7bd292f964f5d97384b93dfbdf7b64061 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Tue, 8 Oct 2024 20:27:50 +0200 Subject: [PATCH 025/269] [3.12] gh-124832: Add a note to indicate that `datetime.now` may return the same instant (GH-124834) (#125146) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit gh-124832: Add a note to indicate that `datetime.now` may return the same instant (GH-124834) * Update datetime.rst * Update datetime.rst replace warning with note * Update Doc/library/datetime.rst * Update Doc/library/datetime.rst --------- (cherry picked from commit 760b1e103a0aa696cdf448e0d500cd1bac2213fa) Co-authored-by: spacemanspiff2007 <10754716+spacemanspiff2007@users.noreply.github.com> Co-authored-by: Victor Stinner Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> --- Doc/library/datetime.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Doc/library/datetime.rst b/Doc/library/datetime.rst index 8f5ea47da0db25..671554f2cf3d0d 100644 --- a/Doc/library/datetime.rst +++ b/Doc/library/datetime.rst @@ -896,6 +896,10 @@ Other constructors, all class methods: This function is preferred over :meth:`today` and :meth:`utcnow`. + .. note:: + + Subsequent calls to :meth:`!datetime.now` may return the same + instant depending on the precision of the underlying clock. .. classmethod:: datetime.utcnow() From cf2532b39d099e004d1c07b2d0fcc46567b68e75 Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Tue, 8 Oct 2024 15:16:18 -0500 Subject: [PATCH 026/269] [3.12] Tee of tee was not producing n independent iterators (gh-123884) (gh-125153) --- Doc/library/itertools.rst | 49 +++-- Lib/test/test_itertools.py | 171 +++++++++++++++++- ...-09-24-22-38-51.gh-issue-123884.iEPTK4.rst | 4 + Modules/itertoolsmodule.c | 37 +--- 4 files changed, 214 insertions(+), 47 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-09-24-22-38-51.gh-issue-123884.iEPTK4.rst diff --git a/Doc/library/itertools.rst b/Doc/library/itertools.rst index 3fab46c3c0a5b4..047d805eda628a 100644 --- a/Doc/library/itertools.rst +++ b/Doc/library/itertools.rst @@ -676,24 +676,37 @@ loops that truncate the stream. Roughly equivalent to:: def tee(iterable, n=2): - iterator = iter(iterable) - shared_link = [None, None] - return tuple(_tee(iterator, shared_link) for _ in range(n)) - - def _tee(iterator, link): - try: - while True: - if link[1] is None: - link[0] = next(iterator) - link[1] = [None, None] - value, link = link - yield value - except StopIteration: - return - - Once a :func:`tee` has been created, the original *iterable* should not be - used anywhere else; otherwise, the *iterable* could get advanced without - the tee objects being informed. + if n < 0: + raise ValueError + if n == 0: + return () + iterator = _tee(iterable) + result = [iterator] + for _ in range(n - 1): + result.append(_tee(iterator)) + return tuple(result) + + class _tee: + + def __init__(self, iterable): + it = iter(iterable) + if isinstance(it, _tee): + self.iterator = it.iterator + self.link = it.link + else: + self.iterator = it + self.link = [None, None] + + def __iter__(self): + return self + + def __next__(self): + link = self.link + if link[1] is None: + link[0] = next(self.iterator) + link[1] = [None, None] + value, self.link = link + return value ``tee`` iterators are not threadsafe. A :exc:`RuntimeError` may be raised when simultaneously using iterators returned by the same :func:`tee` diff --git a/Lib/test/test_itertools.py b/Lib/test/test_itertools.py index 3d20e70fc1b63f..b6404f4366ca0e 100644 --- a/Lib/test/test_itertools.py +++ b/Lib/test/test_itertools.py @@ -1612,10 +1612,11 @@ def test_tee(self): self.assertEqual(len(result), n) self.assertEqual([list(x) for x in result], [list('abc')]*n) - # tee pass-through to copyable iterator + # tee objects are independent (see bug gh-123884) a, b = tee('abc') c, d = tee(a) - self.assertTrue(a is c) + e, f = tee(c) + self.assertTrue(len({a, b, c, d, e, f}) == 6) # test tee_new t1, t2 = tee('abc') @@ -2029,6 +2030,172 @@ def test_islice_recipe(self): self.assertEqual(next(c), 3) + def test_tee_recipe(self): + + # Begin tee() recipe ########################################### + + def tee(iterable, n=2): + if n < 0: + raise ValueError + if n == 0: + return () + iterator = _tee(iterable) + result = [iterator] + for _ in range(n - 1): + result.append(_tee(iterator)) + return tuple(result) + + class _tee: + + def __init__(self, iterable): + it = iter(iterable) + if isinstance(it, _tee): + self.iterator = it.iterator + self.link = it.link + else: + self.iterator = it + self.link = [None, None] + + def __iter__(self): + return self + + def __next__(self): + link = self.link + if link[1] is None: + link[0] = next(self.iterator) + link[1] = [None, None] + value, self.link = link + return value + + # End tee() recipe ############################################# + + n = 200 + + a, b = tee([]) # test empty iterator + self.assertEqual(list(a), []) + self.assertEqual(list(b), []) + + a, b = tee(irange(n)) # test 100% interleaved + self.assertEqual(lzip(a,b), lzip(range(n), range(n))) + + a, b = tee(irange(n)) # test 0% interleaved + self.assertEqual(list(a), list(range(n))) + self.assertEqual(list(b), list(range(n))) + + a, b = tee(irange(n)) # test dealloc of leading iterator + for i in range(100): + self.assertEqual(next(a), i) + del a + self.assertEqual(list(b), list(range(n))) + + a, b = tee(irange(n)) # test dealloc of trailing iterator + for i in range(100): + self.assertEqual(next(a), i) + del b + self.assertEqual(list(a), list(range(100, n))) + + for j in range(5): # test randomly interleaved + order = [0]*n + [1]*n + random.shuffle(order) + lists = ([], []) + its = tee(irange(n)) + for i in order: + value = next(its[i]) + lists[i].append(value) + self.assertEqual(lists[0], list(range(n))) + self.assertEqual(lists[1], list(range(n))) + + # test argument format checking + self.assertRaises(TypeError, tee) + self.assertRaises(TypeError, tee, 3) + self.assertRaises(TypeError, tee, [1,2], 'x') + self.assertRaises(TypeError, tee, [1,2], 3, 'x') + + # tee object should be instantiable + a, b = tee('abc') + c = type(a)('def') + self.assertEqual(list(c), list('def')) + + # test long-lagged and multi-way split + a, b, c = tee(range(2000), 3) + for i in range(100): + self.assertEqual(next(a), i) + self.assertEqual(list(b), list(range(2000))) + self.assertEqual([next(c), next(c)], list(range(2))) + self.assertEqual(list(a), list(range(100,2000))) + self.assertEqual(list(c), list(range(2,2000))) + + # test invalid values of n + self.assertRaises(TypeError, tee, 'abc', 'invalid') + self.assertRaises(ValueError, tee, [], -1) + + for n in range(5): + result = tee('abc', n) + self.assertEqual(type(result), tuple) + self.assertEqual(len(result), n) + self.assertEqual([list(x) for x in result], [list('abc')]*n) + + # tee objects are independent (see bug gh-123884) + a, b = tee('abc') + c, d = tee(a) + e, f = tee(c) + self.assertTrue(len({a, b, c, d, e, f}) == 6) + + # test tee_new + t1, t2 = tee('abc') + tnew = type(t1) + self.assertRaises(TypeError, tnew) + self.assertRaises(TypeError, tnew, 10) + t3 = tnew(t1) + self.assertTrue(list(t1) == list(t2) == list(t3) == list('abc')) + + # test that tee objects are weak referencable + a, b = tee(range(10)) + p = weakref.proxy(a) + self.assertEqual(getattr(p, '__class__'), type(b)) + del a + gc.collect() # For PyPy or other GCs. + self.assertRaises(ReferenceError, getattr, p, '__class__') + + ans = list('abc') + long_ans = list(range(10000)) + + # Tests not applicable to the tee() recipe + if False: + # check copy + a, b = tee('abc') + self.assertEqual(list(copy.copy(a)), ans) + self.assertEqual(list(copy.copy(b)), ans) + a, b = tee(list(range(10000))) + self.assertEqual(list(copy.copy(a)), long_ans) + self.assertEqual(list(copy.copy(b)), long_ans) + + # check partially consumed copy + a, b = tee('abc') + take(2, a) + take(1, b) + self.assertEqual(list(copy.copy(a)), ans[2:]) + self.assertEqual(list(copy.copy(b)), ans[1:]) + self.assertEqual(list(a), ans[2:]) + self.assertEqual(list(b), ans[1:]) + a, b = tee(range(10000)) + take(100, a) + take(60, b) + self.assertEqual(list(copy.copy(a)), long_ans[100:]) + self.assertEqual(list(copy.copy(b)), long_ans[60:]) + self.assertEqual(list(a), long_ans[100:]) + self.assertEqual(list(b), long_ans[60:]) + + # Issue 13454: Crash when deleting backward iterator from tee() + forward, backward = tee(repeat(None, 2000)) # 20000000 + try: + any(forward) # exhaust the iterator + del backward + except: + del forward, backward + raise + + class TestGC(unittest.TestCase): def makecycle(self, iterator, container): diff --git a/Misc/NEWS.d/next/Library/2024-09-24-22-38-51.gh-issue-123884.iEPTK4.rst b/Misc/NEWS.d/next/Library/2024-09-24-22-38-51.gh-issue-123884.iEPTK4.rst new file mode 100644 index 00000000000000..55f1d4b41125c3 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-09-24-22-38-51.gh-issue-123884.iEPTK4.rst @@ -0,0 +1,4 @@ +Fixed bug in itertools.tee() handling of other tee inputs (a tee in a tee). +The output now has the promised *n* independent new iterators. Formerly, +the first iterator was identical (not independent) to the input iterator. +This would sometimes give surprising results. diff --git a/Modules/itertoolsmodule.c b/Modules/itertoolsmodule.c index d42f9dd0768658..e87c753113563f 100644 --- a/Modules/itertoolsmodule.c +++ b/Modules/itertoolsmodule.c @@ -1137,7 +1137,7 @@ itertools_tee_impl(PyObject *module, PyObject *iterable, Py_ssize_t n) /*[clinic end generated code: output=1c64519cd859c2f0 input=c99a1472c425d66d]*/ { Py_ssize_t i; - PyObject *it, *copyable, *copyfunc, *result; + PyObject *it, *to, *result; if (n < 0) { PyErr_SetString(PyExc_ValueError, "n must be >= 0"); @@ -1154,41 +1154,24 @@ itertools_tee_impl(PyObject *module, PyObject *iterable, Py_ssize_t n) return NULL; } - if (_PyObject_LookupAttr(it, &_Py_ID(__copy__), ©func) < 0) { - Py_DECREF(it); + (void)&_Py_ID(__copy__); // Retain a reference to __copy__ + itertools_state *state = get_module_state(module); + to = tee_fromiterable(state, it); + Py_DECREF(it); + if (to == NULL) { Py_DECREF(result); return NULL; } - if (copyfunc != NULL) { - copyable = it; - } - else { - itertools_state *state = get_module_state(module); - copyable = tee_fromiterable(state, it); - Py_DECREF(it); - if (copyable == NULL) { - Py_DECREF(result); - return NULL; - } - copyfunc = PyObject_GetAttr(copyable, &_Py_ID(__copy__)); - if (copyfunc == NULL) { - Py_DECREF(copyable); - Py_DECREF(result); - return NULL; - } - } - PyTuple_SET_ITEM(result, 0, copyable); + PyTuple_SET_ITEM(result, 0, to); for (i = 1; i < n; i++) { - copyable = _PyObject_CallNoArgs(copyfunc); - if (copyable == NULL) { - Py_DECREF(copyfunc); + to = tee_copy((teeobject *)to, NULL); + if (to == NULL) { Py_DECREF(result); return NULL; } - PyTuple_SET_ITEM(result, i, copyable); + PyTuple_SET_ITEM(result, i, to); } - Py_DECREF(copyfunc); return result; } From 0dbad1d8b723e1a6a46784ce720ea474f693127e Mon Sep 17 00:00:00 2001 From: Savannah Ostrowski Date: Tue, 8 Oct 2024 17:20:49 -0600 Subject: [PATCH 027/269] [3.12] GH-124478: Cleanup argparse documentation (GH-124877) (#125164) (cherry picked from commit 37228bd16e3ef97d32da08848552f7ef016d68ab) Co-authored-by: Jelle Zijlstra Co-authored-by: Tomas R --- Doc/howto/argparse-optparse.rst | 55 ++++ Doc/library/argparse.rst | 548 +++++++++----------------------- 2 files changed, 201 insertions(+), 402 deletions(-) create mode 100644 Doc/howto/argparse-optparse.rst diff --git a/Doc/howto/argparse-optparse.rst b/Doc/howto/argparse-optparse.rst new file mode 100644 index 00000000000000..cef2d893b28a62 --- /dev/null +++ b/Doc/howto/argparse-optparse.rst @@ -0,0 +1,55 @@ +.. currentmodule:: argparse + +.. _upgrading-optparse-code: + +========================== +Upgrading optparse code +========================== + +Originally, the :mod:`argparse` module had attempted to maintain compatibility +with :mod:`optparse`. However, :mod:`optparse` was difficult to extend +transparently, particularly with the changes required to support +``nargs=`` specifiers and better usage messages. When most everything in +:mod:`optparse` had either been copy-pasted over or monkey-patched, it no +longer seemed practical to try to maintain the backwards compatibility. + +The :mod:`argparse` module improves on the :mod:`optparse` +module in a number of ways including: + +* Handling positional arguments. +* Supporting subcommands. +* Allowing alternative option prefixes like ``+`` and ``/``. +* Handling zero-or-more and one-or-more style arguments. +* Producing more informative usage messages. +* Providing a much simpler interface for custom ``type`` and ``action``. + +A partial upgrade path from :mod:`optparse` to :mod:`argparse`: + +* Replace all :meth:`optparse.OptionParser.add_option` calls with + :meth:`ArgumentParser.add_argument` calls. + +* Replace ``(options, args) = parser.parse_args()`` with ``args = + parser.parse_args()`` and add additional :meth:`ArgumentParser.add_argument` + calls for the positional arguments. Keep in mind that what was previously + called ``options``, now in the :mod:`argparse` context is called ``args``. + +* Replace :meth:`optparse.OptionParser.disable_interspersed_args` + by using :meth:`~ArgumentParser.parse_intermixed_args` instead of + :meth:`~ArgumentParser.parse_args`. + +* Replace callback actions and the ``callback_*`` keyword arguments with + ``type`` or ``action`` arguments. + +* Replace string names for ``type`` keyword arguments with the corresponding + type objects (e.g. int, float, complex, etc). + +* Replace :class:`optparse.Values` with :class:`Namespace` and + :exc:`optparse.OptionError` and :exc:`optparse.OptionValueError` with + :exc:`ArgumentError`. + +* Replace strings with implicit arguments such as ``%default`` or ``%prog`` with + the standard Python syntax to use dictionaries to format strings, that is, + ``%(default)s`` and ``%(prog)s``. + +* Replace the OptionParser constructor ``version`` argument with a call to + ``parser.add_argument('--version', action='version', version='')``. diff --git a/Doc/library/argparse.rst b/Doc/library/argparse.rst index 632185b3f183f5..abe20d7d611695 100644 --- a/Doc/library/argparse.rst +++ b/Doc/library/argparse.rst @@ -1,4 +1,4 @@ -:mod:`!argparse` --- Parser for command-line options, arguments and sub-commands +:mod:`!argparse` --- Parser for command-line options, arguments and subcommands ================================================================================ .. module:: argparse @@ -19,17 +19,13 @@ introduction to Python command-line parsing, have a look at the :ref:`argparse tutorial `. -The :mod:`argparse` module makes it easy to write user-friendly command-line -interfaces. The program defines what arguments it requires, and :mod:`argparse` -will figure out how to parse those out of :data:`sys.argv`. The :mod:`argparse` +The :mod:`!argparse` module makes it easy to write user-friendly command-line +interfaces. The program defines what arguments it requires, and :mod:`!argparse` +will figure out how to parse those out of :data:`sys.argv`. The :mod:`!argparse` module also automatically generates help and usage messages. The module will also issue errors when users give the program invalid arguments. - -Core Functionality ------------------- - -The :mod:`argparse` module's support for command-line interfaces is built +The :mod:`!argparse` module's support for command-line interfaces is built around an instance of :class:`argparse.ArgumentParser`. It is a container for argument specifications and has options that apply to the parser as whole:: @@ -53,133 +49,9 @@ the extracted data in a :class:`argparse.Namespace` object:: args = parser.parse_args() print(args.filename, args.count, args.verbose) - -Quick Links for add_argument() ------------------------------- - -============================ =========================================================== ========================================================================================================================== -Name Description Values -============================ =========================================================== ========================================================================================================================== -action_ Specify how an argument should be handled ``'store'``, ``'store_const'``, ``'store_true'``, ``'append'``, ``'append_const'``, ``'count'``, ``'help'``, ``'version'`` -choices_ Limit values to a specific set of choices ``['foo', 'bar']``, ``range(1, 10)``, or :class:`~collections.abc.Container` instance -const_ Store a constant value -default_ Default value used when an argument is not provided Defaults to ``None`` -dest_ Specify the attribute name used in the result namespace -help_ Help message for an argument -metavar_ Alternate display name for the argument as shown in help -nargs_ Number of times the argument can be used :class:`int`, ``'?'``, ``'*'``, or ``'+'`` -required_ Indicate whether an argument is required or optional ``True`` or ``False`` -:ref:`type ` Automatically convert an argument to the given type :class:`int`, :class:`float`, ``argparse.FileType('w')``, or callable function -============================ =========================================================== ========================================================================================================================== - - -Example -------- - -The following code is a Python program that takes a list of integers and -produces either the sum or the max:: - - import argparse - - parser = argparse.ArgumentParser(description='Process some integers.') - parser.add_argument('integers', metavar='N', type=int, nargs='+', - help='an integer for the accumulator') - parser.add_argument('--sum', dest='accumulate', action='store_const', - const=sum, default=max, - help='sum the integers (default: find the max)') - - args = parser.parse_args() - print(args.accumulate(args.integers)) - -Assuming the above Python code is saved into a file called ``prog.py``, it can -be run at the command line and it provides useful help messages: - -.. code-block:: shell-session - - $ python prog.py -h - usage: prog.py [-h] [--sum] N [N ...] - - Process some integers. - - positional arguments: - N an integer for the accumulator - - options: - -h, --help show this help message and exit - --sum sum the integers (default: find the max) - -When run with the appropriate arguments, it prints either the sum or the max of -the command-line integers: - -.. code-block:: shell-session - - $ python prog.py 1 2 3 4 - 4 - - $ python prog.py 1 2 3 4 --sum - 10 - -If invalid arguments are passed in, an error will be displayed: - -.. code-block:: shell-session - - $ python prog.py a b c - usage: prog.py [-h] [--sum] N [N ...] - prog.py: error: argument N: invalid int value: 'a' - -The following sections walk you through this example. - - -Creating a parser -^^^^^^^^^^^^^^^^^ - -The first step in using the :mod:`argparse` is creating an -:class:`ArgumentParser` object:: - - >>> parser = argparse.ArgumentParser(description='Process some integers.') - -The :class:`ArgumentParser` object will hold all the information necessary to -parse the command line into Python data types. - - -Adding arguments -^^^^^^^^^^^^^^^^ - -Filling an :class:`ArgumentParser` with information about program arguments is -done by making calls to the :meth:`~ArgumentParser.add_argument` method. -Generally, these calls tell the :class:`ArgumentParser` how to take the strings -on the command line and turn them into objects. This information is stored and -used when :meth:`~ArgumentParser.parse_args` is called. For example:: - - >>> parser.add_argument('integers', metavar='N', type=int, nargs='+', - ... help='an integer for the accumulator') - >>> parser.add_argument('--sum', dest='accumulate', action='store_const', - ... const=sum, default=max, - ... help='sum the integers (default: find the max)') - -Later, calling :meth:`~ArgumentParser.parse_args` will return an object with -two attributes, ``integers`` and ``accumulate``. The ``integers`` attribute -will be a list of one or more integers, and the ``accumulate`` attribute will be -either the :func:`sum` function, if ``--sum`` was specified at the command line, -or the :func:`max` function if it was not. - - -Parsing arguments -^^^^^^^^^^^^^^^^^ - -:class:`ArgumentParser` parses arguments through the -:meth:`~ArgumentParser.parse_args` method. This will inspect the command line, -convert each argument to the appropriate type and then invoke the appropriate action. -In most cases, this means a simple :class:`Namespace` object will be built up from -attributes parsed out of the command line:: - - >>> parser.parse_args(['--sum', '7', '-1', '42']) - Namespace(accumulate=, integers=[7, -1, 42]) - -In a script, :meth:`~ArgumentParser.parse_args` will typically be called with no -arguments, and the :class:`ArgumentParser` will automatically determine the -command-line arguments from :data:`sys.argv`. - +.. note:: + If you're looking a guide about how to upgrade optparse code + to argparse, see :ref:`Upgrading Optparse Code `. ArgumentParser objects ---------------------- @@ -249,39 +121,21 @@ The following sections describe how each of these are used. prog ^^^^ -By default, :class:`ArgumentParser` objects use the base name -(see :func:`os.path.basename`) of ``sys.argv[0]`` to determine -how to display the name of the program in help messages. This default is almost -always desirable because it will make the help messages match the name that was -used to invoke the program on the command line. For example, consider a file -named ``myprogram.py`` with the following code:: - - import argparse - parser = argparse.ArgumentParser() - parser.add_argument('--foo', help='foo help') - args = parser.parse_args() - -The help for this program will display ``myprogram.py`` as the program name -(regardless of where the program was invoked from): -.. code-block:: shell-session +By default, :class:`ArgumentParser` calculates the name of the program +to display in help messages depending on the way the Python interpreter was run: - $ python myprogram.py --help - usage: myprogram.py [-h] [--foo FOO] +* The :func:`base name ` of ``sys.argv[0]`` if a file was + passed as argument. +* The Python interpreter name followed by ``sys.argv[0]`` if a directory or + a zipfile was passed as argument. +* The Python interpreter name followed by ``-m`` followed by the + module or package name if the :option:`-m` option was used. - options: - -h, --help show this help message and exit - --foo FOO foo help - $ cd .. - $ python subdir/myprogram.py --help - usage: myprogram.py [-h] [--foo FOO] - - options: - -h, --help show this help message and exit - --foo FOO foo help - -To change this default behavior, another value can be supplied using the -``prog=`` argument to :class:`ArgumentParser`:: +This default is almost always desirable because it will make the help messages +match the string that was used to invoke the program on the command line. +However, to change this default behavior, another value can be supplied using +the ``prog=`` argument to :class:`ArgumentParser`:: >>> parser = argparse.ArgumentParser(prog='myprogram') >>> parser.print_help() @@ -310,22 +164,8 @@ usage ^^^^^ By default, :class:`ArgumentParser` calculates the usage message from the -arguments it contains:: - - >>> parser = argparse.ArgumentParser(prog='PROG') - >>> parser.add_argument('--foo', nargs='?', help='foo help') - >>> parser.add_argument('bar', nargs='+', help='bar help') - >>> parser.print_help() - usage: PROG [-h] [--foo [FOO]] bar [bar ...] - - positional arguments: - bar bar help - - options: - -h, --help show this help message and exit - --foo [FOO] foo help - -The default message can be overridden with the ``usage=`` keyword argument:: +arguments it contains. The default message can be overridden with the +``usage=`` keyword argument:: >>> parser = argparse.ArgumentParser(prog='PROG', usage='%(prog)s [options]') >>> parser.add_argument('--foo', nargs='?', help='foo help') @@ -353,16 +193,7 @@ Most calls to the :class:`ArgumentParser` constructor will use the ``description=`` keyword argument. This argument gives a brief description of what the program does and how it works. In help messages, the description is displayed between the command-line usage string and the help messages for the -various arguments:: - - >>> parser = argparse.ArgumentParser(description='A foo that bars') - >>> parser.print_help() - usage: argparse.py [-h] - - A foo that bars - - options: - -h, --help show this help message and exit +various arguments. By default, the description will be line-wrapped so that it fits within the given space. To change this behavior, see the formatter_class_ argument. @@ -492,7 +323,7 @@ should not be line-wrapped:: -h, --help show this help message and exit :class:`RawTextHelpFormatter` maintains whitespace for all sorts of help text, -including argument descriptions. However, multiple new lines are replaced with +including argument descriptions. However, multiple newlines are replaced with one. If you wish to preserve multiple blank lines, add spaces between the newlines. @@ -586,8 +417,8 @@ arguments will never be treated as file references. .. versionchanged:: 3.12 :class:`ArgumentParser` changed encoding and errors to read arguments files - from default (e.g. :func:`locale.getpreferredencoding(False) ` and - ``"strict"``) to :term:`filesystem encoding and error handler`. + from default (e.g. :func:`locale.getpreferredencoding(False) ` + and ``"strict"``) to the :term:`filesystem encoding and error handler`. Arguments file should be encoded in UTF-8 instead of ANSI Codepage on Windows. @@ -673,25 +504,8 @@ add_help ^^^^^^^^ By default, ArgumentParser objects add an option which simply displays -the parser's help message. For example, consider a file named -``myprogram.py`` containing the following code:: - - import argparse - parser = argparse.ArgumentParser() - parser.add_argument('--foo', help='foo help') - args = parser.parse_args() - -If ``-h`` or ``--help`` is supplied at the command line, the ArgumentParser -help will be printed: - -.. code-block:: shell-session - - $ python myprogram.py --help - usage: myprogram.py [-h] [--foo FOO] - - options: - -h, --help show this help message and exit - --foo FOO foo help +the parser's help message. If ``-h`` or ``--help`` is supplied at the command +line, the ArgumentParser help will be printed. Occasionally, it may be useful to disable the addition of this help option. This can be achieved by passing ``False`` as the ``add_help=`` argument to @@ -828,12 +642,7 @@ them, though most actions simply add an attribute to the object returned by how the command-line arguments should be handled. The supplied actions are: * ``'store'`` - This just stores the argument's value. This is the default - action. For example:: - - >>> parser = argparse.ArgumentParser() - >>> parser.add_argument('--foo') - >>> parser.parse_args('--foo 1'.split()) - Namespace(foo='1') + action. * ``'store_const'`` - This stores the value specified by the const_ keyword argument; note that the const_ keyword argument defaults to ``None``. The @@ -848,7 +657,7 @@ how the command-line arguments should be handled. The supplied actions are: * ``'store_true'`` and ``'store_false'`` - These are special cases of ``'store_const'`` used for storing the values ``True`` and ``False`` respectively. In addition, they create default values of ``False`` and - ``True`` respectively. For example:: + ``True`` respectively:: >>> parser = argparse.ArgumentParser() >>> parser.add_argument('--foo', action='store_true') @@ -1093,7 +902,7 @@ was not present at the command line:: Namespace(foo=42) If the target namespace already has an attribute set, the action *default* -will not over write it:: +will not overwrite it:: >>> parser = argparse.ArgumentParser() >>> parser.add_argument('--foo', default=42) @@ -1167,7 +976,6 @@ Common built-in types and functions can be used as type converters: parser.add_argument('distance', type=float) parser.add_argument('street', type=ascii) parser.add_argument('code_point', type=ord) - parser.add_argument('source_file', type=open) parser.add_argument('dest_file', type=argparse.FileType('w', encoding='latin-1')) parser.add_argument('datapath', type=pathlib.Path) @@ -1198,10 +1006,11 @@ better reporting than can be given by the ``type`` keyword. A :exc:`FileNotFoundError` exception would not be handled at all. Even :class:`~argparse.FileType` has its limitations for use with the ``type`` -keyword. If one argument uses *FileType* and then a subsequent argument fails, -an error is reported but the file is not automatically closed. In this case, it -would be better to wait until after the parser has run and then use the -:keyword:`with`-statement to manage the files. +keyword. If one argument uses :class:`~argparse.FileType` and then a +subsequent argument fails, an error is reported but the file is not +automatically closed. In this case, it would be better to wait until after +the parser has run and then use the :keyword:`with`-statement to manage the +files. For type checkers that simply check against a fixed set of values, consider using the choices_ keyword instead. @@ -1229,15 +1038,7 @@ if the argument was not one of the acceptable values:: Note that inclusion in the *choices* sequence is checked after any type_ conversions have been performed, so the type of the objects in the *choices* -sequence should match the type_ specified:: - - >>> parser = argparse.ArgumentParser(prog='doors.py') - >>> parser.add_argument('door', type=int, choices=range(1, 4)) - >>> print(parser.parse_args(['3'])) - Namespace(door=3) - >>> parser.parse_args(['4']) - usage: doors.py [-h] {1,2,3} - doors.py: error: argument door: invalid choice: 4 (choose from 1, 2, 3) +sequence should match the type_ specified. Any sequence can be passed as the *choices* value, so :class:`list` objects, :class:`tuple` objects, and custom sequences are all supported. @@ -1287,22 +1088,7 @@ help The ``help`` value is a string containing a brief description of the argument. When a user requests help (usually by using ``-h`` or ``--help`` at the command line), these ``help`` descriptions will be displayed with each -argument:: - - >>> parser = argparse.ArgumentParser(prog='frobble') - >>> parser.add_argument('--foo', action='store_true', - ... help='foo the bars before frobbling') - >>> parser.add_argument('bar', nargs='+', - ... help='one of the bars to be frobbled') - >>> parser.parse_args(['-h']) - usage: frobble [-h] [--foo] bar [bar ...] - - positional arguments: - bar one of the bars to be frobbled - - options: - -h, --help show this help message and exit - --foo foo the bars before frobbling +argument. The ``help`` strings can include various format specifiers to avoid repetition of things like the program name or the argument default_. The available @@ -1455,40 +1241,41 @@ this API may be passed as the ``action`` parameter to type=None, choices=None, required=False, help=None, \ metavar=None) -Action objects are used by an ArgumentParser to represent the information -needed to parse a single argument from one or more strings from the -command line. The Action class must accept the two positional arguments -plus any keyword arguments passed to :meth:`ArgumentParser.add_argument` -except for the ``action`` itself. + Action objects are used by an ArgumentParser to represent the information + needed to parse a single argument from one or more strings from the + command line. The Action class must accept the two positional arguments + plus any keyword arguments passed to :meth:`ArgumentParser.add_argument` + except for the ``action`` itself. -Instances of Action (or return value of any callable to the ``action`` -parameter) should have attributes "dest", "option_strings", "default", "type", -"required", "help", etc. defined. The easiest way to ensure these attributes -are defined is to call ``Action.__init__``. + Instances of Action (or return value of any callable to the ``action`` + parameter) should have attributes "dest", "option_strings", "default", "type", + "required", "help", etc. defined. The easiest way to ensure these attributes + are defined is to call ``Action.__init__``. -Action instances should be callable, so subclasses must override the -``__call__`` method, which should accept four parameters: + Action instances should be callable, so subclasses must override the + ``__call__`` method, which should accept four parameters: -* ``parser`` - The ArgumentParser object which contains this action. + * *parser* - The ArgumentParser object which contains this action. -* ``namespace`` - The :class:`Namespace` object that will be returned by - :meth:`~ArgumentParser.parse_args`. Most actions add an attribute to this - object using :func:`setattr`. + * *namespace* - The :class:`Namespace` object that will be returned by + :meth:`~ArgumentParser.parse_args`. Most actions add an attribute to this + object using :func:`setattr`. -* ``values`` - The associated command-line arguments, with any type conversions - applied. Type conversions are specified with the type_ keyword argument to - :meth:`~ArgumentParser.add_argument`. + * *values* - The associated command-line arguments, with any type conversions + applied. Type conversions are specified with the type_ keyword argument to + :meth:`~ArgumentParser.add_argument`. -* ``option_string`` - The option string that was used to invoke this action. - The ``option_string`` argument is optional, and will be absent if the action - is associated with a positional argument. + * *option_string* - The option string that was used to invoke this action. + The ``option_string`` argument is optional, and will be absent if the action + is associated with a positional argument. -The ``__call__`` method may perform arbitrary actions, but will typically set -attributes on the ``namespace`` based on ``dest`` and ``values``. + The ``__call__`` method may perform arbitrary actions, but will typically set + attributes on the ``namespace`` based on ``dest`` and ``values``. + + Action subclasses can define a ``format_usage`` method that takes no argument + and return a string which will be used when printing the usage of the program. + If such method is not provided, a sensible default will be used. -Action subclasses can define a ``format_usage`` method that takes no argument -and return a string which will be used when printing the usage of the program. -If such method is not provided, a sensible default will be used. The parse_args() method ----------------------- @@ -1683,29 +1470,29 @@ The Namespace object Simple class used by default by :meth:`~ArgumentParser.parse_args` to create an object holding attributes and return it. -This class is deliberately simple, just an :class:`object` subclass with a -readable string representation. If you prefer to have dict-like view of the -attributes, you can use the standard Python idiom, :func:`vars`:: + This class is deliberately simple, just an :class:`object` subclass with a + readable string representation. If you prefer to have dict-like view of the + attributes, you can use the standard Python idiom, :func:`vars`:: - >>> parser = argparse.ArgumentParser() - >>> parser.add_argument('--foo') - >>> args = parser.parse_args(['--foo', 'BAR']) - >>> vars(args) - {'foo': 'BAR'} - -It may also be useful to have an :class:`ArgumentParser` assign attributes to an -already existing object, rather than a new :class:`Namespace` object. This can -be achieved by specifying the ``namespace=`` keyword argument:: - - >>> class C: - ... pass - ... - >>> c = C() - >>> parser = argparse.ArgumentParser() - >>> parser.add_argument('--foo') - >>> parser.parse_args(args=['--foo', 'BAR'], namespace=c) - >>> c.foo - 'BAR' + >>> parser = argparse.ArgumentParser() + >>> parser.add_argument('--foo') + >>> args = parser.parse_args(['--foo', 'BAR']) + >>> vars(args) + {'foo': 'BAR'} + + It may also be useful to have an :class:`ArgumentParser` assign attributes to an + already existing object, rather than a new :class:`Namespace` object. This can + be achieved by specifying the ``namespace=`` keyword argument:: + + >>> class C: + ... pass + ... + >>> c = C() + >>> parser = argparse.ArgumentParser() + >>> parser.add_argument('--foo') + >>> parser.parse_args(args=['--foo', 'BAR'], namespace=c) + >>> c.foo + 'BAR' Other utilities @@ -1719,12 +1506,12 @@ Sub-commands [option_strings], [dest], [required], \ [help], [metavar]) - Many programs split up their functionality into a number of sub-commands, - for example, the ``svn`` program can invoke sub-commands like ``svn + Many programs split up their functionality into a number of subcommands, + for example, the ``svn`` program can invoke subcommands like ``svn checkout``, ``svn update``, and ``svn commit``. Splitting up functionality this way can be a particularly good idea when a program performs several different functions which require different kinds of command-line arguments. - :class:`ArgumentParser` supports the creation of such sub-commands with the + :class:`ArgumentParser` supports the creation of such subcommands with the :meth:`add_subparsers` method. The :meth:`add_subparsers` method is normally called with no arguments and returns a special action object. This object has a single method, :meth:`~_SubParsersAction.add_parser`, which takes a @@ -1733,18 +1520,18 @@ Sub-commands Description of parameters: - * title - title for the sub-parser group in help output; by default + * *title* - title for the sub-parser group in help output; by default "subcommands" if description is provided, otherwise uses title for positional arguments - * description - description for the sub-parser group in help output, by + * *description* - description for the sub-parser group in help output, by default ``None`` - * prog - usage information that will be displayed with sub-command help, + * *prog* - usage information that will be displayed with sub-command help, by default the name of the program and any positional arguments before the subparser argument - * parser_class - class which will be used to create sub-parser instances, by + * *parser_class* - class which will be used to create sub-parser instances, by default the class of the current parser (e.g. ArgumentParser) * action_ - the basic type of action to be taken when this argument is @@ -1758,15 +1545,15 @@ Sub-commands * help_ - help for sub-parser group in help output, by default ``None`` - * metavar_ - string presenting available sub-commands in help; by default it - is ``None`` and presents sub-commands in form {cmd1, cmd2, ..} + * metavar_ - string presenting available subcommands in help; by default it + is ``None`` and presents subcommands in form {cmd1, cmd2, ..} Some example usage:: >>> # create the top-level parser >>> parser = argparse.ArgumentParser(prog='PROG') >>> parser.add_argument('--foo', action='store_true', help='foo help') - >>> subparsers = parser.add_subparsers(help='sub-command help') + >>> subparsers = parser.add_subparsers(help='subcommand help') >>> >>> # create the parser for the "a" command >>> parser_a = subparsers.add_parser('a', help='a help') @@ -1801,7 +1588,7 @@ Sub-commands usage: PROG [-h] [--foo] {a,b} ... positional arguments: - {a,b} sub-command help + {a,b} subcommand help a a help b b help @@ -1862,7 +1649,7 @@ Sub-commands that each subparser knows which Python function it should execute. For example:: - >>> # sub-command functions + >>> # subcommand functions >>> def foo(args): ... print(args.x * args.y) ... @@ -2144,20 +1931,20 @@ Partial parsing .. method:: ArgumentParser.parse_known_args(args=None, namespace=None) -Sometimes a script may only parse a few of the command-line arguments, passing -the remaining arguments on to another script or program. In these cases, the -:meth:`~ArgumentParser.parse_known_args` method can be useful. It works much like -:meth:`~ArgumentParser.parse_args` except that it does not produce an error when -extra arguments are present. Instead, it returns a two item tuple containing -the populated namespace and the list of remaining argument strings. + Sometimes a script may only parse a few of the command-line arguments, passing + the remaining arguments on to another script or program. In these cases, the + :meth:`~ArgumentParser.parse_known_args` method can be useful. It works much like + :meth:`~ArgumentParser.parse_args` except that it does not produce an error when + extra arguments are present. Instead, it returns a two item tuple containing + the populated namespace and the list of remaining argument strings. -:: + :: - >>> parser = argparse.ArgumentParser() - >>> parser.add_argument('--foo', action='store_true') - >>> parser.add_argument('bar') - >>> parser.parse_known_args(['--foo', '--badger', 'BAR', 'spam']) - (Namespace(bar='BAR', foo=True), ['--badger', 'spam']) + >>> parser = argparse.ArgumentParser() + >>> parser.add_argument('--foo', action='store_true') + >>> parser.add_argument('bar') + >>> parser.parse_known_args(['--foo', '--badger', 'BAR', 'spam']) + (Namespace(bar='BAR', foo=True), ['--badger', 'spam']) .. warning:: :ref:`Prefix matching ` rules apply to @@ -2215,90 +2002,38 @@ Intermixed parsing .. method:: ArgumentParser.parse_intermixed_args(args=None, namespace=None) .. method:: ArgumentParser.parse_known_intermixed_args(args=None, namespace=None) -A number of Unix commands allow the user to intermix optional arguments with -positional arguments. The :meth:`~ArgumentParser.parse_intermixed_args` -and :meth:`~ArgumentParser.parse_known_intermixed_args` methods -support this parsing style. - -These parsers do not support all the argparse features, and will raise -exceptions if unsupported features are used. In particular, subparsers, -and mutually exclusive groups that include both -optionals and positionals are not supported. - -The following example shows the difference between -:meth:`~ArgumentParser.parse_known_args` and -:meth:`~ArgumentParser.parse_intermixed_args`: the former returns ``['2', -'3']`` as unparsed arguments, while the latter collects all the positionals -into ``rest``. :: - - >>> parser = argparse.ArgumentParser() - >>> parser.add_argument('--foo') - >>> parser.add_argument('cmd') - >>> parser.add_argument('rest', nargs='*', type=int) - >>> parser.parse_known_args('doit 1 --foo bar 2 3'.split()) - (Namespace(cmd='doit', foo='bar', rest=[1]), ['2', '3']) - >>> parser.parse_intermixed_args('doit 1 --foo bar 2 3'.split()) - Namespace(cmd='doit', foo='bar', rest=[1, 2, 3]) - -:meth:`~ArgumentParser.parse_known_intermixed_args` returns a two item tuple -containing the populated namespace and the list of remaining argument strings. -:meth:`~ArgumentParser.parse_intermixed_args` raises an error if there are any -remaining unparsed argument strings. - -.. versionadded:: 3.7 - -.. _upgrading-optparse-code: - -Upgrading optparse code ------------------------ - -Originally, the :mod:`argparse` module had attempted to maintain compatibility -with :mod:`optparse`. However, :mod:`optparse` was difficult to extend -transparently, particularly with the changes required to support the new -``nargs=`` specifiers and better usage messages. When most everything in -:mod:`optparse` had either been copy-pasted over or monkey-patched, it no -longer seemed practical to try to maintain the backwards compatibility. - -The :mod:`argparse` module improves on the standard library :mod:`optparse` -module in a number of ways including: - -* Handling positional arguments. -* Supporting sub-commands. -* Allowing alternative option prefixes like ``+`` and ``/``. -* Handling zero-or-more and one-or-more style arguments. -* Producing more informative usage messages. -* Providing a much simpler interface for custom ``type`` and ``action``. + A number of Unix commands allow the user to intermix optional arguments with + positional arguments. The :meth:`~ArgumentParser.parse_intermixed_args` + and :meth:`~ArgumentParser.parse_known_intermixed_args` methods + support this parsing style. -A partial upgrade path from :mod:`optparse` to :mod:`argparse`: + These parsers do not support all the argparse features, and will raise + exceptions if unsupported features are used. In particular, subparsers, + and mutually exclusive groups that include both + optionals and positionals are not supported. -* Replace all :meth:`optparse.OptionParser.add_option` calls with - :meth:`ArgumentParser.add_argument` calls. + The following example shows the difference between + :meth:`~ArgumentParser.parse_known_args` and + :meth:`~ArgumentParser.parse_intermixed_args`: the former returns ``['2', + '3']`` as unparsed arguments, while the latter collects all the positionals + into ``rest``. :: -* Replace ``(options, args) = parser.parse_args()`` with ``args = - parser.parse_args()`` and add additional :meth:`ArgumentParser.add_argument` - calls for the positional arguments. Keep in mind that what was previously - called ``options``, now in the :mod:`argparse` context is called ``args``. - -* Replace :meth:`optparse.OptionParser.disable_interspersed_args` - by using :meth:`~ArgumentParser.parse_intermixed_args` instead of - :meth:`~ArgumentParser.parse_args`. - -* Replace callback actions and the ``callback_*`` keyword arguments with - ``type`` or ``action`` arguments. - -* Replace string names for ``type`` keyword arguments with the corresponding - type objects (e.g. int, float, complex, etc). + >>> parser = argparse.ArgumentParser() + >>> parser.add_argument('--foo') + >>> parser.add_argument('cmd') + >>> parser.add_argument('rest', nargs='*', type=int) + >>> parser.parse_known_args('doit 1 --foo bar 2 3'.split()) + (Namespace(cmd='doit', foo='bar', rest=[1]), ['2', '3']) + >>> parser.parse_intermixed_args('doit 1 --foo bar 2 3'.split()) + Namespace(cmd='doit', foo='bar', rest=[1, 2, 3]) -* Replace :class:`optparse.Values` with :class:`Namespace` and - :exc:`optparse.OptionError` and :exc:`optparse.OptionValueError` with - :exc:`ArgumentError`. + :meth:`~ArgumentParser.parse_known_intermixed_args` returns a two item tuple + containing the populated namespace and the list of remaining argument strings. + :meth:`~ArgumentParser.parse_intermixed_args` raises an error if there are any + remaining unparsed argument strings. -* Replace strings with implicit arguments such as ``%default`` or ``%prog`` with - the standard Python syntax to use dictionaries to format strings, that is, - ``%(default)s`` and ``%(prog)s``. + .. versionadded:: 3.7 -* Replace the OptionParser constructor ``version`` argument with a call to - ``parser.add_argument('--version', action='version', version='')``. Exceptions ---------- @@ -2313,3 +2048,12 @@ Exceptions .. exception:: ArgumentTypeError Raised when something goes wrong converting a command line string to a type. + + +.. rubric:: Guides and Tutorials + +.. toctree:: + :maxdepth: 1 + + ../howto/argparse.rst + ../howto/argparse-optparse.rst From 7187d4f3b65a26aca1229f4c987d19c6d3ead0bb Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Wed, 9 Oct 2024 16:16:13 +0200 Subject: [PATCH 028/269] [3.12] gh-101100: Fix Sphinx warnings in `library/unittest.mock.rst` (GH-124106) (#125191) Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> --- Doc/library/unittest.mock.rst | 60 +++++++++++++++++------------------ 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/Doc/library/unittest.mock.rst b/Doc/library/unittest.mock.rst index bb3baa97275e7f..84f34a0696a26c 100644 --- a/Doc/library/unittest.mock.rst +++ b/Doc/library/unittest.mock.rst @@ -68,7 +68,7 @@ available, and then make assertions about how they have been used: 3 >>> thing.method.assert_called_with(3, 4, 5, key='value') -:attr:`side_effect` allows you to perform side effects, including raising an +:attr:`~Mock.side_effect` allows you to perform side effects, including raising an exception when a mock is called: >>> from unittest.mock import Mock @@ -756,8 +756,8 @@ the *new_callable* argument to :func:`patch`. .. attribute:: __class__ - Normally the :attr:`__class__` attribute of an object will return its type. - For a mock object with a :attr:`spec`, ``__class__`` returns the spec class + Normally the :attr:`!__class__` attribute of an object will return its type. + For a mock object with a :attr:`!spec`, :attr:`!__class__` returns the spec class instead. This allows mock objects to pass :func:`isinstance` tests for the object they are replacing / masquerading as: @@ -765,7 +765,7 @@ the *new_callable* argument to :func:`patch`. >>> isinstance(mock, int) True - :attr:`__class__` is assignable to, this allows a mock to pass an + :attr:`!__class__` is assignable to, this allows a mock to pass an :func:`isinstance` check without forcing you to use a spec: >>> mock = Mock() @@ -779,8 +779,8 @@ the *new_callable* argument to :func:`patch`. meaning of :class:`Mock`, with the exception of *return_value* and *side_effect* which have no meaning on a non-callable mock. -Mock objects that use a class or an instance as a :attr:`spec` or -:attr:`spec_set` are able to pass :func:`isinstance` tests: +Mock objects that use a class or an instance as a :attr:`!spec` or +:attr:`!spec_set` are able to pass :func:`isinstance` tests: >>> mock = Mock(spec=SomeClass) >>> isinstance(mock, SomeClass) @@ -1149,7 +1149,7 @@ Calls made to the object will be recorded in the attributes like :attr:`~Mock.call_args` and :attr:`~Mock.call_args_list`. If :attr:`~Mock.side_effect` is set then it will be called after the call has -been recorded, so if :attr:`side_effect` raises an exception the call is still +been recorded, so if :attr:`!side_effect` raises an exception the call is still recorded. The simplest way to make a mock raise an exception when called is to make @@ -1170,8 +1170,8 @@ The simplest way to make a mock raise an exception when called is to make >>> m.mock_calls [call(1, 2, 3), call('two', 'three', 'four')] -If :attr:`side_effect` is a function then whatever that function returns is what -calls to the mock return. The :attr:`side_effect` function is called with the +If :attr:`~Mock.side_effect` is a function then whatever that function returns is what +calls to the mock return. The :attr:`!side_effect` function is called with the same arguments as the mock. This allows you to vary the return value of the call dynamically, based on the input: @@ -1188,7 +1188,7 @@ call dynamically, based on the input: If you want the mock to still return the default return value (a new mock), or any set return value, then there are two ways of doing this. Either return -:attr:`mock.return_value` from inside :attr:`side_effect`, or return :data:`DEFAULT`: +:attr:`~Mock.return_value` from inside :attr:`~Mock.side_effect`, or return :data:`DEFAULT`: >>> m = MagicMock() >>> def side_effect(*args, **kwargs): @@ -1205,8 +1205,8 @@ any set return value, then there are two ways of doing this. Either return >>> m() 3 -To remove a :attr:`side_effect`, and return to the default behaviour, set the -:attr:`side_effect` to ``None``: +To remove a :attr:`~Mock.side_effect`, and return to the default behaviour, set the +:attr:`!side_effect` to ``None``: >>> m = MagicMock(return_value=6) >>> def side_effect(*args, **kwargs): @@ -1219,7 +1219,7 @@ To remove a :attr:`side_effect`, and return to the default behaviour, set the >>> m() 6 -The :attr:`side_effect` can also be any iterable object. Repeated calls to the mock +The :attr:`~Mock.side_effect` can also be any iterable object. Repeated calls to the mock will return values from the iterable (until the iterable is exhausted and a :exc:`StopIteration` is raised): @@ -1260,7 +1260,7 @@ objects of any type. You may want a mock object to return ``False`` to a :func:`hasattr` call, or raise an :exc:`AttributeError` when an attribute is fetched. You can do this by providing -an object as a :attr:`spec` for a mock, but that isn't always convenient. +an object as a :attr:`!spec` for a mock, but that isn't always convenient. You "block" attributes by deleting them. Once deleted, accessing an attribute will raise an :exc:`AttributeError`. @@ -1429,7 +1429,7 @@ patch If you are patching builtins in a module then you don't need to pass ``create=True``, it will be added by default. - Patch can be used as a :class:`TestCase` class decorator. It works by + Patch can be used as a :class:`~unittest.TestCase` class decorator. It works by decorating each test method in the class. This reduces the boilerplate code when your test methods share a common patchings set. :func:`patch` finds tests by looking for method names that start with ``patch.TEST_PREFIX``. @@ -1467,7 +1467,7 @@ If the class is instantiated multiple times you could use can set the *return_value* to be anything you want. To configure return values on methods of *instances* on the patched class -you must do this on the :attr:`return_value`. For example:: +you must do this on the :attr:`~Mock.return_value`. For example:: >>> class Class: ... def method(self): @@ -1788,13 +1788,13 @@ context manager is a dictionary where created mocks are keyed by name:: patch methods: start and stop ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -All the patchers have :meth:`start` and :meth:`stop` methods. These make it simpler to do +All the patchers have :meth:`!start` and :meth:`!stop` methods. These make it simpler to do patching in ``setUp`` methods or where you want to do multiple patches without nesting decorators or with statements. To use them call :func:`patch`, :func:`patch.object` or :func:`patch.dict` as normal and keep a reference to the returned ``patcher`` object. You can then -call :meth:`start` to put the patch in place and :meth:`stop` to undo it. +call :meth:`!start` to put the patch in place and :meth:`!stop` to undo it. If you are using :func:`patch` to create a mock for you then it will be returned by the call to ``patcher.start``. :: @@ -1811,7 +1811,7 @@ the call to ``patcher.start``. :: A typical use case for this might be for doing multiple patches in the ``setUp`` -method of a :class:`TestCase`:: +method of a :class:`~unittest.TestCase`:: >>> class MyTest(unittest.TestCase): ... def setUp(self): @@ -2484,7 +2484,7 @@ behaviour you can switch it off by setting the module level switch Alternatively you can just use ``vars(my_mock)`` (instance members) and ``dir(type(my_mock))`` (type members) to bypass the filtering irrespective of -:const:`mock.FILTER_DIR`. +:const:`FILTER_DIR`. mock_open @@ -2499,7 +2499,7 @@ mock_open default) then a :class:`MagicMock` will be created for you, with the API limited to methods or attributes available on standard file handles. - *read_data* is a string for the :meth:`~io.IOBase.read`, + *read_data* is a string for the :meth:`~io.RawIOBase.read`, :meth:`~io.IOBase.readline`, and :meth:`~io.IOBase.readlines` methods of the file handle to return. Calls to those methods will take data from *read_data* until it is depleted. The mock of these methods is pretty @@ -2511,7 +2511,7 @@ mock_open .. versionchanged:: 3.4 Added :meth:`~io.IOBase.readline` and :meth:`~io.IOBase.readlines` support. - The mock of :meth:`~io.IOBase.read` changed to consume *read_data* rather + The mock of :meth:`~io.RawIOBase.read` changed to consume *read_data* rather than returning it on each call. .. versionchanged:: 3.5 @@ -2563,7 +2563,7 @@ And for reading files:: Autospeccing ~~~~~~~~~~~~ -Autospeccing is based on the existing :attr:`spec` feature of mock. It limits the +Autospeccing is based on the existing :attr:`!spec` feature of mock. It limits the api of mocks to the api of an original object (the spec), but it is recursive (implemented lazily) so that attributes of mocks only have the same api as the attributes of the spec. In addition mocked functions / methods have the @@ -2588,8 +2588,8 @@ unit tests. Testing everything in isolation is all fine and dandy, but if you don't test how your units are "wired together" there is still lots of room for bugs that tests might have caught. -:mod:`mock` already provides a feature to help with this, called speccing. If you -use a class or instance as the :attr:`spec` for a mock then you can only access +:mod:`unittest.mock` already provides a feature to help with this, called speccing. If you +use a class or instance as the :attr:`!spec` for a mock then you can only access attributes on the mock that exist on the real class: >>> from urllib import request @@ -2627,7 +2627,7 @@ Here's an example of it in use:: >>> mock_request.Request -You can see that :class:`request.Request` has a spec. :class:`request.Request` takes two +You can see that :class:`!request.Request` has a spec. :class:`!request.Request` takes two arguments in the constructor (one of which is *self*). Here's what happens if we try to call it incorrectly:: @@ -2643,8 +2643,8 @@ specced mocks):: >>> req -:class:`Request` objects are not callable, so the return value of instantiating our -mocked out :class:`request.Request` is a non-callable mock. With the spec in place +:class:`!Request` objects are not callable, so the return value of instantiating our +mocked out :class:`!request.Request` is a non-callable mock. With the spec in place any typos in our asserts will raise the correct error:: >>> req.add_header('spam', 'eggs') @@ -2796,8 +2796,8 @@ Sealing mocks .. versionadded:: 3.7 -Order of precedence of :attr:`side_effect`, :attr:`return_value` and *wraps* ----------------------------------------------------------------------------- +Order of precedence of :attr:`!side_effect`, :attr:`!return_value` and *wraps* +------------------------------------------------------------------------------ The order of their precedence is: From 046687c620f5bff8fc9dead066ab30e9802ba5b0 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Wed, 9 Oct 2024 18:54:38 +0200 Subject: [PATCH 029/269] [3.12] docs: in venv table use executable name (GH-124315) (#125171) (cherry picked from commit 7f93dbf6fec152888727a0f25a3aa030d1fe27ca) --- Doc/library/venv.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/venv.rst b/Doc/library/venv.rst index 546ce3716d28b9..9eb0e9d191c7cd 100644 --- a/Doc/library/venv.rst +++ b/Doc/library/venv.rst @@ -205,7 +205,7 @@ containing the virtual environment): | +------------+--------------------------------------------------+ | | csh/tcsh | :samp:`$ source {}/bin/activate.csh` | | +------------+--------------------------------------------------+ -| | PowerShell | :samp:`$ {}/bin/Activate.ps1` | +| | pwsh | :samp:`$ {}/bin/Activate.ps1` | +-------------+------------+--------------------------------------------------+ | Windows | cmd.exe | :samp:`C:\\> {}\\Scripts\\activate.bat` | | +------------+--------------------------------------------------+ From cba37762acfb82ca7fdf4b2c9ea3ea54595b7571 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Wed, 9 Oct 2024 20:18:38 +0100 Subject: [PATCH 030/269] [3.12] gh-101100: Consolidate documentation on `ModuleType` attributes (#124709) (#125211) Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Co-authored-by: Barry Warsaw Co-authored-by: Jelle Zijlstra --- Doc/c-api/import.rst | 26 +-- Doc/c-api/module.rst | 22 +- Doc/deprecations/pending-removal-in-3.14.rst | 7 + Doc/glossary.rst | 11 +- Doc/library/ast.rst | 2 +- Doc/library/importlib.rst | 87 +++---- Doc/library/pkgutil.rst | 3 +- Doc/library/sys.rst | 3 +- Doc/library/types.rst | 65 +----- Doc/reference/datamodel.rst | 232 ++++++++++++++++--- Doc/reference/import.rst | 161 +++---------- Doc/tutorial/modules.rst | 5 +- Doc/whatsnew/2.6.rst | 8 +- Doc/whatsnew/3.0.rst | 4 +- Doc/whatsnew/3.12.rst | 13 +- Doc/whatsnew/3.2.rst | 4 +- Doc/whatsnew/3.4.rst | 3 +- Doc/whatsnew/3.5.rst | 4 +- Misc/NEWS.d/3.10.0a2.rst | 4 +- Misc/NEWS.d/3.11.0a5.rst | 4 +- Misc/NEWS.d/3.12.0a1.rst | 4 +- 21 files changed, 348 insertions(+), 324 deletions(-) diff --git a/Doc/c-api/import.rst b/Doc/c-api/import.rst index b8687c61c26d0d..d6370bc9645ff2 100644 --- a/Doc/c-api/import.rst +++ b/Doc/c-api/import.rst @@ -120,14 +120,14 @@ Importing Modules such modules have no way to know that the module object is an unknown (and probably damaged with respect to the module author's intents) state. - The module's :attr:`__spec__` and :attr:`__loader__` will be set, if - not set already, with the appropriate values. The spec's loader will - be set to the module's ``__loader__`` (if set) and to an instance of - :class:`~importlib.machinery.SourceFileLoader` otherwise. + The module's :attr:`~module.__spec__` and :attr:`~module.__loader__` will be + set, if not set already, with the appropriate values. The spec's loader + will be set to the module's :attr:`!__loader__` (if set) and to an instance + of :class:`~importlib.machinery.SourceFileLoader` otherwise. - The module's :attr:`__file__` attribute will be set to the code object's - :attr:`~codeobject.co_filename`. If applicable, :attr:`__cached__` will also - be set. + The module's :attr:`~module.__file__` attribute will be set to the code + object's :attr:`~codeobject.co_filename`. If applicable, + :attr:`~module.__cached__` will also be set. This function will reload the module if it was already imported. See :c:func:`PyImport_ReloadModule` for the intended way to reload a module. @@ -139,29 +139,29 @@ Importing Modules :c:func:`PyImport_ExecCodeModuleWithPathnames`. .. versionchanged:: 3.12 - The setting of :attr:`__cached__` and :attr:`__loader__` is - deprecated. See :class:`~importlib.machinery.ModuleSpec` for + The setting of :attr:`~module.__cached__` and :attr:`~module.__loader__` + is deprecated. See :class:`~importlib.machinery.ModuleSpec` for alternatives. .. c:function:: PyObject* PyImport_ExecCodeModuleEx(const char *name, PyObject *co, const char *pathname) - Like :c:func:`PyImport_ExecCodeModule`, but the :attr:`__file__` attribute of - the module object is set to *pathname* if it is non-``NULL``. + Like :c:func:`PyImport_ExecCodeModule`, but the :attr:`~module.__file__` + attribute of the module object is set to *pathname* if it is non-``NULL``. See also :c:func:`PyImport_ExecCodeModuleWithPathnames`. .. c:function:: PyObject* PyImport_ExecCodeModuleObject(PyObject *name, PyObject *co, PyObject *pathname, PyObject *cpathname) - Like :c:func:`PyImport_ExecCodeModuleEx`, but the :attr:`__cached__` + Like :c:func:`PyImport_ExecCodeModuleEx`, but the :attr:`~module.__cached__` attribute of the module object is set to *cpathname* if it is non-``NULL``. Of the three functions, this is the preferred one to use. .. versionadded:: 3.3 .. versionchanged:: 3.12 - Setting :attr:`__cached__` is deprecated. See + Setting :attr:`~module.__cached__` is deprecated. See :class:`~importlib.machinery.ModuleSpec` for alternatives. diff --git a/Doc/c-api/module.rst b/Doc/c-api/module.rst index 910137f6302006..cfa6a1a985245e 100644 --- a/Doc/c-api/module.rst +++ b/Doc/c-api/module.rst @@ -37,18 +37,19 @@ Module Objects single: __package__ (module attribute) single: __loader__ (module attribute) - Return a new module object with the :attr:`__name__` attribute set to *name*. - The module's :attr:`__name__`, :attr:`__doc__`, :attr:`__package__`, and - :attr:`__loader__` attributes are filled in (all but :attr:`__name__` are set - to ``None``); the caller is responsible for providing a :attr:`__file__` - attribute. + Return a new module object with :attr:`module.__name__` set to *name*. + The module's :attr:`!__name__`, :attr:`~module.__doc__`, + :attr:`~module.__package__` and :attr:`~module.__loader__` attributes are + filled in (all but :attr:`!__name__` are set to ``None``). The caller is + responsible for setting a :attr:`~module.__file__` attribute. Return ``NULL`` with an exception set on error. .. versionadded:: 3.3 .. versionchanged:: 3.4 - :attr:`__package__` and :attr:`__loader__` are set to ``None``. + :attr:`~module.__package__` and :attr:`~module.__loader__` are now set to + ``None``. .. c:function:: PyObject* PyModule_New(const char *name) @@ -77,8 +78,9 @@ Module Objects single: __name__ (module attribute) single: SystemError (built-in exception) - Return *module*'s :attr:`__name__` value. If the module does not provide one, - or if it is not a string, :exc:`SystemError` is raised and ``NULL`` is returned. + Return *module*'s :attr:`~module.__name__` value. If the module does not + provide one, or if it is not a string, :exc:`SystemError` is raised and + ``NULL`` is returned. .. versionadded:: 3.3 @@ -108,8 +110,8 @@ Module Objects single: SystemError (built-in exception) Return the name of the file from which *module* was loaded using *module*'s - :attr:`__file__` attribute. If this is not defined, or if it is not a - unicode string, raise :exc:`SystemError` and return ``NULL``; otherwise return + :attr:`~module.__file__` attribute. If this is not defined, or if it is not a + string, raise :exc:`SystemError` and return ``NULL``; otherwise return a reference to a Unicode object. .. versionadded:: 3.2 diff --git a/Doc/deprecations/pending-removal-in-3.14.rst b/Doc/deprecations/pending-removal-in-3.14.rst index d1ad3300a9f6d2..5f01e093ce1b8b 100644 --- a/Doc/deprecations/pending-removal-in-3.14.rst +++ b/Doc/deprecations/pending-removal-in-3.14.rst @@ -1,6 +1,13 @@ Pending Removal in Python 3.14 ------------------------------ +* The import system: + + * Setting :attr:`~module.__loader__` on a module while + failing to set :attr:`__spec__.loader ` + is deprecated. In Python 3.14, :attr:`!__loader__` will cease to be set or + taken into consideration by the import system or the standard library. + * :mod:`argparse`: The *type*, *choices*, and *metavar* parameters of :class:`!argparse.BooleanOptionalAction` are deprecated and will be removed in 3.14. diff --git a/Doc/glossary.rst b/Doc/glossary.rst index b233967adc9e6a..9b29699e866433 100644 --- a/Doc/glossary.rst +++ b/Doc/glossary.rst @@ -437,7 +437,7 @@ Glossary ` for use with :data:`sys.meta_path`, and :term:`path entry finders ` for use with :data:`sys.path_hooks`. - See :ref:`importsystem` and :mod:`importlib` for much more detail. + See :ref:`finders-and-loaders` and :mod:`importlib` for much more detail. floor division Mathematical division that rounds down to nearest integer. The floor @@ -750,8 +750,11 @@ Glossary loader An object that loads a module. It must define a method named :meth:`load_module`. A loader is typically returned by a - :term:`finder`. See :pep:`302` for details and - :class:`importlib.abc.Loader` for an :term:`abstract base class`. + :term:`finder`. See also: + + * :ref:`finders-and-loaders` + * :class:`importlib.abc.Loader` + * :pep:`302` locale encoding On Unix, it is the encoding of the LC_CTYPE locale. It can be set with @@ -821,6 +824,8 @@ Glossary A namespace containing the import-related information used to load a module. An instance of :class:`importlib.machinery.ModuleSpec`. + See also :ref:`module-specs`. + MRO See :term:`method resolution order`. diff --git a/Doc/library/ast.rst b/Doc/library/ast.rst index e6010f3c64e352..6a98d9b3c1167e 100644 --- a/Doc/library/ast.rst +++ b/Doc/library/ast.rst @@ -889,7 +889,7 @@ Statements (indicating a "simple" target). A "simple" target consists solely of a :class:`Name` node that does not appear between parentheses; all other targets are considered complex. Only simple targets appear in - the :attr:`__annotations__` dictionary of modules and classes. + the :attr:`~object.__annotations__` dictionary of modules and classes. .. doctest:: diff --git a/Doc/library/importlib.rst b/Doc/library/importlib.rst index 7ecfc6ad3c4e1c..1dacbe64b748db 100644 --- a/Doc/library/importlib.rst +++ b/Doc/library/importlib.rst @@ -249,7 +249,7 @@ ABC hierarchy:: An abstract method for finding a :term:`spec ` for the specified module. If this is a top-level import, *path* will be ``None``. Otherwise, this is a search for a subpackage or - module and *path* will be the value of :attr:`__path__` from the + module and *path* will be the value of :attr:`~module.__path__` from the parent package. If a spec cannot be found, ``None`` is returned. When passed in, ``target`` is a module object that the finder may use to make a more educated guess about what spec to return. @@ -355,34 +355,12 @@ ABC hierarchy:: (note that some of these attributes can change when a module is reloaded): - - :attr:`__name__` - The module's fully qualified name. - It is ``'__main__'`` for an executed module. - - - :attr:`__file__` - The location the :term:`loader` used to load the module. - For example, for modules loaded from a .py file this is the filename. - It is not set on all modules (e.g. built-in modules). - - - :attr:`__cached__` - The filename of a compiled version of the module's code. - It is not set on all modules (e.g. built-in modules). - - - :attr:`__path__` - The list of locations where the package's submodules will be found. - Most of the time this is a single directory. - The import system passes this attribute to ``__import__()`` and to finders - in the same way as :data:`sys.path` but just for the package. - It is not set on non-package modules so it can be used - as an indicator that the module is a package. - - - :attr:`__package__` - The fully qualified name of the package the module is in (or the - empty string for a top-level module). - If the module is a package then this is the same as :attr:`__name__`. - - - :attr:`__loader__` - The :term:`loader` used to load the module. + - :attr:`module.__name__` + - :attr:`module.__file__` + - :attr:`module.__cached__` + - :attr:`module.__path__` + - :attr:`module.__package__` + - :attr:`module.__loader__` *(deprecated)* When :meth:`exec_module` is available then backwards-compatible functionality is provided. @@ -418,7 +396,8 @@ ABC hierarchy:: can implement this abstract method to give direct access to the data stored. :exc:`OSError` is to be raised if the *path* cannot be found. The *path* is expected to be constructed using a module's - :attr:`__file__` attribute or an item from a package's :attr:`__path__`. + :attr:`~module.__file__` attribute or an item from a package's + :attr:`~module.__path__`. .. versionchanged:: 3.4 Raises :exc:`OSError` instead of :exc:`NotImplementedError`. @@ -505,9 +484,9 @@ ABC hierarchy:: .. abstractmethod:: get_filename(fullname) - An abstract method that is to return the value of :attr:`__file__` for - the specified module. If no path is available, :exc:`ImportError` is - raised. + An abstract method that is to return the value of + :attr:`~module.__file__` for the specified module. If no path is + available, :exc:`ImportError` is raised. If source code is available, then the method should return the path to the source file, regardless of whether a bytecode was used to load the @@ -1166,43 +1145,45 @@ find and load modules. .. class:: ModuleSpec(name, loader, *, origin=None, loader_state=None, is_package=None) A specification for a module's import-system-related state. This is - typically exposed as the module's :attr:`__spec__` attribute. Many + typically exposed as the module's :attr:`~module.__spec__` attribute. Many of these attributes are also available directly on a module: for example, ``module.__spec__.origin == module.__file__``. Note, however, that while the *values* are usually equivalent, they can differ since there is - no synchronization between the two objects. For example, it is possible to update - the module's :attr:`__file__` at runtime and this will not be automatically - reflected in the module's :attr:`__spec__.origin`, and vice versa. + no synchronization between the two objects. For example, it is possible to + update the module's :attr:`~module.__file__` at runtime and this will not be + automatically reflected in the module's + :attr:`__spec__.origin `, and vice versa. .. versionadded:: 3.4 .. attribute:: name - The module's fully qualified name - (see :attr:`__name__` attributes on modules). + The module's fully qualified name (see :attr:`module.__name__`). The :term:`finder` should always set this attribute to a non-empty string. .. attribute:: loader - The :term:`loader` used to load the module - (see :attr:`__loader__` attributes on modules). + The :term:`loader` used to load the module (see :attr:`module.__loader__`). The :term:`finder` should always set this attribute. .. attribute:: origin The location the :term:`loader` should use to load the module - (see :attr:`__file__` attributes on modules). - For example, for modules loaded from a .py file this is the filename. + (see :attr:`module.__file__`). + For example, for modules loaded from a ``.py`` file this is the filename. The :term:`finder` should always set this attribute to a meaningful value for the :term:`loader` to use. In the uncommon case that there is not one (like for namespace packages), it should be set to ``None``. .. attribute:: submodule_search_locations - The list of locations where the package's submodules will be found - (see :attr:`__path__` attributes on modules). - Most of the time this is a single directory. - The :term:`finder` should set this attribute to a list, even an empty one, to indicate + A (possibly empty) :term:`sequence` of strings enumerating the locations + in which a package's submodules will be found + (see :attr:`module.__path__`). Most of the time there will only be a + single directory in this list. + + The :term:`finder` should set this attribute to a sequence, even an empty + one, to indicate to the import system that the module is a package. It should be set to ``None`` for non-package modules. It is set automatically later to a special object for namespace packages. @@ -1216,7 +1197,7 @@ find and load modules. .. attribute:: cached The filename of a compiled version of the module's code - (see :attr:`__cached__` attributes on modules). + (see :attr:`module.__cached__`). The :term:`finder` should always set this attribute but it may be ``None`` for modules that do not need compiled code stored. @@ -1224,14 +1205,14 @@ find and load modules. (Read-only) The fully qualified name of the package the module is in (or the empty string for a top-level module). - See :attr:`__package__` attributes on modules. + See :attr:`module.__package__`. If the module is a package then this is the same as :attr:`name`. .. attribute:: has_location ``True`` if the spec's :attr:`origin` refers to a loadable location, - ``False`` otherwise. This value impacts how :attr:`origin` is interpreted - and how the module's :attr:`__file__` is populated. + ``False`` otherwise. This value impacts how :attr:`!origin` is interpreted + and how the module's :attr:`~module.__file__` is populated. :mod:`importlib.util` -- Utility code for importers @@ -1353,8 +1334,8 @@ an :term:`importer`. .. versionchanged:: 3.7 Raises :exc:`ModuleNotFoundError` instead of :exc:`AttributeError` if - **package** is in fact not a package (i.e. lacks a :attr:`__path__` - attribute). + **package** is in fact not a package (i.e. lacks a + :attr:`~module.__path__` attribute). .. function:: module_from_spec(spec) diff --git a/Doc/library/pkgutil.rst b/Doc/library/pkgutil.rst index f095cc84173737..4a39d53a5f1440 100644 --- a/Doc/library/pkgutil.rst +++ b/Doc/library/pkgutil.rst @@ -26,7 +26,8 @@ support. __path__ = extend_path(__path__, __name__) For each directory on :data:`sys.path` that has a subdirectory that matches the - package name, add the subdirectory to the package's :attr:`__path__`. This is useful + package name, add the subdirectory to the package's + :attr:`~module.__path__`. This is useful if one wants to distribute different parts of a single logical package as multiple directories. diff --git a/Doc/library/sys.rst b/Doc/library/sys.rst index 40d0ef80a3fe43..bf2ea94f23f84d 100644 --- a/Doc/library/sys.rst +++ b/Doc/library/sys.rst @@ -1239,7 +1239,8 @@ always available. that implement Python's default import semantics. The :meth:`~importlib.abc.MetaPathFinder.find_spec` method is called with at least the absolute name of the module being imported. If the module to be - imported is contained in a package, then the parent package's :attr:`__path__` + imported is contained in a package, then the parent package's + :attr:`~module.__path__` attribute is passed in as a second argument. The method returns a :term:`module spec`, or ``None`` if the module cannot be found. diff --git a/Doc/library/types.rst b/Doc/library/types.rst index 00a126ef6de306..ce31a24888686c 100644 --- a/Doc/library/types.rst +++ b/Doc/library/types.rst @@ -260,63 +260,18 @@ Standard names are defined for the following types: The type of :term:`modules `. The constructor takes the name of the module to be created and optionally its :term:`docstring`. - .. note:: - Use :func:`importlib.util.module_from_spec` to create a new module if you - wish to set the various import-controlled attributes. - - .. attribute:: __doc__ - - The :term:`docstring` of the module. Defaults to ``None``. - - .. attribute:: __loader__ - - The :term:`loader` which loaded the module. Defaults to ``None``. - - This attribute is to match :attr:`importlib.machinery.ModuleSpec.loader` - as stored in the :attr:`__spec__` object. - - .. note:: - A future version of Python may stop setting this attribute by default. - To guard against this potential change, preferably read from the - :attr:`__spec__` attribute instead or use - ``getattr(module, "__loader__", None)`` if you explicitly need to use - this attribute. - - .. versionchanged:: 3.4 - Defaults to ``None``. Previously the attribute was optional. - - .. attribute:: __name__ - - The name of the module. Expected to match - :attr:`importlib.machinery.ModuleSpec.name`. - - .. attribute:: __package__ - - Which :term:`package` a module belongs to. If the module is top-level - (i.e. not a part of any specific package) then the attribute should be set - to ``''``, else it should be set to the name of the package (which can be - :attr:`__name__` if the module is a package itself). Defaults to ``None``. - - This attribute is to match :attr:`importlib.machinery.ModuleSpec.parent` - as stored in the :attr:`__spec__` object. - - .. note:: - A future version of Python may stop setting this attribute by default. - To guard against this potential change, preferably read from the - :attr:`__spec__` attribute instead or use - ``getattr(module, "__package__", None)`` if you explicitly need to use - this attribute. - - .. versionchanged:: 3.4 - Defaults to ``None``. Previously the attribute was optional. - - .. attribute:: __spec__ - - A record of the module's import-system-related state. Expected to be an - instance of :class:`importlib.machinery.ModuleSpec`. + .. seealso:: - .. versionadded:: 3.4 + :ref:`Documentation on module objects ` + Provides details on the special attributes that can be found on + instances of :class:`!ModuleType`. + :func:`importlib.util.module_from_spec` + Modules created using the :class:`!ModuleType` constructor are + created with many of their special attributes unset or set to default + values. :func:`!module_from_spec` provides a more robust way of + creating :class:`!ModuleType` instances which ensures the various + attributes are set appropriately. .. data:: EllipsisType diff --git a/Doc/reference/datamodel.rst b/Doc/reference/datamodel.rst index d1993fedb0ca44..7c0719bd93d5da 100644 --- a/Doc/reference/datamodel.rst +++ b/Doc/reference/datamodel.rst @@ -849,6 +849,8 @@ Instances of arbitrary classes can be made callable by defining a :meth:`~object.__call__` method in their class. +.. _module-objects: + Modules ------- @@ -874,46 +876,222 @@ Attribute assignment updates the module's namespace dictionary, e.g., .. index:: single: __name__ (module attribute) - single: __doc__ (module attribute) + single: __spec__ (module attribute) + single: __package__ (module attribute) + single: __loader__ (module attribute) + single: __path__ (module attribute) single: __file__ (module attribute) + single: __cached__ (module attribute) + single: __doc__ (module attribute) single: __annotations__ (module attribute) pair: module; namespace -Predefined (writable) attributes: +.. _import-mod-attrs: + +Import-related attributes on module objects +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Module objects have the following attributes that relate to the +:ref:`import system `. When a module is created using the machinery associated +with the import system, these attributes are filled in based on the module's +:term:`spec `, before the :term:`loader` executes and loads the +module. + +To create a module dynamically rather than using the import system, +it's recommended to use :func:`importlib.util.module_from_spec`, +which will set the various import-controlled attributes to appropriate values. +It's also possible to use the :class:`types.ModuleType` constructor to create +modules directly, but this technique is more error-prone, as most attributes +must be manually set on the module object after it has been created when using +this approach. + +.. caution:: + + With the exception of :attr:`~module.__name__`, it is **strongly** + recommended that you rely on :attr:`~module.__spec__` and its attributes + instead of any of the other individual attributes listed in this subsection. + Note that updating an attribute on :attr:`!__spec__` will not update the + corresponding attribute on the module itself: + + .. doctest:: + + >>> import typing + >>> typing.__name__, typing.__spec__.name + ('typing', 'typing') + >>> typing.__spec__.name = 'spelling' + >>> typing.__name__, typing.__spec__.name + ('typing', 'spelling') + >>> typing.__name__ = 'keyboard_smashing' + >>> typing.__name__, typing.__spec__.name + ('keyboard_smashing', 'spelling') - :attr:`__name__` - The module's name. +.. attribute:: module.__name__ - :attr:`__doc__` - The module's documentation string, or ``None`` if - unavailable. + The name used to uniquely identify the module in the import system. + For a directly executed module, this will be set to ``"__main__"``. + + This attribute must be set to the fully qualified name of the module. + It is expected to match the value of + :attr:`module.__spec__.name `. + +.. attribute:: module.__spec__ + + A record of the module's import-system-related state. + + Set to the :class:`module spec ` that was + used when importing the module. See :ref:`module-specs` for more details. + + .. versionadded:: 3.4 + +.. attribute:: module.__package__ + + The :term:`package` a module belongs to. + + If the module is top-level (that is, not a part of any specific package) + then the attribute should be set to ``''`` (the empty string). Otherwise, + it should be set to the name of the module's package (which can be equal to + :attr:`module.__name__` if the module itself is a package). See :pep:`366` + for further details. + + This attribute is used instead of :attr:`~module.__name__` to calculate + explicit relative imports for main modules. It defaults to ``None`` for + modules created dynamically using the :class:`types.ModuleType` constructor; + use :func:`importlib.util.module_from_spec` instead to ensure the attribute + is set to a :class:`str`. + + It is **strongly** recommended that you use + :attr:`module.__spec__.parent ` + instead of :attr:`!module.__package__`. :attr:`__package__` is now only used + as a fallback if :attr:`!__spec__.parent` is not set, and this fallback + path is deprecated. + + .. versionchanged:: 3.4 + This attribute now defaults to ``None`` for modules created dynamically + using the :class:`types.ModuleType` constructor. + Previously the attribute was optional. - :attr:`__file__` - The pathname of the file from which the - module was loaded, if it was loaded from a file. - The :attr:`__file__` - attribute may be missing for certain types of modules, such as C modules - that are statically linked into the interpreter. For extension modules - loaded dynamically from a shared library, it's the pathname of the shared - library file. + .. versionchanged:: 3.6 + The value of :attr:`!__package__` is expected to be the same as + :attr:`__spec__.parent `. + :attr:`__package__` is now only used as a fallback during import + resolution if :attr:`!__spec__.parent` is not defined. - :attr:`__annotations__` - A dictionary containing - :term:`variable annotations ` collected during - module body execution. For best practices on working - with :attr:`__annotations__`, please see :ref:`annotations-howto`. + .. versionchanged:: 3.10 + :exc:`ImportWarning` is raised if an import resolution falls back to + :attr:`!__package__` instead of + :attr:`__spec__.parent `. + + .. versionchanged:: 3.12 + Raise :exc:`DeprecationWarning` instead of :exc:`ImportWarning` when + falling back to :attr:`!__package__` during import resolution. + +.. attribute:: module.__loader__ + + The :term:`loader` object that the import machinery used to load the module. + + This attribute is mostly useful for introspection, but can be used for + additional loader-specific functionality, for example getting data + associated with a loader. + + :attr:`!__loader__` defaults to ``None`` for modules created dynamically + using the :class:`types.ModuleType` constructor; + use :func:`importlib.util.module_from_spec` instead to ensure the attribute + is set to a :term:`loader` object. + + It is **strongly** recommended that you use + :attr:`module.__spec__.loader ` + instead of :attr:`!module.__loader__`. + + .. versionchanged:: 3.4 + This attribute now defaults to ``None`` for modules created dynamically + using the :class:`types.ModuleType` constructor. + Previously the attribute was optional. + + .. deprecated-removed:: 3.12 3.14 + Setting :attr:`!__loader__` on a module while failing to set + :attr:`!__spec__.loader` is deprecated. In Python 3.14, + :attr:`!__loader__` will cease to be set or taken into consideration by + the import system or the standard library. + +.. attribute:: module.__path__ + + A (possibly empty) :term:`sequence` of strings enumerating the locations + where the package's submodules will be found. Non-package modules should + not have a :attr:`!__path__` attribute. See :ref:`package-path-rules` for + more details. + + It is **strongly** recommended that you use + :attr:`module.__spec__.submodule_search_locations ` + instead of :attr:`!module.__path__`. + +.. attribute:: module.__file__ +.. attribute:: module.__cached__ + + :attr:`!__file__` and :attr:`!__cached__` are both optional attributes that + may or may not be set. Both attributes should be a :class:`str` when they + are available. + + :attr:`!__file__` indicates the pathname of the file from which the module + was loaded (if loaded from a file), or the pathname of the shared library + file for extension modules loaded dynamically from a shared library. + It might be missing for certain types of modules, such as C modules that are + statically linked into the interpreter, and the + :ref:`import system ` may opt to leave it unset if it + has no semantic meaning (for example, a module loaded from a database). + + If :attr:`!__file__` is set then the :attr:`!__cached__` attribute might + also be set, which is the path to any compiled version of + the code (for example, a byte-compiled file). The file does not need to exist + to set this attribute; the path can simply point to where the + compiled file *would* exist (see :pep:`3147`). + + Note that :attr:`!__cached__` may be set even if :attr:`!__file__` is not + set. However, that scenario is quite atypical. Ultimately, the + :term:`loader` is what makes use of the module spec provided by the + :term:`finder` (from which :attr:`!__file__` and :attr:`!__cached__` are + derived). So if a loader can load from a cached module but otherwise does + not load from a file, that atypical scenario may be appropriate. + + It is **strongly** recommended that you use + :attr:`module.__spec__.cached ` + instead of :attr:`!module.__cached__`. + +Other writable attributes on module objects +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +As well as the import-related attributes listed above, module objects also have +the following writable attributes: + +.. attribute:: module.__doc__ + + The module's documentation string, or ``None`` if unavailable. + See also: :attr:`__doc__ attributes `. + +.. attribute:: module.__annotations__ + + A dictionary containing + :term:`variable annotations ` collected during module + body execution. For best practices on working with :attr:`__annotations__`, + please see :ref:`annotations-howto`. + +Module dictionaries +^^^^^^^^^^^^^^^^^^^ + +Module objects also have the following special read-only attribute: .. index:: single: __dict__ (module attribute) +.. attribute:: module.__dict__ -Special read-only attribute: :attr:`~object.__dict__` is the module's -namespace as a dictionary object. + The module's namespace as a dictionary object. Uniquely among the attributes + listed here, :attr:`!__dict__` cannot be accessed as a global variable from + within a module; it can only be accessed as an attribute on module objects. -.. impl-detail:: + .. impl-detail:: - Because of the way CPython clears module dictionaries, the module - dictionary will be cleared when the module falls out of scope even if the - dictionary still has live references. To avoid this, copy the dictionary - or keep the module around while using its dictionary directly. + Because of the way CPython clears module dictionaries, the module + dictionary will be cleared when the module falls out of scope even if the + dictionary still has live references. To avoid this, copy the dictionary + or keep the module around while using its dictionary directly. .. _class-attrs-and-methods: diff --git a/Doc/reference/import.rst b/Doc/reference/import.rst index 7de995b12702ec..ac363e8cfa00dc 100644 --- a/Doc/reference/import.rst +++ b/Doc/reference/import.rst @@ -513,8 +513,10 @@ holding is that if you have ``sys.modules['spam']`` and ``sys.modules['spam.foo']`` (as you would after the above import), the latter must appear as the ``foo`` attribute of the former. -Module spec ------------ +.. _module-specs: + +Module specs +------------ The import machinery uses a variety of information about each module during import, especially before loading. Most of the information is @@ -527,155 +529,44 @@ and the loader that executes it. Most importantly, it allows the import machinery to perform the boilerplate operations of loading, whereas without a module spec the loader had that responsibility. -The module's spec is exposed as the ``__spec__`` attribute on a module object. +The module's spec is exposed as :attr:`module.__spec__`. Setting +:attr:`!__spec__` appropriately applies equally to +:ref:`modules initialized during interpreter startup `. +The one exception is ``__main__``, where :attr:`!__spec__` is +:ref:`set to None in some cases `. + See :class:`~importlib.machinery.ModuleSpec` for details on the contents of the module spec. .. versionadded:: 3.4 -.. _import-mod-attrs: - -Import-related module attributes --------------------------------- - -The import machinery fills in these attributes on each module object -during loading, based on the module's spec, before the loader executes -the module. - -It is **strongly** recommended that you rely on :attr:`__spec__` and -its attributes instead of any of the other individual attributes -listed below. - -.. attribute:: __name__ - - The ``__name__`` attribute must be set to the fully qualified name of - the module. This name is used to uniquely identify the module in - the import system. - -.. attribute:: __loader__ - - The ``__loader__`` attribute must be set to the loader object that - the import machinery used when loading the module. This is mostly - for introspection, but can be used for additional loader-specific - functionality, for example getting data associated with a loader. - - It is **strongly** recommended that you rely on :attr:`__spec__` - instead of this attribute. - - .. versionchanged:: 3.12 - The value of ``__loader__`` is expected to be the same as - ``__spec__.loader``. The use of ``__loader__`` is deprecated and slated - for removal in Python 3.14. - -.. attribute:: __package__ - - The module's ``__package__`` attribute may be set. Its value must - be a string, but it can be the same value as its ``__name__``. When - the module is a package, its ``__package__`` value should be set to - its ``__name__``. When the module is not a package, ``__package__`` - should be set to the empty string for top-level modules, or for - submodules, to the parent package's name. See :pep:`366` for further - details. - - This attribute is used instead of ``__name__`` to calculate explicit - relative imports for main modules, as defined in :pep:`366`. - - It is **strongly** recommended that you rely on :attr:`__spec__` - instead of this attribute. - - .. versionchanged:: 3.6 - The value of ``__package__`` is expected to be the same as - ``__spec__.parent``. - - .. versionchanged:: 3.10 - :exc:`ImportWarning` is raised if import falls back to - ``__package__`` instead of - :attr:`~importlib.machinery.ModuleSpec.parent`. - - .. versionchanged:: 3.12 - Raise :exc:`DeprecationWarning` instead of :exc:`ImportWarning` - when falling back to ``__package__``. - - -.. attribute:: __spec__ - - The ``__spec__`` attribute must be set to the module spec that was - used when importing the module. Setting ``__spec__`` - appropriately applies equally to :ref:`modules initialized during - interpreter startup `. The one exception is ``__main__``, - where ``__spec__`` is :ref:`set to None in some cases `. - - When ``__spec__.parent`` is not set, ``__package__`` is used as - a fallback. - - .. versionadded:: 3.4 - - .. versionchanged:: 3.6 - ``__spec__.parent`` is used as a fallback when ``__package__`` is - not defined. - -.. attribute:: __path__ - - If the module is a package (either regular or namespace), the module - object's ``__path__`` attribute must be set. The value must be - iterable, but may be empty if ``__path__`` has no further significance. - If ``__path__`` is not empty, it must produce strings when iterated - over. More details on the semantics of ``__path__`` are given - :ref:`below `. - - Non-package modules should not have a ``__path__`` attribute. - -.. attribute:: __file__ -.. attribute:: __cached__ - - ``__file__`` is optional (if set, value must be a string). It indicates - the pathname of the file from which the module was loaded (if - loaded from a file), or the pathname of the shared library file - for extension modules loaded dynamically from a shared library. - It might be missing for certain types of modules, such as C - modules that are statically linked into the interpreter, and the - import system may opt to leave it unset if it has no semantic - meaning (e.g. a module loaded from a database). - - If ``__file__`` is set then the ``__cached__`` attribute might also - be set, which is the path to any compiled version of - the code (e.g. byte-compiled file). The file does not need to exist - to set this attribute; the path can simply point to where the - compiled file would exist (see :pep:`3147`). - - Note that ``__cached__`` may be set even if ``__file__`` is not - set. However, that scenario is quite atypical. Ultimately, the - loader is what makes use of the module spec provided by the finder - (from which ``__file__`` and ``__cached__`` are derived). So - if a loader can load from a cached module but otherwise does not load - from a file, that atypical scenario may be appropriate. - - It is **strongly** recommended that you rely on :attr:`__spec__` - instead of ``__cached__``. - .. _package-path-rules: -module.__path__ ---------------- +__path__ attributes on modules +------------------------------ -By definition, if a module has a ``__path__`` attribute, it is a package. +The :attr:`~module.__path__` attribute should be a (possibly empty) +:term:`sequence` of strings enumerating the locations where the package's +submodules will be found. By definition, if a module has a :attr:`!__path__` +attribute, it is a :term:`package`. -A package's ``__path__`` attribute is used during imports of its subpackages. +A package's :attr:`~module.__path__` attribute is used during imports of its +subpackages. Within the import machinery, it functions much the same as :data:`sys.path`, i.e. providing a list of locations to search for modules during import. -However, ``__path__`` is typically much more constrained than -:data:`sys.path`. +However, :attr:`!__path__` is typically much more constrained than +:data:`!sys.path`. -``__path__`` must be an iterable of strings, but it may be empty. The same rules used for :data:`sys.path` also apply to a package's -``__path__``, and :data:`sys.path_hooks` (described below) are -consulted when traversing a package's ``__path__``. +:attr:`!__path__`. :data:`sys.path_hooks` (described below) are +consulted when traversing a package's :attr:`!__path__`. -A package's ``__init__.py`` file may set or alter the package's ``__path__`` +A package's ``__init__.py`` file may set or alter the package's +:attr:`~module.__path__` attribute, and this was typically the way namespace packages were implemented prior to :pep:`420`. With the adoption of :pep:`420`, namespace packages no -longer need to supply ``__init__.py`` files containing only ``__path__`` -manipulation code; the import machinery automatically sets ``__path__`` +longer need to supply ``__init__.py`` files containing only :attr:`!__path__` +manipulation code; the import machinery automatically sets :attr:`!__path__` correctly for the namespace package. Module reprs diff --git a/Doc/tutorial/modules.rst b/Doc/tutorial/modules.rst index 0316239e776a95..de7aa0e2342946 100644 --- a/Doc/tutorial/modules.rst +++ b/Doc/tutorial/modules.rst @@ -585,8 +585,9 @@ as the main module of a Python application must always use absolute imports. Packages in Multiple Directories -------------------------------- -Packages support one more special attribute, :attr:`__path__`. This is -initialized to be a list containing the name of the directory holding the +Packages support one more special attribute, :attr:`~module.__path__`. This is +initialized to be a :term:`sequence` of strings containing the name of the +directory holding the package's :file:`__init__.py` before the code in that file is executed. This variable can be modified; doing so affects future searches for modules and subpackages contained in the package. diff --git a/Doc/whatsnew/2.6.rst b/Doc/whatsnew/2.6.rst index 7e575e82523898..e04684b9f901e5 100644 --- a/Doc/whatsnew/2.6.rst +++ b/Doc/whatsnew/2.6.rst @@ -502,12 +502,12 @@ Python's :option:`-m` switch allows running a module as a script. When you ran a module that was located inside a package, relative imports didn't work correctly. -The fix for Python 2.6 adds a :attr:`__package__` attribute to -modules. When this attribute is present, relative imports will be +The fix for Python 2.6 adds a :attr:`module.__package__` attribute. +When this attribute is present, relative imports will be relative to the value of this attribute instead of the -:attr:`__name__` attribute. +:attr:`~module.__name__` attribute. -PEP 302-style importers can then set :attr:`__package__` as necessary. +PEP 302-style importers can then set :attr:`~module.__package__` as necessary. The :mod:`runpy` module that implements the :option:`-m` switch now does this, so relative imports will now work correctly in scripts running from inside a package. diff --git a/Doc/whatsnew/3.0.rst b/Doc/whatsnew/3.0.rst index 22e44671caf79b..766db5ecc67b73 100644 --- a/Doc/whatsnew/3.0.rst +++ b/Doc/whatsnew/3.0.rst @@ -357,8 +357,8 @@ New Syntax provides a standardized way of annotating a function's parameters and return value. There are no semantics attached to such annotations except that they can be introspected at runtime using - the :attr:`__annotations__` attribute. The intent is to encourage - experimentation through metaclasses, decorators or frameworks. + the :attr:`~object.__annotations__` attribute. The intent is to + encourage experimentation through metaclasses, decorators or frameworks. * :pep:`3102`: Keyword-only arguments. Named parameters occurring after ``*args`` in the parameter list *must* be specified using diff --git a/Doc/whatsnew/3.12.rst b/Doc/whatsnew/3.12.rst index 5c36e2d4bcfb49..7ec1935ce956d3 100644 --- a/Doc/whatsnew/3.12.rst +++ b/Doc/whatsnew/3.12.rst @@ -1320,14 +1320,15 @@ Deprecated may be removed in a future version of Python. Use the single-arg versions of these functions instead. (Contributed by Ofey Chan in :gh:`89874`.) -* :exc:`DeprecationWarning` is now raised when ``__package__`` on a - module differs from ``__spec__.parent`` (previously it was - :exc:`ImportWarning`). +* :exc:`DeprecationWarning` is now raised when :attr:`~module.__package__` on a + module differs from + :attr:`__spec__.parent ` (previously + it was :exc:`ImportWarning`). (Contributed by Brett Cannon in :gh:`65961`.) -* Setting ``__package__`` or ``__cached__`` on a module is deprecated, - and will cease to be set or taken into consideration by the import system in Python 3.14. - (Contributed by Brett Cannon in :gh:`65961`.) +* Setting :attr:`~module.__package__` or :attr:`~module.__cached__` on a + module is deprecated, and will cease to be set or taken into consideration by + the import system in Python 3.14. (Contributed by Brett Cannon in :gh:`65961`.) * The bitwise inversion operator (``~``) on bool is deprecated. It will throw an error in Python 3.16. Use ``not`` for logical negation of bools instead. diff --git a/Doc/whatsnew/3.2.rst b/Doc/whatsnew/3.2.rst index 89c717875e7d7e..135ecda4cd75be 100644 --- a/Doc/whatsnew/3.2.rst +++ b/Doc/whatsnew/3.2.rst @@ -312,8 +312,8 @@ cluttering source directories, the *pyc* files are now collected in a Aside from the filenames and target directories, the new scheme has a few aspects that are visible to the programmer: -* Imported modules now have a :attr:`__cached__` attribute which stores the name - of the actual file that was imported: +* Imported modules now have a :attr:`~module.__cached__` attribute which stores + the name of the actual file that was imported: >>> import collections >>> collections.__cached__ # doctest: +SKIP diff --git a/Doc/whatsnew/3.4.rst b/Doc/whatsnew/3.4.rst index 79dbfc5788d9b6..33534ff2c93f9e 100644 --- a/Doc/whatsnew/3.4.rst +++ b/Doc/whatsnew/3.4.rst @@ -2271,7 +2271,8 @@ Changes in the Python API :func:`super` and falling through all the way to the ABCs. For compatibility, catch both :exc:`NotImplementedError` or the appropriate exception as needed. -* The module type now initializes the :attr:`__package__` and :attr:`__loader__` +* The module type now initializes the :attr:`~module.__package__` and + :attr:`~module.__loader__` attributes to ``None`` by default. To determine if these attributes were set in a backwards-compatible fashion, use e.g. ``getattr(module, '__loader__', None) is not None``. (:issue:`17115`.) diff --git a/Doc/whatsnew/3.5.rst b/Doc/whatsnew/3.5.rst index fe829fc19fe482..b18fbb00bc62c7 100644 --- a/Doc/whatsnew/3.5.rst +++ b/Doc/whatsnew/3.5.rst @@ -423,8 +423,8 @@ are declared in the annotations:: return 'Hello ' + name While these annotations are available at runtime through the usual -:attr:`__annotations__` attribute, *no automatic type checking happens at -runtime*. Instead, it is assumed that a separate off-line type checker +:attr:`~object.__annotations__` attribute, *no automatic type checking happens +at runtime*. Instead, it is assumed that a separate off-line type checker (e.g. `mypy `_) will be used for on-demand source code analysis. diff --git a/Misc/NEWS.d/3.10.0a2.rst b/Misc/NEWS.d/3.10.0a2.rst index bd002b6ad3db9b..3e82de9ef266d6 100644 --- a/Misc/NEWS.d/3.10.0a2.rst +++ b/Misc/NEWS.d/3.10.0a2.rst @@ -226,8 +226,8 @@ thread at the time the function is called. .. section: Core and Builtins Enable ``from __future__ import annotations`` (:pep:`563`) by default. The -values found in :attr:`__annotations__` dicts are now strings, e.g. ``{"x": -"int"}`` instead of ``{"x": int}``. +values found in :attr:`~object.__annotations__` dicts are now strings, for +example ``{"x": "int"}`` instead of ``{"x": int}``. .. diff --git a/Misc/NEWS.d/3.11.0a5.rst b/Misc/NEWS.d/3.11.0a5.rst index 954f5c18b48000..5418d5d59dd583 100644 --- a/Misc/NEWS.d/3.11.0a5.rst +++ b/Misc/NEWS.d/3.11.0a5.rst @@ -486,8 +486,8 @@ Use ``dis.Positions`` in ``dis.Instruction`` instead of a regular ``tuple``. .. nonce: geS-aP .. section: Library -:mod:`pdb` now gracefully handles ``help`` when :attr:`__doc__` is missing, -for example when run with pregenerated optimized ``.pyc`` files. +:mod:`pdb` now gracefully handles ``help`` when :attr:`~module.__doc__` is +missing, for example when run with pregenerated optimized ``.pyc`` files. .. diff --git a/Misc/NEWS.d/3.12.0a1.rst b/Misc/NEWS.d/3.12.0a1.rst index 305607c297f1f4..f95a1231d5c73b 100644 --- a/Misc/NEWS.d/3.12.0a1.rst +++ b/Misc/NEWS.d/3.12.0a1.rst @@ -4237,8 +4237,8 @@ by :mod:`asyncio` to AIX platform only. .. nonce: 4dzB80 .. section: Library -Set :attr:`doctest.DocTest.lineno` to ``None`` when object does not have -:attr:`__doc__`. +Set :attr:`doctest.DocTest.lineno` to ``None`` when an object does not have +:attr:`~definition.__doc__`. .. From caa4924917f2c5e70d9e199a71893a6fc8bc43da Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+AA-Turner@users.noreply.github.com> Date: Thu, 10 Oct 2024 10:16:03 +0100 Subject: [PATCH 031/269] [3.12] GH-121970: Extract ``availability`` into a new extension (GH-125082) (#125238) (cherry picked from commit cbfd39247983309a9ef0ae6da6c61cc71665b967) --- Doc/conf.py | 1 + Doc/tools/extensions/availability.py | 123 +++++++++++++++++++++++++++ Doc/tools/extensions/pyspecific.py | 76 ----------------- 3 files changed, 124 insertions(+), 76 deletions(-) create mode 100644 Doc/tools/extensions/availability.py diff --git a/Doc/conf.py b/Doc/conf.py index 95e2e1774479b3..4859063edd24ea 100644 --- a/Doc/conf.py +++ b/Doc/conf.py @@ -21,6 +21,7 @@ extensions = [ 'audit_events', + 'availability', 'c_annotations', 'glossary_search', 'lexers', diff --git a/Doc/tools/extensions/availability.py b/Doc/tools/extensions/availability.py new file mode 100644 index 00000000000000..897af70a9f4b40 --- /dev/null +++ b/Doc/tools/extensions/availability.py @@ -0,0 +1,123 @@ +"""Support for documenting platform availability""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from docutils import nodes +from sphinx import addnodes +from sphinx.util import logging +from sphinx.util.docutils import SphinxDirective + +if TYPE_CHECKING: + from sphinx.application import Sphinx + from sphinx.util.typing import ExtensionMetadata + +logger = logging.getLogger("availability") + +# known platform, libc, and threading implementations +_PLATFORMS = frozenset({ + "AIX", + "Android", + "BSD", + "DragonFlyBSD", + "Emscripten", + "FreeBSD", + "Linux", + "macOS", + "NetBSD", + "OpenBSD", + "POSIX", + "Solaris", + "Unix", + "VxWorks", + "WASI", + "Windows", +}) +_LIBC = frozenset({ + "BSD libc", + "glibc", + "musl", +}) +_THREADING = frozenset({ + # POSIX platforms with pthreads + "pthreads", +}) +KNOWN_PLATFORMS = _PLATFORMS | _LIBC | _THREADING + + +class Availability(SphinxDirective): + has_content = True + required_arguments = 1 + optional_arguments = 0 + final_argument_whitespace = True + + def run(self) -> list[nodes.container]: + title = "Availability" + refnode = addnodes.pending_xref( + title, + nodes.inline(title, title, classes=["xref", "std", "std-ref"]), + refdoc=self.env.docname, + refdomain="std", + refexplicit=True, + reftarget="availability", + reftype="ref", + refwarn=True, + ) + sep = nodes.Text(": ") + parsed, msgs = self.state.inline_text(self.arguments[0], self.lineno) + pnode = nodes.paragraph(title, "", refnode, sep, *parsed, *msgs) + self.set_source_info(pnode) + cnode = nodes.container("", pnode, classes=["availability"]) + self.set_source_info(cnode) + if self.content: + self.state.nested_parse(self.content, self.content_offset, cnode) + self.parse_platforms() + + return [cnode] + + def parse_platforms(self) -> dict[str, str | bool]: + """Parse platform information from arguments + + Arguments is a comma-separated string of platforms. A platform may + be prefixed with "not " to indicate that a feature is not available. + + Example:: + + .. availability:: Windows, Linux >= 4.2, not WASI + + Arguments like "Linux >= 3.17 with glibc >= 2.27" are currently not + parsed into separate tokens. + """ + platforms = {} + for arg in self.arguments[0].rstrip(".").split(","): + arg = arg.strip() + platform, _, version = arg.partition(" >= ") + if platform.startswith("not "): + version = False + platform = platform.removeprefix("not ") + elif not version: + version = True + platforms[platform] = version + + if unknown := set(platforms).difference(KNOWN_PLATFORMS): + logger.warning( + "Unknown platform%s or syntax '%s' in '.. availability:: %s', " + "see %s:KNOWN_PLATFORMS for a set of known platforms.", + "s" if len(platforms) != 1 else "", + " ".join(sorted(unknown)), + self.arguments[0], + __file__, + ) + + return platforms + + +def setup(app: Sphinx) -> ExtensionMetadata: + app.add_directive("availability", Availability) + + return { + "version": "1.0", + "parallel_read_safe": True, + "parallel_write_safe": True, + } diff --git a/Doc/tools/extensions/pyspecific.py b/Doc/tools/extensions/pyspecific.py index 96a4f24fad33b7..d3a4835edd58a4 100644 --- a/Doc/tools/extensions/pyspecific.py +++ b/Doc/tools/extensions/pyspecific.py @@ -24,7 +24,6 @@ from sphinx.domains.changeset import VersionChange, versionlabels, versionlabel_classes from sphinx.domains.python import PyFunction, PyMethod, PyModule from sphinx.locale import _ as sphinx_gettext -from sphinx.util import logging from sphinx.util.docutils import SphinxDirective from sphinx.writers.text import TextWriter, TextTranslator from sphinx.util.display import status_iterator @@ -108,80 +107,6 @@ def run(self): return [pnode] -# Support for documenting platform availability - -class Availability(SphinxDirective): - - has_content = True - required_arguments = 1 - optional_arguments = 0 - final_argument_whitespace = True - - # known platform, libc, and threading implementations - known_platforms = frozenset({ - "AIX", "Android", "BSD", "DragonFlyBSD", "Emscripten", "FreeBSD", - "Linux", "NetBSD", "OpenBSD", "POSIX", "Solaris", "Unix", "VxWorks", - "WASI", "Windows", "macOS", - # libc - "BSD libc", "glibc", "musl", - # POSIX platforms with pthreads - "pthreads", - }) - - def run(self): - availability_ref = ':ref:`Availability `: ' - avail_nodes, avail_msgs = self.state.inline_text( - availability_ref + self.arguments[0], - self.lineno) - pnode = nodes.paragraph(availability_ref + self.arguments[0], - '', *avail_nodes, *avail_msgs) - self.set_source_info(pnode) - cnode = nodes.container("", pnode, classes=["availability"]) - self.set_source_info(cnode) - if self.content: - self.state.nested_parse(self.content, self.content_offset, cnode) - self.parse_platforms() - - return [cnode] - - def parse_platforms(self): - """Parse platform information from arguments - - Arguments is a comma-separated string of platforms. A platform may - be prefixed with "not " to indicate that a feature is not available. - - Example:: - - .. availability:: Windows, Linux >= 4.2, not Emscripten, not WASI - - Arguments like "Linux >= 3.17 with glibc >= 2.27" are currently not - parsed into separate tokens. - """ - platforms = {} - for arg in self.arguments[0].rstrip(".").split(","): - arg = arg.strip() - platform, _, version = arg.partition(" >= ") - if platform.startswith("not "): - version = False - platform = platform[4:] - elif not version: - version = True - platforms[platform] = version - - unknown = set(platforms).difference(self.known_platforms) - if unknown: - cls = type(self) - logger = logging.getLogger(cls.__qualname__) - logger.warning( - f"Unknown platform(s) or syntax '{' '.join(sorted(unknown))}' " - f"in '.. availability:: {self.arguments[0]}', see " - f"{__file__}:{cls.__qualname__}.known_platforms for a set " - "known platforms." - ) - - return platforms - - # Support for documenting decorators class PyDecoratorMixin(object): @@ -473,7 +398,6 @@ def setup(app): app.add_role('issue', issue_role) app.add_role('gh', gh_issue_role) app.add_directive('impl-detail', ImplementationDetail) - app.add_directive('availability', Availability) app.add_directive('deprecated-removed', DeprecatedRemoved) app.add_builder(PydocTopicsBuilder) app.add_object_type('opcode', 'opcode', '%s (opcode)', parse_opcode_signature) From 01d9a899978626615d172aa5d832aaf0e4500bc7 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Thu, 10 Oct 2024 11:52:54 +0200 Subject: [PATCH 032/269] [3.12] Pin the doctest workflow to Ubuntu 22.04 (GH-125236) (#125241) Pin the doctest workflow to Ubuntu 22.04 (GH-125236) (cherry picked from commit 7a10cdec359750b5154490fa9e24475c90d05aab) Co-authored-by: Adam Turner <9087854+AA-Turner@users.noreply.github.com> --- .github/workflows/reusable-docs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/reusable-docs.yml b/.github/workflows/reusable-docs.yml index 4b384f4b3fa602..55c42ff396e05a 100644 --- a/.github/workflows/reusable-docs.yml +++ b/.github/workflows/reusable-docs.yml @@ -92,7 +92,7 @@ jobs: # Run "doctest" on HEAD as new syntax doesn't exist in the latest stable release doctest: name: 'Doctest' - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 timeout-minutes: 60 steps: - uses: actions/checkout@v4 From 01daccff3c2c7b389a2c2ef5a7f80f1b733f5881 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Thu, 10 Oct 2024 12:37:14 +0200 Subject: [PATCH 033/269] [3.12] gh-71784: [doc] add usage examples for traceback.TracebackException (GH-125189) (#125248) Co-authored-by: Irit Katriel <1055913+iritkatriel@users.noreply.github.com> Co-authored-by: Alex Waygood --- Doc/library/traceback.rst | 133 +++++++++++++++++++++++++++++++++----- 1 file changed, 118 insertions(+), 15 deletions(-) diff --git a/Doc/library/traceback.rst b/Doc/library/traceback.rst index d3f47b9e4fb596..9d57c354523370 100644 --- a/Doc/library/traceback.rst +++ b/Doc/library/traceback.rst @@ -8,11 +8,15 @@ -------------- -This module provides a standard interface to extract, format and print stack -traces of Python programs. It exactly mimics the behavior of the Python -interpreter when it prints a stack trace. This is useful when you want to print -stack traces under program control, such as in a "wrapper" around the -interpreter. +This module provides a standard interface to extract, format and print +stack traces of Python programs. It is more flexible than the +interpreter's default traceback display, and therefore makes it +possible to configure certain aspects of the output. Finally, +it contains a utility for capturing enough information about an +exception to print it later, without the need to save a reference +to the actual exception. Since exceptions can be the roots of large +objects graph, this utility can significantly improve +memory management. .. index:: pair: object; traceback @@ -29,7 +33,20 @@ which are assigned to the :attr:`~BaseException.__traceback__` field of Module :mod:`pdb` Interactive source code debugger for Python programs. -The module defines the following functions: +The module's API can be divided into two parts: + +* Module-level functions offering basic functionality, which are useful for interactive + inspection of exceptions and tracebacks. + +* :class:`TracebackException` class and its helper classes + :class:`StackSummary` and :class:`FrameSummary`. These offer both more + flexibility in the output generated and the ability to store the information + necessary for later formatting without holding references to actual exception + and traceback objects. + + +Module-Level Functions +---------------------- .. function:: print_tb(tb, limit=None, file=None) @@ -230,7 +247,6 @@ The module defines the following functions: .. versionadded:: 3.5 -The module also defines the following classes: :class:`!TracebackException` Objects ------------------------------------ @@ -238,12 +254,17 @@ The module also defines the following classes: .. versionadded:: 3.5 :class:`!TracebackException` objects are created from actual exceptions to -capture data for later printing in a lightweight fashion. +capture data for later printing. They offer a more lightweight method of +storing this information by avoiding holding references to +:ref:`traceback` and :ref:`frame` objects +In addition, they expose more options to configure the output compared to +the module-level functions described above. .. class:: TracebackException(exc_type, exc_value, exc_traceback, *, limit=None, lookup_lines=True, capture_locals=False, compact=False, max_group_width=15, max_group_depth=10) - Capture an exception for later rendering. *limit*, *lookup_lines* and - *capture_locals* are as for the :class:`StackSummary` class. + Capture an exception for later rendering. The meaning of *limit*, + *lookup_lines* and *capture_locals* are as for the :class:`StackSummary` + class. If *compact* is true, only data that is required by :class:`!TracebackException`'s :meth:`format` method @@ -488,8 +509,8 @@ in a :ref:`traceback `. .. _traceback-example: -Traceback Examples ------------------- +Examples of Using the Module-Level Functions +-------------------------------------------- This simple example implements a basic read-eval-print loop, similar to (but less useful than) the standard Python interactive interpreter loop. For a more @@ -528,8 +549,7 @@ exception and traceback: try: lumberjack() - except IndexError: - exc = sys.exception() + except IndexError as exc: print("*** print_tb:") traceback.print_tb(exc.__traceback__, limit=1, file=sys.stdout) print("*** print_exception:") @@ -627,5 +647,88 @@ This last example demonstrates the final few formatting functions: [' File "spam.py", line 3, in \n spam.eggs()\n', ' File "eggs.py", line 42, in eggs\n return "bacon"\n'] >>> an_error = IndexError('tuple index out of range') - >>> traceback.format_exception_only(type(an_error), an_error) + >>> traceback.format_exception_only(an_error) ['IndexError: tuple index out of range\n'] + + +Examples of Using :class:`TracebackException` +--------------------------------------------- + +With the helper class, we have more options:: + + >>> import sys + >>> from traceback import TracebackException + >>> + >>> def lumberjack(): + ... bright_side_of_life() + ... + >>> def bright_side_of_life(): + ... t = "bright", "side", "of", "life" + ... return t[5] + ... + >>> try: + ... lumberjack() + ... except IndexError as e: + ... exc = e + ... + >>> try: + ... try: + ... lumberjack() + ... except: + ... 1/0 + ... except Exception as e: + ... chained_exc = e + ... + >>> # limit works as with the module-level functions + >>> TracebackException.from_exception(exc, limit=-2).print() + Traceback (most recent call last): + File "", line 6, in lumberjack + bright_side_of_life() + ~~~~~~~~~~~~~~~~~~~^^ + File "", line 10, in bright_side_of_life + return t[5] + ~^^^ + IndexError: tuple index out of range + + >>> # capture_locals adds local variables in frames + >>> TracebackException.from_exception(exc, limit=-2, capture_locals=True).print() + Traceback (most recent call last): + File "", line 6, in lumberjack + bright_side_of_life() + ~~~~~~~~~~~~~~~~~~~^^ + File "", line 10, in bright_side_of_life + return t[5] + ~^^^ + t = ("bright", "side", "of", "life") + IndexError: tuple index out of range + + >>> # The *chain* kwarg to print() controls whether chained + >>> # exceptions are displayed + >>> TracebackException.from_exception(chained_exc).print() + Traceback (most recent call last): + File "", line 4, in + lumberjack() + ~~~~~~~~~~^^ + File "", line 7, in lumberjack + bright_side_of_life() + ~~~~~~~~~~~~~~~~~~~^^ + File "", line 11, in bright_side_of_life + return t[5] + ~^^^ + IndexError: tuple index out of range + + During handling of the above exception, another exception occurred: + + Traceback (most recent call last): + File "", line 6, in + 1/0 + ~^~ + ZeroDivisionError: division by zero + + >>> TracebackException.from_exception(chained_exc).print(chain=False) + Traceback (most recent call last): + File "", line 6, in + 1/0 + ~^~ + ZeroDivisionError: division by zero + From 74df3a79d308d036e79b600b1e7850e7828e6b39 Mon Sep 17 00:00:00 2001 From: Donghee Na Date: Thu, 10 Oct 2024 22:01:41 +0900 Subject: [PATCH 034/269] [3.12] gh-124471: Set name for unnamed reusable workflow (GH-124475) (gh-125257) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit (cherry picked from commit e4cab488d4445e8444932f3bed1c329c0d9e5038) Co-authored-by: Adam Turner <9087854+AA-Turner@users.noreply.github.com> Co-authored-by: Sviatoslav Sydorenko (Святослав Сидоренко) --- .github/workflows/reusable-change-detection.yml | 4 +--- .github/workflows/reusable-docs.yml | 2 +- .github/workflows/reusable-macos.yml | 2 ++ .github/workflows/reusable-tsan.yml | 2 ++ .github/workflows/reusable-ubuntu.yml | 2 ++ .github/workflows/reusable-windows-msi.yml | 2 +- .github/workflows/reusable-windows.yml | 2 ++ 7 files changed, 11 insertions(+), 5 deletions(-) diff --git a/.github/workflows/reusable-change-detection.yml b/.github/workflows/reusable-change-detection.yml index 6f599f75547ceb..5cd6fb39f1e12f 100644 --- a/.github/workflows/reusable-change-detection.yml +++ b/.github/workflows/reusable-change-detection.yml @@ -1,6 +1,4 @@ ---- - -name: Change detection +name: Reusable change detection on: # yamllint disable-line rule:truthy workflow_call: diff --git a/.github/workflows/reusable-docs.yml b/.github/workflows/reusable-docs.yml index 55c42ff396e05a..4d1dc04e8b638a 100644 --- a/.github/workflows/reusable-docs.yml +++ b/.github/workflows/reusable-docs.yml @@ -1,4 +1,4 @@ -name: Docs +name: Reusable Docs on: workflow_call: diff --git a/.github/workflows/reusable-macos.yml b/.github/workflows/reusable-macos.yml index 47f5ee2fb63888..6df272c8839f16 100644 --- a/.github/workflows/reusable-macos.yml +++ b/.github/workflows/reusable-macos.yml @@ -1,3 +1,5 @@ +name: Reusable macOS + on: workflow_call: inputs: diff --git a/.github/workflows/reusable-tsan.yml b/.github/workflows/reusable-tsan.yml index 96a9c1b0cda3c3..b20ba062d62d63 100644 --- a/.github/workflows/reusable-tsan.yml +++ b/.github/workflows/reusable-tsan.yml @@ -1,3 +1,5 @@ +name: Reusable Thread Sanitizer + on: workflow_call: inputs: diff --git a/.github/workflows/reusable-ubuntu.yml b/.github/workflows/reusable-ubuntu.yml index b2ac6f6127d883..a5abec11555083 100644 --- a/.github/workflows/reusable-ubuntu.yml +++ b/.github/workflows/reusable-ubuntu.yml @@ -1,3 +1,5 @@ +name: Reusable Ubuntu + on: workflow_call: inputs: diff --git a/.github/workflows/reusable-windows-msi.yml b/.github/workflows/reusable-windows-msi.yml index fc34ab7c3eb1f2..abdb1a1982fef8 100644 --- a/.github/workflows/reusable-windows-msi.yml +++ b/.github/workflows/reusable-windows-msi.yml @@ -1,4 +1,4 @@ -name: TestsMSI +name: Reusable Windows MSI on: workflow_call: diff --git a/.github/workflows/reusable-windows.yml b/.github/workflows/reusable-windows.yml index 8b886056fe56b8..9393328fa1458b 100644 --- a/.github/workflows/reusable-windows.yml +++ b/.github/workflows/reusable-windows.yml @@ -1,3 +1,5 @@ +name: Reusable Windows + on: workflow_call: inputs: From 67f8302b9c94079f73dba23eaa47443fcb6008ad Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Thu, 10 Oct 2024 17:56:49 +0300 Subject: [PATCH 035/269] [3.12] gh-125118: don't copy arbitrary values to _Bool in the struct module (GH-125169) (#125265) memcopy'ing arbitrary values to _Bool variable triggers undefined behaviour. Avoid this. We assume that `false` is represented by all zero bytes. Credits to Alex Gaynor. (cherry picked from commit 87d7315ac57250046372b0d9ae4619ba619c8c87) Co-authored-by: Sam Gross Co-authored-by: Victor Stinner Co-authored-by: Petr Viktorin --- Lib/test/test_struct.py | 3 +++ .../Library/2024-10-09-07-09-00.gh-issue-125118.J9rQ1S.rst | 1 + Modules/_struct.c | 5 ++--- 3 files changed, 6 insertions(+), 3 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-10-09-07-09-00.gh-issue-125118.J9rQ1S.rst diff --git a/Lib/test/test_struct.py b/Lib/test/test_struct.py index 39ca78cece1fb0..c683e5dd6c47c6 100644 --- a/Lib/test/test_struct.py +++ b/Lib/test/test_struct.py @@ -529,6 +529,9 @@ def __bool__(self): for c in [b'\x01', b'\x7f', b'\xff', b'\x0f', b'\xf0']: self.assertTrue(struct.unpack('>?', c)[0]) + self.assertTrue(struct.unpack(' Date: Thu, 10 Oct 2024 19:31:27 +0200 Subject: [PATCH 036/269] [3.12] gh-121607: Edited source file import recipe to make it more clear (GH-121519) (GH-124081) gh-121607: Edited source file import recipe to make it more clear (GH-121519) (cherry picked from commit 38809171b8768517824fb62d48abe2cb0aff8429) Co-authored-by: Chris Barker Co-authored-by: Brett Cannon Co-authored-by: Peter Bierma --- Doc/library/importlib.rst | 37 +++++++++++++++++++++++++------------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/Doc/library/importlib.rst b/Doc/library/importlib.rst index 1dacbe64b748db..e4e09b096f7c04 100644 --- a/Doc/library/importlib.rst +++ b/Doc/library/importlib.rst @@ -1495,20 +1495,34 @@ Note that if ``name`` is a submodule (contains a dot), Importing a source file directly '''''''''''''''''''''''''''''''' -To import a Python source file directly, use the following recipe:: +This recipe should be used with caution: it is an approximation of an import +statement where the file path is specified directly, rather than +:data:`sys.path` being searched. Alternatives should first be considered first, +such as modifying :data:`sys.path` when a proper module is required, or using +:func:`runpy.run_path` when the global namespace resulting from running a Python +file is appropriate. - import importlib.util - import sys +To import a Python source file directly from a path, use the following recipe:: + + import importlib.util + import sys - # For illustrative purposes. - import tokenize - file_path = tokenize.__file__ - module_name = tokenize.__name__ - spec = importlib.util.spec_from_file_location(module_name, file_path) - module = importlib.util.module_from_spec(spec) - sys.modules[module_name] = module - spec.loader.exec_module(module) + def import_from_path(module_name, file_path): + spec = importlib.util.spec_from_file_location(module_name, file_path) + module = importlib.util.module_from_spec(spec) + sys.modules[module_name] = module + spec.loader.exec_module(module) + return module + + + # For illustrative purposes only (use of `json` is arbitrary). + import json + file_path = json.__file__ + module_name = json.__name__ + + # Similar outcome as `import json`. + json = import_from_path(module_name, file_path) Implementing lazy imports @@ -1534,7 +1548,6 @@ The example below shows how to implement lazy imports:: False - Setting up an importer '''''''''''''''''''''' From eb320f5ac846faa9ad81fc0c07553cf5b181652d Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Thu, 10 Oct 2024 20:18:01 +0200 Subject: [PATCH 037/269] [3.12] Note argparse exit code in documentation (GH-119568) (GH-125275) (cherry picked from commit 3b87fb74c907510402678bf1b7c4a94df0e5e65a) Co-authored-by: Justin Kunimune Co-authored-by: Savannah Ostrowski --- Doc/library/argparse.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Doc/library/argparse.rst b/Doc/library/argparse.rst index abe20d7d611695..6d8b207cbc03ab 100644 --- a/Doc/library/argparse.rst +++ b/Doc/library/argparse.rst @@ -537,7 +537,8 @@ exit_on_error ^^^^^^^^^^^^^ Normally, when you pass an invalid argument list to the :meth:`~ArgumentParser.parse_args` -method of an :class:`ArgumentParser`, it will exit with error info. +method of an :class:`ArgumentParser`, it will print a *message* to :data:`sys.stderr` and exit with a status +code of 2. If the user would like to catch errors manually, the feature can be enabled by setting ``exit_on_error`` to ``False``:: From 91c7a2cb890856dde865cbc98b70c7421550f49e Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Thu, 10 Oct 2024 22:32:40 +0200 Subject: [PATCH 038/269] [3.12] Doc: Upgrade Sphinx to 8.1 (GH-125276) (#125279) Doc: Upgrade Sphinx to 8.1 (GH-125276) (cherry picked from commit dd0ee201da34d1d4a631d77b420728f9233f53f9) Co-authored-by: Adam Turner <9087854+AA-Turner@users.noreply.github.com> --- Doc/conf.py | 31 ++++++++++++++++++++++++------- Doc/requirements.txt | 2 +- 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/Doc/conf.py b/Doc/conf.py index 4859063edd24ea..36f6243969c8b2 100644 --- a/Doc/conf.py +++ b/Doc/conf.py @@ -11,6 +11,8 @@ import sys import time +import sphinx + sys.path.append(os.path.abspath('tools/extensions')) sys.path.append(os.path.abspath('includes')) @@ -56,7 +58,10 @@ # General substitutions. project = 'Python' -copyright = f"2001-{time.strftime('%Y')}, Python Software Foundation" +if sphinx.version_info[:2] >= (8, 1): + copyright = "2001-%Y, Python Software Foundation" +else: + copyright = f"2001-{time.strftime('%Y')}, Python Software Foundation" # We look for the Include/patchlevel.h file in the current Python source tree # and replace the values accordingly. @@ -339,10 +344,14 @@ } # This 'Last updated on:' timestamp is inserted at the bottom of every page. -html_time = int(os.environ.get('SOURCE_DATE_EPOCH', time.time())) -html_last_updated_fmt = time.strftime( - '%b %d, %Y (%H:%M UTC)', time.gmtime(html_time) -) +html_last_updated_fmt = '%b %d, %Y (%H:%M UTC)' +if sphinx.version_info[:2] >= (8, 1): + html_last_updated_use_utc = True +else: + html_time = int(os.environ.get('SOURCE_DATE_EPOCH', time.time())) + html_last_updated_fmt = time.strftime( + html_last_updated_fmt, time.gmtime(html_time) + ) # Path to find HTML templates. templates_path = ['tools/templates'] @@ -575,13 +584,21 @@ # mapping unique short aliases to a base URL and a prefix. # https://www.sphinx-doc.org/en/master/usage/extensions/extlinks.html extlinks = { - "cve": ("https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-%s", "CVE-%s"), - "cwe": ("https://cwe.mitre.org/data/definitions/%s.html", "CWE-%s"), "pypi": ("https://pypi.org/project/%s/", "%s"), "source": (SOURCE_URI, "%s"), } extlinks_detect_hardcoded_links = True +if sphinx.version_info[:2] < (8, 1): + # Sphinx 8.1 has in-built CVE and CWE roles. + extlinks |= { + "cve": ( + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-%s", + "CVE-%s", + ), + "cwe": ("https://cwe.mitre.org/data/definitions/%s.html", "CWE-%s"), + } + # Options for c_annotations # ------------------------- diff --git a/Doc/requirements.txt b/Doc/requirements.txt index bf1028020b7af7..5105786ccf283c 100644 --- a/Doc/requirements.txt +++ b/Doc/requirements.txt @@ -6,7 +6,7 @@ # Sphinx version is pinned so that new versions that introduce new warnings # won't suddenly cause build failures. Updating the version is fine as long # as no warnings are raised by doing so. -sphinx~=8.0.0 +sphinx~=8.1.0 blurb From 171ebcd4df268c77f1ca03339a137ca6a54242e5 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Fri, 11 Oct 2024 08:37:55 +0200 Subject: [PATCH 039/269] [3.12] [3.13] gh-124969: Fix locale.nl_langinfo(locale.ALT_DIGITS) (GH-124974) (GH-125232) (GH-125284) (cherry picked from commit 26a93189e4c3674a9e0acbd7923b1f27ff01419e) Co-authored-by: Serhiy Storchaka Returns a tuple of up to 100 strings for ALT_DIGITS lookup (an empty tuple on most locales). Previously it returned the first item of that tuple or an empty string. (cherry picked from commit 21c04e1a972bd1b6285e0ea41fa107d635bbe43a) Co-authored-by: Serhiy Storchaka --- Doc/library/locale.rst | 6 +-- Lib/test/test__locale.py | 42 ++++++++++++++++++- ...-10-08-12-09-09.gh-issue-124969._VBQLq.rst | 3 ++ Modules/_localemodule.c | 29 ++++++++++++- 4 files changed, 75 insertions(+), 5 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-10-08-12-09-09.gh-issue-124969._VBQLq.rst diff --git a/Doc/library/locale.rst b/Doc/library/locale.rst index 60975bf9177dad..f0553d51fedf14 100644 --- a/Doc/library/locale.rst +++ b/Doc/library/locale.rst @@ -158,7 +158,8 @@ The :mod:`locale` module defines the following exception and functions: .. function:: nl_langinfo(option) - Return some locale-specific information as a string. This function is not + Return some locale-specific information as a string (or a tuple for + ``ALT_DIGITS``). This function is not available on all systems, and the set of possible options might also vary across platforms. The possible argument values are numbers, for which symbolic constants are available in the locale module. @@ -311,8 +312,7 @@ The :mod:`locale` module defines the following exception and functions: .. data:: ALT_DIGITS - Get a representation of up to 100 values used to represent the values - 0 to 99. + Get a tuple of up to 100 strings used to represent the values 0 to 99. .. function:: getdefaultlocale([envvars]) diff --git a/Lib/test/test__locale.py b/Lib/test/test__locale.py index 0947464bb8c04e..5041def7216197 100644 --- a/Lib/test/test__locale.py +++ b/Lib/test/test__locale.py @@ -1,4 +1,4 @@ -from _locale import (setlocale, LC_ALL, LC_CTYPE, LC_NUMERIC, localeconv, Error) +from _locale import (setlocale, LC_ALL, LC_CTYPE, LC_NUMERIC, LC_TIME, localeconv, Error) try: from _locale import (RADIXCHAR, THOUSEP, nl_langinfo) except ImportError: @@ -74,6 +74,17 @@ def accept(loc): 'ps_AF': ('\u066b', '\u066c'), } +known_alt_digits = { + 'C': (0, {}), + 'en_US': (0, {}), + 'fa_IR': (100, {0: '\u06f0\u06f0', 10: '\u06f1\u06f0', 99: '\u06f9\u06f9'}), + 'ja_JP': (100, {0: '\u3007', 10: '\u5341', 99: '\u4e5d\u5341\u4e5d'}), + 'lzh_TW': (32, {0: '\u3007', 10: '\u5341', 31: '\u5345\u4e00'}), + 'my_MM': (100, {0: '\u1040\u1040', 10: '\u1041\u1040', 99: '\u1049\u1049'}), + 'or_IN': (100, {0: '\u0b66', 10: '\u0b67\u0b66', 99: '\u0b6f\u0b6f'}), + 'shn_MM': (100, {0: '\u1090\u1090', 10: '\u1091\u1090', 99: '\u1099\u1099'}), +} + if sys.platform == 'win32': # ps_AF doesn't work on Windows: see bpo-38324 (msg361830) del known_numerics['ps_AF'] @@ -176,6 +187,35 @@ def test_lc_numeric_basic(self): if not tested: self.skipTest('no suitable locales') + @unittest.skipUnless(nl_langinfo, "nl_langinfo is not available") + @unittest.skipUnless(hasattr(locale, 'ALT_DIGITS'), "requires locale.ALT_DIGITS") + @unittest.skipIf( + support.is_emscripten or support.is_wasi, + "musl libc issue on Emscripten, bpo-46390" + ) + def test_alt_digits_nl_langinfo(self): + # Test nl_langinfo(ALT_DIGITS) + tested = False + for loc, (count, samples) in known_alt_digits.items(): + with self.subTest(locale=loc): + try: + setlocale(LC_TIME, loc) + setlocale(LC_CTYPE, loc) + except Error: + self.skipTest(f'no locale {loc!r}') + continue + with self.subTest(locale=loc): + alt_digits = nl_langinfo(locale.ALT_DIGITS) + self.assertIsInstance(alt_digits, tuple) + if count and not alt_digits and support.is_apple: + self.skipTest(f'ALT_DIGITS is not set for locale {loc!r} on Apple platforms') + self.assertEqual(len(alt_digits), count) + for i in samples: + self.assertEqual(alt_digits[i], samples[i]) + tested = True + if not tested: + self.skipTest('no suitable locales') + def test_float_parsing(self): # Bug #1391872: Test whether float parsing is okay on European # locales. diff --git a/Misc/NEWS.d/next/Library/2024-10-08-12-09-09.gh-issue-124969._VBQLq.rst b/Misc/NEWS.d/next/Library/2024-10-08-12-09-09.gh-issue-124969._VBQLq.rst new file mode 100644 index 00000000000000..b5082b90721d42 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-10-08-12-09-09.gh-issue-124969._VBQLq.rst @@ -0,0 +1,3 @@ +Fix ``locale.nl_langinfo(locale.ALT_DIGITS)``. Now it returns a tuple of up +to 100 strings (an empty tuple on most locales). Previously it returned the +first item of that tuple or an empty string. diff --git a/Modules/_localemodule.c b/Modules/_localemodule.c index f080b97034c0c0..3dea764fdaad27 100644 --- a/Modules/_localemodule.c +++ b/Modules/_localemodule.c @@ -618,7 +618,34 @@ _locale_nl_langinfo_impl(PyObject *module, int item) instead of an empty string for nl_langinfo(ERA). */ const char *result = nl_langinfo(item); result = result != NULL ? result : ""; - return PyUnicode_DecodeLocale(result, NULL); + PyObject *pyresult; +#ifdef ALT_DIGITS + if (item == ALT_DIGITS) { + /* The result is a sequence of up to 100 NUL-separated strings. */ + const char *s = result; + int count = 0; + for (; count < 100 && *s; count++) { + s += strlen(s) + 1; + } + pyresult = PyTuple_New(count); + if (pyresult != NULL) { + for (int i = 0; i < count; i++) { + PyObject *unicode = PyUnicode_DecodeLocale(result, NULL); + if (unicode == NULL) { + Py_CLEAR(pyresult); + break; + } + PyTuple_SET_ITEM(pyresult, i, unicode); + result += strlen(result) + 1; + } + } + } + else +#endif + { + pyresult = PyUnicode_DecodeLocale(result, NULL); + } + return pyresult; } PyErr_SetString(PyExc_ValueError, "unsupported langinfo constant"); return NULL; From ee1257cb9ca913745fd96730b739f538bce60f0a Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Fri, 11 Oct 2024 08:39:54 +0200 Subject: [PATCH 040/269] [3.12] gh-125296: Fix strange fragment identifier for `name or flags` in argparse docs (GH-125297) (#125300) gh-125296: Fix strange fragment identifier for `name or flags` in argparse docs (GH-125297) (cherry picked from commit c1913effeed4e4da4d5310a40ab518945001ffba) Co-authored-by: Savannah Ostrowski --- Doc/library/argparse.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/argparse.rst b/Doc/library/argparse.rst index 6d8b207cbc03ab..bcbdb0516bba93 100644 --- a/Doc/library/argparse.rst +++ b/Doc/library/argparse.rst @@ -596,7 +596,7 @@ The add_argument() method The following sections describe how each of these are used. -.. _name_or_flags: +.. _`name or flags`: name or flags ^^^^^^^^^^^^^ From 4c40381023df1319661b27f4ab65075569532c73 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Fri, 11 Oct 2024 10:22:34 +0200 Subject: [PATCH 041/269] [3.12] gh-125058: update `_thread` docs regarding interruptibility of `lock.acquire()` (GH-125141) (#125307) gh-125058: update `_thread` docs regarding interruptibility of `lock.acquire()` (GH-125141) (cherry picked from commit 0135848059162ad81478a7776fec622d68a36524) Co-authored-by: Jan Kaliszewski --- Doc/library/_thread.rst | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Doc/library/_thread.rst b/Doc/library/_thread.rst index d82f63834dd2d1..e5cbff0b1ef4bc 100644 --- a/Doc/library/_thread.rst +++ b/Doc/library/_thread.rst @@ -216,9 +216,11 @@ In addition to these methods, lock objects can also be used via the * Calling :func:`sys.exit` or raising the :exc:`SystemExit` exception is equivalent to calling :func:`_thread.exit`. -* It is not possible to interrupt the :meth:`~threading.Lock.acquire` method on - a lock --- the :exc:`KeyboardInterrupt` exception will happen after the lock - has been acquired. +* It is platform-dependent whether the :meth:`~threading.Lock.acquire` method + on a lock can be interrupted (so that the :exc:`KeyboardInterrupt` exception + will happen immediately, rather than only after the lock has been acquired or + the operation has timed out). It can be interrupted on POSIX, but not on + Windows. * When the main thread exits, it is system defined whether the other threads survive. On most systems, they are killed without executing From 23cefd9f4c9e49fbdef50b7deaf76499760e4c4b Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Fri, 11 Oct 2024 11:07:03 +0200 Subject: [PATCH 042/269] [3.12] gh-61011: Fix inheritance of nested mutually exclusive groups in argparse (GH-125210) (GH-125309) Previously, all nested mutually exclusive groups lost their connection to the group containing them and were displayed as belonging directly to the parser. (cherry picked from commit 18c74497681e0107d7cde53e63ea42feb38f2176) Co-authored-by: Serhiy Storchaka Co-authored-by: Danica J. Sutherland --- Lib/argparse.py | 6 +++- Lib/test/test_argparse.py | 29 +++++++++++++++++++ Misc/ACKS | 1 + ...4-10-09-21-42-43.gh-issue-61011.pQXZb1.rst | 4 +++ 4 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2024-10-09-21-42-43.gh-issue-61011.pQXZb1.rst diff --git a/Lib/argparse.py b/Lib/argparse.py index bd5c757197fcf8..2a3253453dff84 100644 --- a/Lib/argparse.py +++ b/Lib/argparse.py @@ -1564,7 +1564,11 @@ def _add_container_actions(self, container): # NOTE: if add_mutually_exclusive_group ever gains title= and # description= then this code will need to be expanded as above for group in container._mutually_exclusive_groups: - mutex_group = self.add_mutually_exclusive_group( + if group._container is container: + cont = self + else: + cont = title_group_map[group._container.title] + mutex_group = cont.add_mutually_exclusive_group( required=group.required) # map the actions to their new mutex group diff --git a/Lib/test/test_argparse.py b/Lib/test/test_argparse.py index 13d15fbf075861..84f8b09fb1d2a4 100644 --- a/Lib/test/test_argparse.py +++ b/Lib/test/test_argparse.py @@ -2880,6 +2880,35 @@ def test_groups_parents(self): -x X '''.format(progname, ' ' if progname else '' ))) + def test_mutex_groups_parents(self): + parent = ErrorRaisingArgumentParser(add_help=False) + g = parent.add_argument_group(title='g', description='gd') + g.add_argument('-w') + g.add_argument('-x') + m = g.add_mutually_exclusive_group() + m.add_argument('-y') + m.add_argument('-z') + parser = ErrorRaisingArgumentParser(prog='PROG', parents=[parent]) + + self.assertRaises(ArgumentParserError, parser.parse_args, + ['-y', 'Y', '-z', 'Z']) + + parser_help = parser.format_help() + self.assertEqual(parser_help, textwrap.dedent('''\ + usage: PROG [-h] [-w W] [-x X] [-y Y | -z Z] + + options: + -h, --help show this help message and exit + + g: + gd + + -w W + -x X + -y Y + -z Z + ''')) + # ============================== # Mutually exclusive group tests # ============================== diff --git a/Misc/ACKS b/Misc/ACKS index b5dc32ee507689..837ffbda18aea1 100644 --- a/Misc/ACKS +++ b/Misc/ACKS @@ -1792,6 +1792,7 @@ Reuben Sumner Eryk Sun Sanjay Sundaresan Marek Šuppa +Danica J. Sutherland Hisao Suzuki Kalle Svensson Andrew Svetlov diff --git a/Misc/NEWS.d/next/Library/2024-10-09-21-42-43.gh-issue-61011.pQXZb1.rst b/Misc/NEWS.d/next/Library/2024-10-09-21-42-43.gh-issue-61011.pQXZb1.rst new file mode 100644 index 00000000000000..20f9c0b9c78b12 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-10-09-21-42-43.gh-issue-61011.pQXZb1.rst @@ -0,0 +1,4 @@ +Fix inheritance of nested mutually exclusive groups from parent parser in +:class:`argparse.ArgumentParser`. Previously, all nested mutually exclusive +groups lost their connection to the group containing them and were displayed +as belonging directly to the parser. From 7c48c630213ada618c7c7b20aa6af88273d75650 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Fri, 11 Oct 2024 11:52:15 +0200 Subject: [PATCH 043/269] [3.12] Add some doctest cleanups for `configparser` (GH-125288) (#125291) Co-authored-by: Alex Waygood Co-authored-by: Adam Turner <9087854+AA-Turner@users.noreply.github.com> --- Doc/library/configparser.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/Doc/library/configparser.rst b/Doc/library/configparser.rst index 5f04cbc42bf374..4f3549d9a8c0a3 100644 --- a/Doc/library/configparser.rst +++ b/Doc/library/configparser.rst @@ -54,6 +54,7 @@ can be customized by end users easily. import os os.remove("example.ini") + os.remove("override.ini") Quick Start From 59036318bfeae049fe13e16b7169caa0e2c00fcd Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Fri, 11 Oct 2024 14:22:27 +0300 Subject: [PATCH 044/269] [3.12] gh-125301: Backport some test support helpers (is_apple_mobile, is_apple) (GH-125311) (cherry picked from commit 391659b3da570bfa28fed5fbdb6f2d9c26ab3dd0) --- Lib/test/support/__init__.py | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py index 9c3dcbc1d2bc29..5432b1ec5c9d84 100644 --- a/Lib/test/support/__init__.py +++ b/Lib/test/support/__init__.py @@ -43,7 +43,7 @@ "requires_limited_api", "requires_specialization", # sys "MS_WINDOWS", "is_jython", "is_android", "is_emscripten", "is_wasi", - "check_impl_detail", "unix_shell", "setswitchinterval", + "is_apple_mobile", "check_impl_detail", "unix_shell", "setswitchinterval", # os "get_pagesize", # network @@ -531,7 +531,7 @@ def requires_legacy_unicode_capi(): is_android = hasattr(sys, 'getandroidapilevel') -if sys.platform not in ('win32', 'vxworks'): +if sys.platform not in {"win32", "vxworks", "ios", "tvos", "watchos"}: unix_shell = '/system/bin/sh' if is_android else '/bin/sh' else: unix_shell = None @@ -541,19 +541,35 @@ def requires_legacy_unicode_capi(): is_emscripten = sys.platform == "emscripten" is_wasi = sys.platform == "wasi" -has_fork_support = hasattr(os, "fork") and not is_emscripten and not is_wasi +# Apple mobile platforms (iOS/tvOS/watchOS) are POSIX-like but do not +# have subprocess or fork support. +is_apple_mobile = sys.platform in {"ios", "tvos", "watchos"} +is_apple = is_apple_mobile or sys.platform == "darwin" + +has_fork_support = hasattr(os, "fork") and not ( + is_emscripten + or is_wasi + or is_apple_mobile +) def requires_fork(): return unittest.skipUnless(has_fork_support, "requires working os.fork()") -has_subprocess_support = not is_emscripten and not is_wasi +has_subprocess_support = not ( + is_emscripten + or is_wasi + or is_apple_mobile +) def requires_subprocess(): """Used for subprocess, os.spawn calls, fd inheritance""" return unittest.skipUnless(has_subprocess_support, "requires subprocess support") # Emscripten's socket emulation and WASI sockets have limitations. -has_socket_support = not is_emscripten and not is_wasi +has_socket_support = not ( + is_emscripten + or is_wasi +) def requires_working_socket(*, module=False): """Skip tests or modules that require working sockets From 4ab19f912d9220d9762a1f2a0bda2add8af6ae94 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Fri, 11 Oct 2024 15:18:46 -0700 Subject: [PATCH 045/269] [3.12] gh-124917: Allow keyword args to os.path.exists/lexists on Windows (GH-124918) (#125334) (cherry picked from commit cc2938a18967c9d462ebb18bc09f73e4364aa7d2) --- Lib/test/test_genericpath.py | 5 +++ ...-10-02-21-11-18.gh-issue-124917.Lnwh5b.rst | 2 + Modules/clinic/posixmodule.c.h | 40 ++++++++++++++++--- Modules/posixmodule.c | 3 +- 4 files changed, 43 insertions(+), 7 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-10-02-21-11-18.gh-issue-124917.Lnwh5b.rst diff --git a/Lib/test/test_genericpath.py b/Lib/test/test_genericpath.py index bdfc5bfe260799..3eefb722b81e55 100644 --- a/Lib/test/test_genericpath.py +++ b/Lib/test/test_genericpath.py @@ -158,6 +158,11 @@ def test_exists(self): self.assertIs(self.pathmodule.lexists(filename + '\x00'), False) self.assertIs(self.pathmodule.lexists(bfilename + b'\x00'), False) + # Keyword arguments are accepted + self.assertIs(self.pathmodule.exists(path=filename), True) + if self.pathmodule is not genericpath: + self.assertIs(self.pathmodule.lexists(path=filename), True) + @unittest.skipUnless(hasattr(os, "pipe"), "requires os.pipe()") @unittest.skipIf(is_emscripten, "Emscripten pipe fds have no stat") def test_exists_fd(self): diff --git a/Misc/NEWS.d/next/Library/2024-10-02-21-11-18.gh-issue-124917.Lnwh5b.rst b/Misc/NEWS.d/next/Library/2024-10-02-21-11-18.gh-issue-124917.Lnwh5b.rst new file mode 100644 index 00000000000000..6218528d2079c3 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-10-02-21-11-18.gh-issue-124917.Lnwh5b.rst @@ -0,0 +1,2 @@ +Allow calling :func:`os.path.exists` and :func:`os.path.lexists` with +keyword arguments on Windows. Fixes a regression in 3.12.4. diff --git a/Modules/clinic/posixmodule.c.h b/Modules/clinic/posixmodule.c.h index f46aa65148da7d..a33461dc5600dd 100644 --- a/Modules/clinic/posixmodule.c.h +++ b/Modules/clinic/posixmodule.c.h @@ -1975,25 +1975,55 @@ os__path_splitroot(PyObject *module, PyObject *const *args, Py_ssize_t nargs, Py #if defined(MS_WINDOWS) PyDoc_STRVAR(os__path_exists__doc__, -"_path_exists($module, path, /)\n" +"_path_exists($module, /, path)\n" "--\n" "\n" "Test whether a path exists. Returns False for broken symbolic links."); #define OS__PATH_EXISTS_METHODDEF \ - {"_path_exists", (PyCFunction)os__path_exists, METH_O, os__path_exists__doc__}, + {"_path_exists", _PyCFunction_CAST(os__path_exists), METH_FASTCALL|METH_KEYWORDS, os__path_exists__doc__}, static int os__path_exists_impl(PyObject *module, path_t *path); static PyObject * -os__path_exists(PyObject *module, PyObject *arg) +os__path_exists(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + + #define NUM_KEYWORDS 1 + static struct { + PyGC_Head _this_is_not_used; + PyObject_VAR_HEAD + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_item = { &_Py_ID(path), }, + }; + #undef NUM_KEYWORDS + #define KWTUPLE (&_kwtuple.ob_base.ob_base) + + #else // !Py_BUILD_CORE + # define KWTUPLE NULL + #endif // !Py_BUILD_CORE + + static const char * const _keywords[] = {"path", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "_path_exists", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[1]; path_t path = PATH_T_INITIALIZE_P("_path_exists", "path", 0, 0, 1, 1); int _return_value; - if (!path_converter(arg, &path)) { + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 1, 1, 0, argsbuf); + if (!args) { + goto exit; + } + if (!path_converter(args[0], &path)) { goto exit; } _return_value = os__path_exists_impl(module, &path); @@ -12002,4 +12032,4 @@ os_waitstatus_to_exitcode(PyObject *module, PyObject *const *args, Py_ssize_t na #ifndef OS_WAITSTATUS_TO_EXITCODE_METHODDEF #define OS_WAITSTATUS_TO_EXITCODE_METHODDEF #endif /* !defined(OS_WAITSTATUS_TO_EXITCODE_METHODDEF) */ -/*[clinic end generated code: output=67c2e3d4537287c1 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=6d34c4564aca7725 input=a9049054013a1b77]*/ diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 2277caee58f505..b8558cc2265a58 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -5209,7 +5209,6 @@ _testFileType(path_t *path, int testedType) os._path_exists -> bool path: path_t(allow_fd=True, suppress_value_error=True) - / Test whether a path exists. Returns False for broken symbolic links. @@ -5217,7 +5216,7 @@ Test whether a path exists. Returns False for broken symbolic links. static int os__path_exists_impl(PyObject *module, path_t *path) -/*[clinic end generated code: output=8da13acf666e16ba input=29198507a6082a57]*/ +/*[clinic end generated code: output=8da13acf666e16ba input=142beabfc66783eb]*/ { return _testFileExists(path, TRUE); } From 3f38ea11c029b68472d6c23aec3cf711e3e62d3c Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Sat, 12 Oct 2024 01:23:54 +0200 Subject: [PATCH 046/269] [3.12] gh-116938: Clarify documentation of `dict` and `dict.update` regarding the positional argument they accept (GH-125213) (#125337) Co-authored-by: Victorien <65306057+Viicos@users.noreply.github.com> Co-authored-by: Alex Waygood --- Doc/library/stdtypes.rst | 25 +++++++++++++------------ Lib/_collections_abc.py | 2 +- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/Doc/library/stdtypes.rst b/Doc/library/stdtypes.rst index e72711dc9cb5b7..0c1f29d8b69d97 100644 --- a/Doc/library/stdtypes.rst +++ b/Doc/library/stdtypes.rst @@ -4460,14 +4460,14 @@ can be used interchangeably to index the same dictionary entry. ``dict([('foo', 100), ('bar', 200)])``, ``dict(foo=100, bar=200)`` If no positional argument is given, an empty dictionary is created. - If a positional argument is given and it is a mapping object, a dictionary - is created with the same key-value pairs as the mapping object. Otherwise, - the positional argument must be an :term:`iterable` object. Each item in - the iterable must itself be an iterable with exactly two objects. The - first object of each item becomes a key in the new dictionary, and the - second object the corresponding value. If a key occurs more than once, the - last value for that key becomes the corresponding value in the new - dictionary. + If a positional argument is given and it defines a ``keys()`` method, a + dictionary is created by calling :meth:`~object.__getitem__` on the argument with + each returned key from the method. Otherwise, the positional argument must be an + :term:`iterable` object. Each item in the iterable must itself be an iterable + with exactly two elements. The first element of each item becomes a key in the + new dictionary, and the second element the corresponding value. If a key occurs + more than once, the last value for that key becomes the corresponding value in + the new dictionary. If keyword arguments are given, the keyword arguments and their values are added to the dictionary created from the positional argument. If a key @@ -4624,10 +4624,11 @@ can be used interchangeably to index the same dictionary entry. Update the dictionary with the key/value pairs from *other*, overwriting existing keys. Return ``None``. - :meth:`update` accepts either another dictionary object or an iterable of - key/value pairs (as tuples or other iterables of length two). If keyword - arguments are specified, the dictionary is then updated with those - key/value pairs: ``d.update(red=1, blue=2)``. + :meth:`update` accepts either another object with a ``keys()`` method (in + which case :meth:`~object.__getitem__` is called with every key returned from + the method). or an iterable of key/value pairs (as tuples or other iterables + of length two). If keyword arguments are specified, the dictionary is then + updated with those key/value pairs: ``d.update(red=1, blue=2)``. .. method:: values() diff --git a/Lib/_collections_abc.py b/Lib/_collections_abc.py index 601107d2d86771..09745658de1925 100644 --- a/Lib/_collections_abc.py +++ b/Lib/_collections_abc.py @@ -973,7 +973,7 @@ def clear(self): def update(self, other=(), /, **kwds): ''' D.update([E, ]**F) -> None. Update D from mapping/iterable E and F. - If E present and has a .keys() method, does: for k in E: D[k] = E[k] + If E present and has a .keys() method, does: for k in E.keys(): D[k] = E[k] If E present and lacks .keys() method, does: for (k, v) in E: D[k] = v In either case, this is followed by: for k, v in F.items(): D[k] = v ''' From 2264c097e09810a563aab379cc17e6b5d9d2bf74 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Sat, 12 Oct 2024 02:47:23 +0200 Subject: [PATCH 047/269] [3.12] Doc: Fix a typo in "Function Examples" in the control-flow tutorial (GH-125338) (#125342) Doc: Fix a typo in "Function Examples" in the control-flow tutorial (GH-125338) (cherry picked from commit 5a074aab845f82f4a150c27b905dae05c337d381) Co-authored-by: Rafael Fontenelle --- Doc/tutorial/controlflow.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/tutorial/controlflow.rst b/Doc/tutorial/controlflow.rst index fd765e58ff2485..9b73ac475c78d5 100644 --- a/Doc/tutorial/controlflow.rst +++ b/Doc/tutorial/controlflow.rst @@ -832,7 +832,7 @@ parameters as there is a ``/`` in the function definition:: File "", line 1, in TypeError: pos_only_arg() got some positional-only arguments passed as keyword arguments: 'arg' -The third function ``kwd_only_args`` only allows keyword arguments as indicated +The third function ``kwd_only_arg`` only allows keyword arguments as indicated by a ``*`` in the function definition:: >>> kwd_only_arg(3) From affffc7ddac68d01ab8d63872770a44e96c46c44 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Sat, 12 Oct 2024 05:12:11 +0200 Subject: [PATCH 048/269] [3.12] gh-124309: fix staggered race on eager tasks (GH-124847) (#125340) gh-124309: fix staggered race on eager tasks (GH-124847) This patch is entirely by Thomas and Peter (cherry picked from commit 979c0df7c0adfb744159a5fc184043dc733d8534) Co-authored-by: Thomas Grainger Co-authored-by: Peter Bierma --- Lib/asyncio/staggered.py | 17 +++++-- .../test_asyncio/test_eager_task_factory.py | 46 +++++++++++++++++++ Lib/test/test_asyncio/test_staggered.py | 27 +++++++++++ ...-10-01-13-46-58.gh-issue-124390.dK1Zcm.rst | 1 + 4 files changed, 88 insertions(+), 3 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-10-01-13-46-58.gh-issue-124390.dK1Zcm.rst diff --git a/Lib/asyncio/staggered.py b/Lib/asyncio/staggered.py index c3a7441a7b091d..7aafcea4d885eb 100644 --- a/Lib/asyncio/staggered.py +++ b/Lib/asyncio/staggered.py @@ -69,7 +69,11 @@ async def staggered_race(coro_fns, delay, *, loop=None): exceptions = [] running_tasks = [] - async def run_one_coro(previous_failed) -> None: + async def run_one_coro(ok_to_start, previous_failed) -> None: + # in eager tasks this waits for the calling task to append this task + # to running_tasks, in regular tasks this wait is a no-op that does + # not yield a future. See gh-124309. + await ok_to_start.wait() # Wait for the previous task to finish, or for delay seconds if previous_failed is not None: with contextlib.suppress(exceptions_mod.TimeoutError): @@ -85,8 +89,12 @@ async def run_one_coro(previous_failed) -> None: return # Start task that will run the next coroutine this_failed = locks.Event() - next_task = loop.create_task(run_one_coro(this_failed)) + next_ok_to_start = locks.Event() + next_task = loop.create_task(run_one_coro(next_ok_to_start, this_failed)) running_tasks.append(next_task) + # next_task has been appended to running_tasks so next_task is ok to + # start. + next_ok_to_start.set() assert len(running_tasks) == this_index + 2 # Prepare place to put this coroutine's exceptions if not won exceptions.append(None) @@ -116,8 +124,11 @@ async def run_one_coro(previous_failed) -> None: if i != this_index: t.cancel() - first_task = loop.create_task(run_one_coro(None)) + ok_to_start = locks.Event() + first_task = loop.create_task(run_one_coro(ok_to_start, None)) running_tasks.append(first_task) + # first_task has been appended to running_tasks so first_task is ok to start. + ok_to_start.set() try: # Wait for a growing list of tasks to all finish: poor man's version of # curio's TaskGroup or trio's nursery diff --git a/Lib/test/test_asyncio/test_eager_task_factory.py b/Lib/test/test_asyncio/test_eager_task_factory.py index 58c06287bc3c5d..b06832e02f00d6 100644 --- a/Lib/test/test_asyncio/test_eager_task_factory.py +++ b/Lib/test/test_asyncio/test_eager_task_factory.py @@ -218,6 +218,52 @@ async def run(): self.run_coro(run()) + def test_staggered_race_with_eager_tasks(self): + # See https://github.com/python/cpython/issues/124309 + + async def fail(): + await asyncio.sleep(0) + raise ValueError("no good") + + async def run(): + winner, index, excs = await asyncio.staggered.staggered_race( + [ + lambda: asyncio.sleep(2, result="sleep2"), + lambda: asyncio.sleep(1, result="sleep1"), + lambda: fail() + ], + delay=0.25 + ) + self.assertEqual(winner, 'sleep1') + self.assertEqual(index, 1) + self.assertIsNone(excs[index]) + self.assertIsInstance(excs[0], asyncio.CancelledError) + self.assertIsInstance(excs[2], ValueError) + + self.run_coro(run()) + + def test_staggered_race_with_eager_tasks_no_delay(self): + # See https://github.com/python/cpython/issues/124309 + async def fail(): + raise ValueError("no good") + + async def run(): + winner, index, excs = await asyncio.staggered.staggered_race( + [ + lambda: fail(), + lambda: asyncio.sleep(1, result="sleep1"), + lambda: asyncio.sleep(0, result="sleep0"), + ], + delay=None + ) + self.assertEqual(winner, 'sleep1') + self.assertEqual(index, 1) + self.assertIsNone(excs[index]) + self.assertIsInstance(excs[0], ValueError) + self.assertEqual(len(excs), 2) + + self.run_coro(run()) + class PyEagerTaskFactoryLoopTests(EagerTaskFactoryLoopTests, test_utils.TestCase): Task = tasks._PyTask diff --git a/Lib/test/test_asyncio/test_staggered.py b/Lib/test/test_asyncio/test_staggered.py index e6e32f7dbbbcba..74941f704c4890 100644 --- a/Lib/test/test_asyncio/test_staggered.py +++ b/Lib/test/test_asyncio/test_staggered.py @@ -95,3 +95,30 @@ async def coro(index): self.assertEqual(len(excs), 2) self.assertIsInstance(excs[0], ValueError) self.assertIsInstance(excs[1], ValueError) + + + async def test_multiple_winners(self): + event = asyncio.Event() + + async def coro(index): + await event.wait() + return index + + async def do_set(): + event.set() + await asyncio.Event().wait() + + winner, index, excs = await staggered_race( + [ + lambda: coro(0), + lambda: coro(1), + do_set, + ], + delay=0.1, + ) + self.assertIs(winner, 0) + self.assertIs(index, 0) + self.assertEqual(len(excs), 3) + self.assertIsNone(excs[0], None) + self.assertIsInstance(excs[1], asyncio.CancelledError) + self.assertIsInstance(excs[2], asyncio.CancelledError) diff --git a/Misc/NEWS.d/next/Library/2024-10-01-13-46-58.gh-issue-124390.dK1Zcm.rst b/Misc/NEWS.d/next/Library/2024-10-01-13-46-58.gh-issue-124390.dK1Zcm.rst new file mode 100644 index 00000000000000..89610fa44bf743 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-10-01-13-46-58.gh-issue-124390.dK1Zcm.rst @@ -0,0 +1 @@ +Fixed :exc:`AssertionError` when using :func:`!asyncio.staggered.staggered_race` with :attr:`asyncio.eager_task_factory`. From aa0cdeb93cedef1e28bc3e4dc6cd4c5c3b1425d6 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Sat, 12 Oct 2024 15:00:24 +0200 Subject: [PATCH 049/269] [3.12] gh-125254: Fix error report about ambiguous option in argparse (GH-125273) (GH-125360) This was a regression introduced in gh-58573. It was only tested for the case when the ambiguous option is the last argument in the command line. (cherry picked from commit 63cf4e914f879ee28a75c02e867baa7c6047ea2b) Co-authored-by: Serhiy Storchaka --- Lib/argparse.py | 2 +- Lib/test/test_argparse.py | 14 ++++++++++++-- .../2024-10-10-19-57-35.gh-issue-125254.RtZxXS.rst | 1 + 3 files changed, 14 insertions(+), 3 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-10-10-19-57-35.gh-issue-125254.RtZxXS.rst diff --git a/Lib/argparse.py b/Lib/argparse.py index 2a3253453dff84..0e13ea5860da97 100644 --- a/Lib/argparse.py +++ b/Lib/argparse.py @@ -2025,7 +2025,7 @@ def consume_optional(start_index): if len(option_tuples) > 1: options = ', '.join([option_string for action, option_string, sep, explicit_arg in option_tuples]) - args = {'option': arg_string, 'matches': options} + args = {'option': arg_strings[start_index], 'matches': options} msg = _('ambiguous option: %(option)s could match %(matches)s') raise ArgumentError(None, msg % args) diff --git a/Lib/test/test_argparse.py b/Lib/test/test_argparse.py index 84f8b09fb1d2a4..956c1cd505a96e 100644 --- a/Lib/test/test_argparse.py +++ b/Lib/test/test_argparse.py @@ -6310,9 +6310,19 @@ def test_conflicting_mutually_exclusive_args_zero_or_more_with_metavar2(self): def test_ambiguous_option(self): self.parser.add_argument('--foobaz') self.parser.add_argument('--fooble', action='store_true') + self.parser.add_argument('--foogle') self.assertRaisesRegex(argparse.ArgumentError, - "ambiguous option: --foob could match --foobaz, --fooble", - self.parser.parse_args, ['--foob']) + "ambiguous option: --foob could match --foobaz, --fooble", + self.parser.parse_args, ['--foob']) + self.assertRaisesRegex(argparse.ArgumentError, + "ambiguous option: --foob=1 could match --foobaz, --fooble$", + self.parser.parse_args, ['--foob=1']) + self.assertRaisesRegex(argparse.ArgumentError, + "ambiguous option: --foob could match --foobaz, --fooble$", + self.parser.parse_args, ['--foob', '1', '--foogle', '2']) + self.assertRaisesRegex(argparse.ArgumentError, + "ambiguous option: --foob=1 could match --foobaz, --fooble$", + self.parser.parse_args, ['--foob=1', '--foogle', '2']) def test_os_error(self): self.parser.add_argument('file') diff --git a/Misc/NEWS.d/next/Library/2024-10-10-19-57-35.gh-issue-125254.RtZxXS.rst b/Misc/NEWS.d/next/Library/2024-10-10-19-57-35.gh-issue-125254.RtZxXS.rst new file mode 100644 index 00000000000000..abe37fefedc3be --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-10-10-19-57-35.gh-issue-125254.RtZxXS.rst @@ -0,0 +1 @@ +Fix a bug where ArgumentError includes the incorrect ambiguous option in :mod:`argparse`. From e01a1784dbfe366cd2d5e22bba8b964107651bf0 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Sat, 12 Oct 2024 15:02:14 +0200 Subject: [PATCH 050/269] [3.12] gh-85935: Explicitly document the case nargs=0 in argparse (GH-125302) (GH-125358) (cherry picked from commit 07c2d15977738165e9dc4248e7edda7c75ecc14b) Co-authored-by: Serhiy Storchaka --- Doc/library/argparse.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Doc/library/argparse.rst b/Doc/library/argparse.rst index bcbdb0516bba93..a1f08ea272934f 100644 --- a/Doc/library/argparse.rst +++ b/Doc/library/argparse.rst @@ -726,6 +726,9 @@ how the command-line arguments should be handled. The supplied actions are: .. versionadded:: 3.8 +Only actions that consume command-line arguments (e.g. ``'store'``, +``'append'`` or ``'extend'``) can be used with positional arguments. + You may also specify an arbitrary action by passing an Action subclass or other object that implements the same interface. The ``BooleanOptionalAction`` is available in ``argparse`` and adds support for boolean actions such as @@ -853,6 +856,8 @@ See also :ref:`specifying-ambiguous-arguments`. The supported values are: If the ``nargs`` keyword argument is not provided, the number of arguments consumed is determined by the action_. Generally this means a single command-line argument will be consumed and a single item (not a list) will be produced. +Actions that do not consume command-line arguments (e.g. +``'store_const'``) set ``nargs=0``. .. _const: From 331fc017ce6afdf778464f6e9540670bb6a0fa4f Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Sat, 12 Oct 2024 20:02:52 +0200 Subject: [PATCH 051/269] [3.12] gh-53203: Fix strptime() for %c and %x formats on many locales (GH-124946) (GH-125370) In some locales (like French or Hebrew) the full or abbreviated names of the default month and weekday used in __calc_date_time can be part of other name or constant part of the %c format. The month name can also match %m with constant suffix (like in Japanese). So the code failed to correctly distinguish formats %a, %A, %b, %B and %m. Cycle all month and all days of the week to find the variable part and distinguish %a from %A and %b from %B or %m. Fixed locales for the following languges: Arabic, Bislama, Breton, Bodo, Kashubian, Chuvash, Estonian, French, Irish, Ge'ez, Gurajati, Manx Gaelic, Hebrew, Hindi, Chhattisgarhi, Haitian Kreyol, Japanese, Kannada, Korean, Marathi, Malay, Norwegian, Nynorsk, Punjabi, Rajasthani, Tok Pisin, Yoruba, Yue Chinese, Yau/Nungon and Chinese. (cherry picked from commit c05f9dde8a12dfd63d3ade93da616042df2dc925) Co-authored-by: Serhiy Storchaka Co-authored-by: Eli Bendersky --- Lib/_strptime.py | 127 +++++++++++++++--- Lib/test/test_strptime.py | 36 +++-- ...4-10-03-20-45-57.gh-issue-53203.3Sk4Ia.rst | 5 + 3 files changed, 134 insertions(+), 34 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-10-03-20-45-57.gh-issue-53203.3Sk4Ia.rst diff --git a/Lib/_strptime.py b/Lib/_strptime.py index 798cf9f9d3fffe..d740c15519a75d 100644 --- a/Lib/_strptime.py +++ b/Lib/_strptime.py @@ -27,6 +27,18 @@ def _getlang(): # Figure out what the current language is set to. return locale.getlocale(locale.LC_TIME) +def _findall(haystack, needle): + # Find all positions of needle in haystack. + if not needle: + return + i = 0 + while True: + i = haystack.find(needle, i) + if i < 0: + break + yield i + i += len(needle) + class LocaleTime(object): """Stores and handles locale-specific information related to time. @@ -101,7 +113,8 @@ def __calc_am_pm(self): am_pm = [] for hour in (1, 22): time_tuple = time.struct_time((1999,3,17,hour,44,55,2,76,0)) - am_pm.append(time.strftime("%p", time_tuple).lower()) + # br_FR has AM/PM info (' ',' '). + am_pm.append(time.strftime("%p", time_tuple).lower().strip()) self.am_pm = am_pm def __calc_date_time(self): @@ -113,42 +126,114 @@ def __calc_date_time(self): # values within the format string is very important; it eliminates # possible ambiguity for what something represents. time_tuple = time.struct_time((1999,3,17,22,44,55,2,76,0)) - date_time = [None, None, None] - date_time[0] = time.strftime("%c", time_tuple).lower() - date_time[1] = time.strftime("%x", time_tuple).lower() - date_time[2] = time.strftime("%X", time_tuple).lower() - replacement_pairs = [('%', '%%'), (self.f_weekday[2], '%A'), - (self.f_month[3], '%B'), (self.a_weekday[2], '%a'), - (self.a_month[3], '%b'), (self.am_pm[1], '%p'), + time_tuple2 = time.struct_time((1999,1,3,1,1,1,6,3,0)) + replacement_pairs = [ ('1999', '%Y'), ('99', '%y'), ('22', '%H'), ('44', '%M'), ('55', '%S'), ('76', '%j'), ('17', '%d'), ('03', '%m'), ('3', '%m'), # '3' needed for when no leading zero. ('2', '%w'), ('10', '%I')] - replacement_pairs.extend([(tz, "%Z") for tz_values in self.timezone - for tz in tz_values]) - for offset,directive in ((0,'%c'), (1,'%x'), (2,'%X')): - current_format = date_time[offset] - for old, new in replacement_pairs: + date_time = [] + for directive in ('%c', '%x', '%X'): + current_format = time.strftime(directive, time_tuple).lower() + current_format = current_format.replace('%', '%%') + # The month and the day of the week formats are treated specially + # because of a possible ambiguity in some locales where the full + # and abbreviated names are equal or names of different types + # are equal. See doc of __find_month_format for more details. + lst, fmt = self.__find_weekday_format(directive) + if lst: + current_format = current_format.replace(lst[2], fmt, 1) + lst, fmt = self.__find_month_format(directive) + if lst: + current_format = current_format.replace(lst[3], fmt, 1) + if self.am_pm[1]: # Must deal with possible lack of locale info # manifesting itself as the empty string (e.g., Swedish's # lack of AM/PM info) or a platform returning a tuple of empty # strings (e.g., MacOS 9 having timezone as ('','')). - if old: - current_format = current_format.replace(old, new) + current_format = current_format.replace(self.am_pm[1], '%p') + for tz_values in self.timezone: + for tz in tz_values: + if tz: + current_format = current_format.replace(tz, "%Z") + for old, new in replacement_pairs: + current_format = current_format.replace(old, new) # If %W is used, then Sunday, 2005-01-03 will fall on week 0 since # 2005-01-03 occurs before the first Monday of the year. Otherwise # %U is used. - time_tuple = time.struct_time((1999,1,3,1,1,1,6,3,0)) - if '00' in time.strftime(directive, time_tuple): + if '00' in time.strftime(directive, time_tuple2): U_W = '%W' else: U_W = '%U' - date_time[offset] = current_format.replace('11', U_W) + current_format = current_format.replace('11', U_W) + date_time.append(current_format) self.LC_date_time = date_time[0] self.LC_date = date_time[1] self.LC_time = date_time[2] + def __find_month_format(self, directive): + """Find the month format appropriate for the current locale. + + In some locales (for example French and Hebrew), the default month + used in __calc_date_time has the same name in full and abbreviated + form. Also, the month name can by accident match other part of the + representation: the day of the week name (for example in Morisyen) + or the month number (for example in Japanese). Thus, cycle months + of the year and find all positions that match the month name for + each month, If no common positions are found, the representation + does not use the month name. + """ + full_indices = abbr_indices = None + for m in range(1, 13): + time_tuple = time.struct_time((1999, m, 17, 22, 44, 55, 2, 76, 0)) + datetime = time.strftime(directive, time_tuple).lower() + indices = set(_findall(datetime, self.f_month[m])) + if full_indices is None: + full_indices = indices + else: + full_indices &= indices + indices = set(_findall(datetime, self.a_month[m])) + if abbr_indices is None: + abbr_indices = indices + else: + abbr_indices &= indices + if not full_indices and not abbr_indices: + return None, None + if full_indices: + return self.f_month, '%B' + if abbr_indices: + return self.a_month, '%b' + return None, None + + def __find_weekday_format(self, directive): + """Find the day of the week format appropriate for the current locale. + + Similar to __find_month_format(). + """ + full_indices = abbr_indices = None + for wd in range(7): + time_tuple = time.struct_time((1999, 3, 17, 22, 44, 55, wd, 76, 0)) + datetime = time.strftime(directive, time_tuple).lower() + indices = set(_findall(datetime, self.f_weekday[wd])) + if full_indices is None: + full_indices = indices + else: + full_indices &= indices + if self.f_weekday[wd] != self.a_weekday[wd]: + indices = set(_findall(datetime, self.a_weekday[wd])) + if abbr_indices is None: + abbr_indices = indices + else: + abbr_indices &= indices + if not full_indices and not abbr_indices: + return None, None + if full_indices: + return self.f_weekday, '%A' + if abbr_indices: + return self.a_weekday, '%a' + return None, None + def __calc_timezone(self): # Set self.timezone by using time.tzname. # Do not worry about possibility of time.tzname[0] == time.tzname[1] @@ -186,7 +271,7 @@ def __init__(self, locale_time=None): 'd': r"(?P3[0-1]|[1-2]\d|0[1-9]|[1-9]| [1-9])", 'f': r"(?P[0-9]{1,6})", 'H': r"(?P2[0-3]|[0-1]\d|\d)", - 'I': r"(?P1[0-2]|0[1-9]|[1-9])", + 'I': r"(?P1[0-2]|0[1-9]|[1-9]| [1-9])", 'G': r"(?P\d\d\d\d)", 'j': r"(?P36[0-6]|3[0-5]\d|[1-2]\d\d|0[1-9]\d|00[1-9]|[1-9]\d|0[1-9]|[1-9])", 'm': r"(?P1[0-2]|0[1-9]|[1-9])", @@ -330,8 +415,8 @@ def _strptime(data_string, format="%a %b %d %H:%M:%S %Y"): _regex_cache[format] = format_regex found = format_regex.match(data_string) if not found: - raise ValueError("time data %r does not match format %r" % - (data_string, format)) + raise ValueError("time data %r does not match format %r :: /%s/" % + (data_string, format, format_regex.pattern)) if len(data_string) != found.end(): raise ValueError("unconverted data remains: %s" % data_string[found.end():]) diff --git a/Lib/test/test_strptime.py b/Lib/test/test_strptime.py index 66a9815e69b9c2..fa7915bead83fd 100644 --- a/Lib/test/test_strptime.py +++ b/Lib/test/test_strptime.py @@ -5,6 +5,7 @@ import locale import re import os +import platform import sys from test import support from test.support import skip_if_buggy_ucrt_strfptime, run_with_locales @@ -12,6 +13,13 @@ import _strptime +libc_ver = platform.libc_ver() +if libc_ver[0] == 'glibc': + glibc_ver = tuple(map(int, libc_ver[1].split('.'))) +else: + glibc_ver = None + + class getlang_Tests(unittest.TestCase): """Test _getlang""" def test_basic(self): @@ -476,16 +484,16 @@ def test_bad_timezone(self): # * Year is not included: ha_NG. # * Use non-Gregorian calendar: lo_LA, thai, th_TH. # - # BUG: Generates invalid regexp for br_FR, csb_PL, Arabic. - # BUG: Generates regexp that does not match the current date and time - # for fa_IR, gez_ER, gez_ET, lzh_TW, my_MM, or_IN, shn_MM, yo_NG. # BUG: Generates regexp that does not match the current date and time - # for fa_IR, gez_ER, gez_ET, lzh_TW, my_MM, or_IN, shn_MM, yo_NG, - # fr_FR, ja_JP, he_IL, ko_KR, zh_CN, etc. - @run_with_locales('LC_TIME', 'C', 'en_US', 'de_DE', - 'eu_ES', 'mfe_MU') + # for az_IR, fa_IR, lzh_TW, my_MM, or_IN, shn_MM. + @run_with_locales('LC_TIME', 'C', 'en_US', 'fr_FR', 'de_DE', 'ja_JP', + 'he_IL', 'eu_ES', 'ar_AE', 'mfe_MU', 'yo_NG', + 'csb_PL', 'br_FR', 'gez_ET', 'brx_IN') def test_date_time_locale(self): # Test %c directive + loc = locale.getlocale(locale.LC_TIME)[0] + if glibc_ver and glibc_ver < (2, 31) and loc == 'br_FR': + self.skipTest('%c in locale br_FR does not include time') now = time.time() self.roundtrip('%c', slice(0, 6), time.localtime(now)) # 1 hour 20 minutes 30 seconds ago @@ -503,7 +511,9 @@ def test_date_time_locale(self): # NB: Dates before 1969 do not roundtrip on some locales: # bo_CN, bo_IN, dz_BT, eu_ES, eu_FR. - @run_with_locales('LC_TIME', 'C', 'en_US', 'de_DE', 'ja_JP') + @run_with_locales('LC_TIME', 'C', 'en_US', 'fr_FR', 'de_DE', 'ja_JP', + 'he_IL', 'ar_AE', 'mfe_MU', 'yo_NG', + 'csb_PL', 'br_FR', 'gez_ET', 'brx_IN') def test_date_time_locale2(self): # Test %c directive self.roundtrip('%c', slice(0, 6), (1900, 1, 1, 0, 0, 0, 0, 1, 0)) @@ -511,10 +521,9 @@ def test_date_time_locale2(self): # NB: Does not roundtrip because use non-Gregorian calendar: # lo_LA, thai, th_TH. # BUG: Generates regexp that does not match the current date - # for az_IR, fa_IR, lzh_TW, my_MM, or_IN, shn_MM, - # Arabic, ja_JP, ko_KR, zh_CN, etc. - @run_with_locales('LC_TIME', 'C', 'en_US', 'fr_FR', 'de_DE', - 'he_IL', 'eu_ES') + # for az_IR, fa_IR, lzh_TW, my_MM, or_IN, shn_MM. + @run_with_locales('LC_TIME', 'C', 'en_US', 'fr_FR', 'de_DE', 'ja_JP', + 'he_IL', 'eu_ES', 'ar_AE') def test_date_locale(self): # Test %x directive now = time.time() @@ -533,7 +542,8 @@ def test_date_locale(self): support.is_emscripten or support.is_wasi, "musl libc issue on Emscripten, bpo-46390" ) - @run_with_locales('LC_TIME', 'en_US', 'fr_FR', 'de_DE', 'ja_JP') + @run_with_locales('LC_TIME', 'en_US', 'fr_FR', 'de_DE', 'ja_JP', + 'eu_ES', 'ar_AE') def test_date_locale2(self): # Test %x directive self.roundtrip('%x', slice(0, 3), (1900, 1, 1, 0, 0, 0, 0, 1, 0)) diff --git a/Misc/NEWS.d/next/Library/2024-10-03-20-45-57.gh-issue-53203.3Sk4Ia.rst b/Misc/NEWS.d/next/Library/2024-10-03-20-45-57.gh-issue-53203.3Sk4Ia.rst new file mode 100644 index 00000000000000..6895cffcf545fd --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-10-03-20-45-57.gh-issue-53203.3Sk4Ia.rst @@ -0,0 +1,5 @@ +Fix :func:`time.strptime` for ``%c`` and ``%x`` formats in many locales: +Arabic, Bislama, Breton, Bodo, Kashubian, Chuvash, Estonian, French, Irish, +Ge'ez, Gurajati, Manx Gaelic, Hebrew, Hindi, Chhattisgarhi, Haitian Kreyol, +Japanese, Kannada, Korean, Marathi, Malay, Norwegian, Nynorsk, Punjabi, +Rajasthani, Tok Pisin, Yoruba, Yue Chinese, Yau/Nungon and Chinese. From 1d45fae26dacd9c07d4a57831e224073ef56cbe7 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Sat, 12 Oct 2024 22:44:41 +0200 Subject: [PATCH 052/269] [3.12] gh-125289: Update sample code in asyncio-task.rst (GH-125292) (GH-125375) gh-125289: Update sample code in asyncio-task.rst (GH-125292) * Update sample code in asyncio-task.rst This will change **coroutines** sample code in the **Awaitables** section and make the example clearer. * Update Doc/library/asyncio-task.rst Revert the added print * Update Doc/library/asyncio-task.rst --------- (cherry picked from commit fa52b82c91a8e1a0971bd5fef656473ec93f41e3) Co-authored-by: Ghorban M. Tavakoly <58617996+galmyk@users.noreply.github.com> Co-authored-by: Carol Willing --- Doc/library/asyncio-task.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/asyncio-task.rst b/Doc/library/asyncio-task.rst index e9b45cd2967310..0ba6e84dab5a41 100644 --- a/Doc/library/asyncio-task.rst +++ b/Doc/library/asyncio-task.rst @@ -158,7 +158,7 @@ other coroutines:: # Nothing happens if we just call "nested()". # A coroutine object is created but not awaited, # so it *won't run at all*. - nested() + nested() # will raise a "RuntimeWarning". # Let's do it differently now and await it: print(await nested()) # will print "42". From 77020dae341764ee440c99cf8ba13c7766730b46 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Sun, 13 Oct 2024 09:55:37 +0200 Subject: [PATCH 053/269] [3.12] Trivial change: Update comments in activate about what running hash -r does (GH-125385) (#125388) (cherry picked from commit 82bcaf15890cf85b76b4f62d2dd1710bb49c3ed1) --- Lib/venv/scripts/common/activate | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Lib/venv/scripts/common/activate b/Lib/venv/scripts/common/activate index d5914e0cbb40d5..44df44a7404b43 100644 --- a/Lib/venv/scripts/common/activate +++ b/Lib/venv/scripts/common/activate @@ -14,8 +14,9 @@ deactivate () { unset _OLD_VIRTUAL_PYTHONHOME fi - # Call hash to forget past commands. Without forgetting - # past commands the $PATH changes we made may not be respected + # Call hash to forget past locations. Without forgetting + # past locations the $PATH changes we made may not be respected. + # See "man bash" for more details. hash is usually a builtin of your shell hash -r 2> /dev/null if [ -n "${_OLD_VIRTUAL_PS1:-}" ] ; then From afddaeb7698d2c572364d61e02c4271439650f6d Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Sun, 13 Oct 2024 09:56:04 +0200 Subject: [PATCH 054/269] [3.12] gh-86673: Loosen test_ttk.test_identify() requirements (GH-125335) (#125391) In aeca373b3 (PR gh-12011, issue gh-71500), test_identify() was changed to expect different results on Darwin. Ned's fix was later adjusted by e52f9bee8. This workaround is only needed for some variants of Tk/Tcl on macOS, so we now allow both the workaround and the generic results for these tests. (cherry picked from commit 4197a796ecf3a751ad7245b8d4f980d6d444b614) Co-authored-by: Erlend E. Aasland --- Lib/test/test_ttk/test_widgets.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Lib/test/test_ttk/test_widgets.py b/Lib/test/test_ttk/test_widgets.py index 3e541fbc5a4ded..ceba95c92a1fc1 100644 --- a/Lib/test/test_ttk/test_widgets.py +++ b/Lib/test/test_ttk/test_widgets.py @@ -336,7 +336,8 @@ class EntryTest(AbstractWidgetTest, unittest.TestCase): 'show', 'state', 'style', 'takefocus', 'textvariable', 'validate', 'validatecommand', 'width', 'xscrollcommand', ) - IDENTIFY_AS = 'Entry.field' if sys.platform == 'darwin' else 'textarea' + # bpo-27313: macOS Tk/Tcl may or may not report 'Entry.field'. + IDENTIFY_AS = {'Entry.field', 'textarea'} def setUp(self): super().setUp() @@ -373,8 +374,7 @@ def test_identify(self): self.entry.pack() self.entry.update() - # bpo-27313: macOS Cocoa widget differs from X, allow either - self.assertEqual(self.entry.identify(5, 5), self.IDENTIFY_AS) + self.assertIn(self.entry.identify(5, 5), self.IDENTIFY_AS) self.assertEqual(self.entry.identify(-1, -1), "") self.assertRaises(tkinter.TclError, self.entry.identify, None, 5) @@ -461,7 +461,7 @@ class ComboboxTest(EntryTest, unittest.TestCase): 'validate', 'validatecommand', 'values', 'width', 'xscrollcommand', ) - IDENTIFY_AS = 'Combobox.button' if sys.platform == 'darwin' else 'textarea' + IDENTIFY_AS = {'Combobox.button', 'textarea'} def setUp(self): super().setUp() @@ -1204,7 +1204,7 @@ class SpinboxTest(EntryTest, unittest.TestCase): 'takefocus', 'textvariable', 'to', 'validate', 'validatecommand', 'values', 'width', 'wrap', 'xscrollcommand', ) - IDENTIFY_AS = 'Spinbox.field' if sys.platform == 'darwin' else 'textarea' + IDENTIFY_AS = {'Spinbox.field', 'textarea'} def setUp(self): super().setUp() From a144037ac3501720b5587ee53de590684943639a Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Sun, 13 Oct 2024 10:39:05 +0200 Subject: [PATCH 055/269] [3.12] gh-61698: Use launchctl to detect macOS window manager in tests (GH-118390) (#125393) (cherry picked from commit ce740d46246b28bb675ba9d62214b59be9b8411e) Co-authored-by: Erlend E. Aasland --- Lib/test/support/__init__.py | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py index 5432b1ec5c9d84..78f410e1455852 100644 --- a/Lib/test/support/__init__.py +++ b/Lib/test/support/__init__.py @@ -250,22 +250,16 @@ class USEROBJECTFLAGS(ctypes.Structure): # process not running under the same user id as the current console # user. To avoid that, raise an exception if the window manager # connection is not available. - from ctypes import cdll, c_int, pointer, Structure - from ctypes.util import find_library - - app_services = cdll.LoadLibrary(find_library("ApplicationServices")) - - if app_services.CGMainDisplayID() == 0: - reason = "gui tests cannot run without OS X window manager" + import subprocess + try: + rc = subprocess.run(["launchctl", "managername"], + capture_output=True, check=True) + managername = rc.stdout.decode("utf-8").strip() + except subprocess.CalledProcessError: + reason = "unable to detect macOS launchd job manager" else: - class ProcessSerialNumber(Structure): - _fields_ = [("highLongOfPSN", c_int), - ("lowLongOfPSN", c_int)] - psn = ProcessSerialNumber() - psn_p = pointer(psn) - if ( (app_services.GetCurrentProcess(psn_p) < 0) or - (app_services.SetFrontProcess(psn_p) < 0) ): - reason = "cannot run without OS X gui process" + if managername != "Aqua": + reason = f"{managername=} -- can only run in a macOS GUI session" # check on every platform whether tkinter can actually do anything if not reason: From 00c596d8ad913ef393d5b75d561608fc9ac521fa Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Sun, 13 Oct 2024 13:17:55 +0200 Subject: [PATCH 056/269] [3.12] gh-125383: Update `fib` function comment for accuracy (GH-125386) (#125396) gh-125383: Update `fib` function comment for accuracy (GH-125386) `Doc/tutorial/controlflow.rst`: fix comment for `fib` function (cherry picked from commit 283ea5f3b2b6a18605b8598a979afe263b0f21ce) Co-authored-by: Wulian --- Doc/tutorial/controlflow.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Doc/tutorial/controlflow.rst b/Doc/tutorial/controlflow.rst index 9b73ac475c78d5..b830ce94ba4f47 100644 --- a/Doc/tutorial/controlflow.rst +++ b/Doc/tutorial/controlflow.rst @@ -461,8 +461,8 @@ Defining Functions We can create a function that writes the Fibonacci series to an arbitrary boundary:: - >>> def fib(n): # write Fibonacci series up to n - ... """Print a Fibonacci series up to n.""" + >>> def fib(n): # write Fibonacci series less than n + ... """Print a Fibonacci series less than n.""" ... a, b = 0, 1 ... while a < n: ... print(a, end=' ') From 243a8a9d68a87d103340b894366497101e6b0226 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Sun, 13 Oct 2024 19:45:59 +0200 Subject: [PATCH 057/269] [3.12] gh-101291: Add versionadded directives for PyUnstable_Long_* (GH-125384) (#125408) gh-101291: Add versionadded directives for PyUnstable_Long_* (GH-125384) (cherry picked from commit c6d7b644c2425b397cfb641f336bea70eb8a329a) Co-authored-by: Sergey B Kirpichev --- Doc/c-api/long.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Doc/c-api/long.rst b/Doc/c-api/long.rst index af86810c6b166b..972d69a5194511 100644 --- a/Doc/c-api/long.rst +++ b/Doc/c-api/long.rst @@ -350,6 +350,9 @@ distinguished from a number. Use :c:func:`PyErr_Occurred` to disambiguate. Exactly what values are considered compact is an implementation detail and is subject to change. + .. versionadded:: 3.12 + + .. c:function:: Py_ssize_t PyUnstable_Long_CompactValue(const PyLongObject* op) If *op* is compact, as determined by :c:func:`PyUnstable_Long_IsCompact`, @@ -357,3 +360,5 @@ distinguished from a number. Use :c:func:`PyErr_Occurred` to disambiguate. Otherwise, the return value is undefined. + .. versionadded:: 3.12 + From f49221af4609ea98e0e41bfae0d056f98bdccae4 Mon Sep 17 00:00:00 2001 From: Barney Gale Date: Sun, 13 Oct 2024 19:18:41 +0100 Subject: [PATCH 058/269] [3.12] GH-125069: Fix inconsistent joining in `WindowsPath(PosixPath(...))` (GH-125156) (#125410) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `PurePath.__init__()` incorrectly uses the `_raw_paths` of a given `PurePath` object with a different flavour, even though the procedure to join path segments can differ between flavours. This change makes the `_raw_paths`-enabled deferred joining apply _only_ when the path flavours match. (cherry picked from commit cb8e5995d89d9b90e83cf43310ec50e177484e70) Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> --- Lib/pathlib.py | 4 ++-- Lib/test/test_pathlib.py | 8 ++++++++ .../2024-10-08-21-17-16.gh-issue-125069.0RP0Mx.rst | 4 ++++ 3 files changed, 14 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-10-08-21-17-16.gh-issue-125069.0RP0Mx.rst diff --git a/Lib/pathlib.py b/Lib/pathlib.py index 65ff0ee1977f80..02eb5c25981e31 100644 --- a/Lib/pathlib.py +++ b/Lib/pathlib.py @@ -359,9 +359,9 @@ def __init__(self, *args): paths = [] for arg in args: if isinstance(arg, PurePath): - if arg._flavour is ntpath and self._flavour is posixpath: + if arg._flavour is not self._flavour: # GH-103631: Convert separators for backwards compatibility. - paths.extend(path.replace('\\', '/') for path in arg._raw_paths) + paths.append(arg.as_posix()) else: paths.extend(arg._raw_paths) else: diff --git a/Lib/test/test_pathlib.py b/Lib/test/test_pathlib.py index ca604df70a9a4e..4437f878004c70 100644 --- a/Lib/test/test_pathlib.py +++ b/Lib/test/test_pathlib.py @@ -831,6 +831,14 @@ class PureWindowsPathTest(_BasePurePathTest, unittest.TestCase): ], }) + def test_constructor_nested_foreign_flavour(self): + # See GH-125069. + p1 = pathlib.PurePosixPath('b/c:\\d') + p2 = pathlib.PurePosixPath('b/', 'c:\\d') + self.assertEqual(p1, p2) + self.assertEqual(self.cls(p1), self.cls('b/c:/d')) + self.assertEqual(self.cls(p2), self.cls('b/c:/d')) + def test_drive_root_parts(self): check = self._check_drive_root_parts # First part is anchored. diff --git a/Misc/NEWS.d/next/Library/2024-10-08-21-17-16.gh-issue-125069.0RP0Mx.rst b/Misc/NEWS.d/next/Library/2024-10-08-21-17-16.gh-issue-125069.0RP0Mx.rst new file mode 100644 index 00000000000000..73d5fa59303d4d --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-10-08-21-17-16.gh-issue-125069.0RP0Mx.rst @@ -0,0 +1,4 @@ +Fix an issue where providing a :class:`pathlib.PurePath` object as an +initializer argument to a second :class:`~pathlib.PurePath` object with a +different flavour resulted in arguments to the former object's initializer + being joined by the latter object's flavour. From 20323bf733810d7dc7e6bcd86dfbc16b7f3c4b30 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Mon, 14 Oct 2024 06:51:59 +0200 Subject: [PATCH 059/269] [3.12] gh-123133: clarify p=0 case for "f" and "e" formatting types (GH-125426) (#125429) gh-123133: clarify p=0 case for "f" and "e" formatting types (GH-125426) (cherry picked from commit cfc27bc50fe165330f2295f9ac0ad56ca5b0f31c) Co-authored-by: Sergey B Kirpichev Co-authored-by: Serhiy Storchaka --- Doc/library/string.rst | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/Doc/library/string.rst b/Doc/library/string.rst index 57a1f920523035..49aeb28d57c8d1 100644 --- a/Doc/library/string.rst +++ b/Doc/library/string.rst @@ -509,9 +509,8 @@ The available presentation types for :class:`float` and | | significant digits. With no precision given, uses a | | | precision of ``6`` digits after the decimal point for | | | :class:`float`, and shows all coefficient digits | - | | for :class:`~decimal.Decimal`. If no digits follow the | - | | decimal point, the decimal point is also removed unless | - | | the ``#`` option is used. | + | | for :class:`~decimal.Decimal`. If ``p=0``, the decimal | + | | point is omitted unless the ``#`` option is used. | +---------+----------------------------------------------------------+ | ``'E'`` | Scientific notation. Same as ``'e'`` except it uses | | | an upper case 'E' as the separator character. | @@ -522,9 +521,8 @@ The available presentation types for :class:`float` and | | precision given, uses a precision of ``6`` digits after | | | the decimal point for :class:`float`, and uses a | | | precision large enough to show all coefficient digits | - | | for :class:`~decimal.Decimal`. If no digits follow the | - | | decimal point, the decimal point is also removed unless | - | | the ``#`` option is used. | + | | for :class:`~decimal.Decimal`. If ``p=0``, the decimal | + | | point is omitted unless the ``#`` option is used. | +---------+----------------------------------------------------------+ | ``'F'`` | Fixed-point notation. Same as ``'f'``, but converts | | | ``nan`` to ``NAN`` and ``inf`` to ``INF``. | From 21524eec48f5b1c807f185253e9350cfdd897ce0 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Mon, 14 Oct 2024 10:04:44 +0300 Subject: [PATCH 060/269] [3.12] gh-86357: argparse: use str() consistently and explicitly to print choices (GH-117766) (GH-125432) (cherry picked from commit 66b3922b97388c328c9bd8df050eef11c0261fae) Signed-off-by: Jan Chren ~rindeal Co-authored-by: rindeal --- Lib/argparse.py | 12 +++---- Lib/test/test_argparse.py | 31 ++++++++++++++++++- ...-04-19-05-58-50.gh-issue-117766.J3xepp.rst | 1 + 3 files changed, 36 insertions(+), 8 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-04-19-05-58-50.gh-issue-117766.J3xepp.rst diff --git a/Lib/argparse.py b/Lib/argparse.py index 0e13ea5860da97..22c9b07db18a4f 100644 --- a/Lib/argparse.py +++ b/Lib/argparse.py @@ -588,8 +588,7 @@ def _metavar_formatter(self, action, default_metavar): if action.metavar is not None: result = action.metavar elif action.choices is not None: - choice_strs = [str(choice) for choice in action.choices] - result = '{%s}' % ','.join(choice_strs) + result = '{%s}' % ','.join(map(str, action.choices)) else: result = default_metavar @@ -637,8 +636,7 @@ def _expand_help(self, action): if hasattr(params[name], '__name__'): params[name] = params[name].__name__ if params.get('choices') is not None: - choices_str = ', '.join([str(c) for c in params['choices']]) - params['choices'] = choices_str + params['choices'] = ', '.join(map(str, params['choices'])) return self._get_help_string(action) % params def _iter_indented_subactions(self, action): @@ -763,7 +761,7 @@ def _get_action_name(argument): elif argument.dest not in (None, SUPPRESS): return argument.dest elif argument.choices: - return '{' + ','.join(argument.choices) + '}' + return '{%s}' % ','.join(map(str, argument.choices)) else: return None @@ -2600,8 +2598,8 @@ def _check_value(self, action, value): if isinstance(choices, str): choices = iter(choices) if value not in choices: - args = {'value': value, - 'choices': ', '.join(map(repr, action.choices))} + args = {'value': str(value), + 'choices': ', '.join(map(str, action.choices))} msg = _('invalid choice: %(value)r (choose from %(choices)s)') raise ArgumentError(action, msg % args) diff --git a/Lib/test/test_argparse.py b/Lib/test/test_argparse.py index 956c1cd505a96e..d8a4a00292d230 100644 --- a/Lib/test/test_argparse.py +++ b/Lib/test/test_argparse.py @@ -15,6 +15,7 @@ import argparse import warnings +from enum import StrEnum from test.support import os_helper from unittest import mock @@ -1021,6 +1022,34 @@ class TestDisallowLongAbbreviationAllowsShortGroupingPrefix(ParserTestCase): ] +class TestStrEnumChoices(TestCase): + class Color(StrEnum): + RED = "red" + GREEN = "green" + BLUE = "blue" + + def test_parse_enum_value(self): + parser = argparse.ArgumentParser() + parser.add_argument('--color', choices=self.Color) + args = parser.parse_args(['--color', 'red']) + self.assertEqual(args.color, self.Color.RED) + + def test_help_message_contains_enum_choices(self): + parser = argparse.ArgumentParser() + parser.add_argument('--color', choices=self.Color, help='Choose a color') + self.assertIn('[--color {red,green,blue}]', parser.format_usage()) + self.assertIn(' --color {red,green,blue}', parser.format_help()) + + def test_invalid_enum_value_raises_error(self): + parser = argparse.ArgumentParser(exit_on_error=False) + parser.add_argument('--color', choices=self.Color) + self.assertRaisesRegex( + argparse.ArgumentError, + r"invalid choice: 'yellow' \(choose from red, green, blue\)", + parser.parse_args, + ['--color', 'yellow'], + ) + # ================ # Positional tests # ================ @@ -2422,7 +2451,7 @@ def test_wrong_argument_subparsers_no_destination_error(self): parser.parse_args(('baz',)) self.assertRegex( excinfo.exception.stderr, - r"error: argument {foo,bar}: invalid choice: 'baz' \(choose from 'foo', 'bar'\)\n$" + r"error: argument {foo,bar}: invalid choice: 'baz' \(choose from foo, bar\)\n$" ) def test_optional_subparsers(self): diff --git a/Misc/NEWS.d/next/Library/2024-04-19-05-58-50.gh-issue-117766.J3xepp.rst b/Misc/NEWS.d/next/Library/2024-04-19-05-58-50.gh-issue-117766.J3xepp.rst new file mode 100644 index 00000000000000..d090f931f0238d --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-04-19-05-58-50.gh-issue-117766.J3xepp.rst @@ -0,0 +1 @@ +Always use :func:`str` to print ``choices`` in :mod:`argparse`. From f1a6f687774ca09255c48d76deb060bd09c8e1ca Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Mon, 14 Oct 2024 17:49:56 +0200 Subject: [PATCH 061/269] [3.12] gh-125461: Remove Python 2 from identifiers in doc (GH-125462) (#125465) gh-125461: Remove Python 2 from identifiers in doc (GH-125462) Remove Python 2 from identifiers in doc (cherry picked from commit 5dac0dceda9097d46a0b5a6ad7c927e002c6c7a5) Co-authored-by: Paul Hoffman --- Doc/reference/lexical_analysis.rst | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Doc/reference/lexical_analysis.rst b/Doc/reference/lexical_analysis.rst index cfae01ba97a555..41d8fbaee97750 100644 --- a/Doc/reference/lexical_analysis.rst +++ b/Doc/reference/lexical_analysis.rst @@ -284,11 +284,10 @@ UAX-31, with elaboration and changes as defined below; see also :pep:`3131` for further details. Within the ASCII range (U+0001..U+007F), the valid characters for identifiers -are the same as in Python 2.x: the uppercase and lowercase letters ``A`` through +include the uppercase and lowercase letters ``A`` through ``Z``, the underscore ``_`` and, except for the first character, the digits ``0`` through ``9``. - -Python 3.0 introduces additional characters from outside the ASCII range (see +Python 3.0 introduced additional characters from outside the ASCII range (see :pep:`3131`). For these characters, the classification uses the version of the Unicode Character Database as included in the :mod:`unicodedata` module. From 92908681557308baa60f86d9ed24c132debfc35b Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Mon, 14 Oct 2024 23:59:01 +0300 Subject: [PATCH 062/269] [3.12] gh-53203: Fix strptime() for %c, %x and %X formats on many locales (GH-125406) (GH-125454) (GH-125483) Fixed most locales that use non-ASCII digits, like Persian, Burmese, Odia and Shan. (cherry picked from commit 5f4e5b598cab86d5fd5727d423c9728221889ed0) (cherry picked from commit cbcdf34a4b69a28a7e31715423fb080cb5cf8075) Co-authored-by: Miss Islington (bot) <31488909+miss-islington@users.noreply.github.com> --- Lib/_strptime.py | 64 ++++++++++++------- Lib/test/test_strptime.py | 48 +++++++++----- Lib/test/test_time.py | 2 +- ...4-10-13-20-21-35.gh-issue-53203.Rz1c8A.rst | 2 + 4 files changed, 74 insertions(+), 42 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-10-13-20-21-35.gh-issue-53203.Rz1c8A.rst diff --git a/Lib/_strptime.py b/Lib/_strptime.py index d740c15519a75d..dfd2bc5d8b4af5 100644 --- a/Lib/_strptime.py +++ b/Lib/_strptime.py @@ -14,6 +14,7 @@ import locale import calendar from re import compile as re_compile +from re import sub as re_sub from re import IGNORECASE from re import escape as re_escape from datetime import (date as datetime_date, @@ -128,11 +129,23 @@ def __calc_date_time(self): time_tuple = time.struct_time((1999,3,17,22,44,55,2,76,0)) time_tuple2 = time.struct_time((1999,1,3,1,1,1,6,3,0)) replacement_pairs = [ - ('1999', '%Y'), ('99', '%y'), ('22', '%H'), - ('44', '%M'), ('55', '%S'), ('76', '%j'), - ('17', '%d'), ('03', '%m'), ('3', '%m'), - # '3' needed for when no leading zero. - ('2', '%w'), ('10', '%I')] + ('1999', '%Y'), ('99', '%y'), ('22', '%H'), + ('44', '%M'), ('55', '%S'), ('76', '%j'), + ('17', '%d'), ('03', '%m'), ('3', '%m'), + # '3' needed for when no leading zero. + ('2', '%w'), ('10', '%I'), + # Non-ASCII digits + ('\u0661\u0669\u0669\u0669', '%Y'), + ('\u0669\u0669', '%Oy'), + ('\u0662\u0662', '%OH'), + ('\u0664\u0664', '%OM'), + ('\u0665\u0665', '%OS'), + ('\u0661\u0667', '%Od'), + ('\u0660\u0663', '%Om'), + ('\u0663', '%Om'), + ('\u0662', '%Ow'), + ('\u0661\u0660', '%OI'), + ] date_time = [] for directive in ('%c', '%x', '%X'): current_format = time.strftime(directive, time_tuple).lower() @@ -157,6 +170,10 @@ def __calc_date_time(self): for tz in tz_values: if tz: current_format = current_format.replace(tz, "%Z") + # Transform all non-ASCII digits to digits in range U+0660 to U+0669. + current_format = re_sub(r'\d(?3[0-1]|[1-2]\d|0[1-9]|[1-9]| [1-9])", 'f': r"(?P[0-9]{1,6})", @@ -295,11 +312,15 @@ def __init__(self, locale_time=None): 'Z': self.__seqToRE((tz for tz_names in self.locale_time.timezone for tz in tz_names), 'Z'), - '%': '%'}) - base.__setitem__('W', base.__getitem__('U').replace('U', 'W')) - base.__setitem__('c', self.pattern(self.locale_time.LC_date_time)) - base.__setitem__('x', self.pattern(self.locale_time.LC_date)) + '%': '%'} + for d in 'dmyHIMS': + mapping['O' + d] = r'(?P<%s>\d\d|\d| \d)' % d + mapping['Ow'] = r'(?P\d)' + mapping['W'] = mapping['U'].replace('U', 'W') + base.__init__(mapping) base.__setitem__('X', self.pattern(self.locale_time.LC_time)) + base.__setitem__('x', self.pattern(self.locale_time.LC_date)) + base.__setitem__('c', self.pattern(self.locale_time.LC_date_time)) def __seqToRE(self, to_convert, directive): """Convert a list to a regex string for matching a directive. @@ -327,21 +348,16 @@ def pattern(self, format): regex syntax are escaped. """ - processed_format = '' # The sub() call escapes all characters that might be misconstrued # as regex syntax. Cannot use re.escape since we have to deal with # format directives (%m, etc.). - regex_chars = re_compile(r"([\\.^$*+?\(\){}\[\]|])") - format = regex_chars.sub(r"\\\1", format) - whitespace_replacement = re_compile(r'\s+') - format = whitespace_replacement.sub(r'\\s+', format) - while '%' in format: - directive_index = format.index('%')+1 - processed_format = "%s%s%s" % (processed_format, - format[:directive_index-1], - self[format[directive_index]]) - format = format[directive_index+1:] - return "%s%s" % (processed_format, format) + format = re_sub(r"([\\.^$*+?\(\){}\[\]|])", r"\\\1", format) + format = re_sub(r'\s+', r'\\s+', format) + format = re_sub(r"'", "['\u02bc]", format) # needed for br_FR + def repl(m): + return self[m[1]] + format = re_sub(r'%(O?.)', repl, format) + return format def compile(self, format): """Return a compiled re object for the format string.""" @@ -415,8 +431,8 @@ def _strptime(data_string, format="%a %b %d %H:%M:%S %Y"): _regex_cache[format] = format_regex found = format_regex.match(data_string) if not found: - raise ValueError("time data %r does not match format %r :: /%s/" % - (data_string, format, format_regex.pattern)) + raise ValueError("time data %r does not match format %r" % + (data_string, format)) if len(data_string) != found.end(): raise ValueError("unconverted data remains: %s" % data_string[found.end():]) diff --git a/Lib/test/test_strptime.py b/Lib/test/test_strptime.py index fa7915bead83fd..45b5c8ed2c3051 100644 --- a/Lib/test/test_strptime.py +++ b/Lib/test/test_strptime.py @@ -290,7 +290,7 @@ def test_strptime_exception_context(self): # additional check for IndexError branch (issue #19545) with self.assertRaises(ValueError) as e: _strptime._strptime_time('19', '%Y %') - self.assertIs(e.exception.__suppress_context__, True) + self.assertIsNone(e.exception.__context__) def test_unconverteddata(self): # Check ValueError is raised when there is unconverted data @@ -483,12 +483,14 @@ def test_bad_timezone(self): # id_ID, ms_MY. # * Year is not included: ha_NG. # * Use non-Gregorian calendar: lo_LA, thai, th_TH. + # On Windows: ar_IN, ar_SA, fa_IR, ps_AF. # # BUG: Generates regexp that does not match the current date and time - # for az_IR, fa_IR, lzh_TW, my_MM, or_IN, shn_MM. + # for lzh_TW. @run_with_locales('LC_TIME', 'C', 'en_US', 'fr_FR', 'de_DE', 'ja_JP', 'he_IL', 'eu_ES', 'ar_AE', 'mfe_MU', 'yo_NG', - 'csb_PL', 'br_FR', 'gez_ET', 'brx_IN') + 'csb_PL', 'br_FR', 'gez_ET', 'brx_IN', + 'my_MM', 'or_IN', 'shn_MM', 'az_IR') def test_date_time_locale(self): # Test %c directive loc = locale.getlocale(locale.LC_TIME)[0] @@ -510,20 +512,23 @@ def test_date_time_locale(self): self.roundtrip('%c', slice(0, 6), time.localtime(now - 366*24*3600)) # NB: Dates before 1969 do not roundtrip on some locales: - # bo_CN, bo_IN, dz_BT, eu_ES, eu_FR. + # az_IR, bo_CN, bo_IN, dz_BT, eu_ES, eu_FR, fa_IR, or_IN. @run_with_locales('LC_TIME', 'C', 'en_US', 'fr_FR', 'de_DE', 'ja_JP', 'he_IL', 'ar_AE', 'mfe_MU', 'yo_NG', - 'csb_PL', 'br_FR', 'gez_ET', 'brx_IN') + 'csb_PL', 'br_FR', 'gez_ET', 'brx_IN', + 'my_MM', 'shn_MM') def test_date_time_locale2(self): # Test %c directive self.roundtrip('%c', slice(0, 6), (1900, 1, 1, 0, 0, 0, 0, 1, 0)) + self.roundtrip('%c', slice(0, 6), (1800, 1, 1, 0, 0, 0, 0, 1, 0)) # NB: Does not roundtrip because use non-Gregorian calendar: - # lo_LA, thai, th_TH. + # lo_LA, thai, th_TH. On Windows: ar_IN, ar_SA, fa_IR, ps_AF. # BUG: Generates regexp that does not match the current date - # for az_IR, fa_IR, lzh_TW, my_MM, or_IN, shn_MM. + # for lzh_TW. @run_with_locales('LC_TIME', 'C', 'en_US', 'fr_FR', 'de_DE', 'ja_JP', - 'he_IL', 'eu_ES', 'ar_AE') + 'he_IL', 'eu_ES', 'ar_AE', + 'az_IR', 'my_MM', 'or_IN', 'shn_MM') def test_date_locale(self): # Test %x directive now = time.time() @@ -543,10 +548,11 @@ def test_date_locale(self): "musl libc issue on Emscripten, bpo-46390" ) @run_with_locales('LC_TIME', 'en_US', 'fr_FR', 'de_DE', 'ja_JP', - 'eu_ES', 'ar_AE') + 'eu_ES', 'ar_AE', 'my_MM', 'shn_MM') def test_date_locale2(self): # Test %x directive self.roundtrip('%x', slice(0, 3), (1900, 1, 1, 0, 0, 0, 0, 1, 0)) + self.roundtrip('%x', slice(0, 3), (1800, 1, 1, 0, 0, 0, 0, 1, 0)) # NB: Does not roundtrip in some locales due to the ambiguity of # the time representation (bugs in locales?): @@ -554,19 +560,27 @@ def test_date_locale2(self): # norwegian, nynorsk. # * Hours are in 12-hour notation without AM/PM indication: hy_AM, # ms_MY, sm_WS. - # BUG: Generates regexp that does not match the current time for - # aa_DJ, aa_ER, aa_ET, am_ET, az_IR, byn_ER, fa_IR, gez_ER, gez_ET, - # lzh_TW, my_MM, om_ET, om_KE, or_IN, shn_MM, sid_ET, so_DJ, so_ET, - # so_SO, ti_ER, ti_ET, tig_ER, wal_ET. - @run_with_locales('LC_TIME', 'C', 'en_US', 'fr_FR', 'de_DE', 'ja_JP') + # BUG: Generates regexp that does not match the current time for lzh_TW. + @run_with_locales('LC_TIME', 'C', 'en_US', 'fr_FR', 'de_DE', 'ja_JP', + 'aa_ET', 'am_ET', 'az_IR', 'byn_ER', 'fa_IR', 'gez_ET', + 'my_MM', 'om_ET', 'or_IN', 'shn_MM', 'sid_ET', 'so_SO', + 'ti_ET', 'tig_ER', 'wal_ET') def test_time_locale(self): # Test %X directive + loc = locale.getlocale(locale.LC_TIME)[0] + pos = slice(3, 6) + if glibc_ver and glibc_ver < (2, 29) and loc in { + 'aa_ET', 'am_ET', 'byn_ER', 'gez_ET', 'om_ET', + 'sid_ET', 'so_SO', 'ti_ET', 'tig_ER', 'wal_ET'}: + # Hours are in 12-hour notation without AM/PM indication. + # Ignore hours. + pos = slice(4, 6) now = time.time() - self.roundtrip('%X', slice(3, 6), time.localtime(now)) + self.roundtrip('%X', pos, time.localtime(now)) # 1 hour 20 minutes 30 seconds ago - self.roundtrip('%X', slice(3, 6), time.localtime(now - 4830)) + self.roundtrip('%X', pos, time.localtime(now - 4830)) # 12 hours ago - self.roundtrip('%X', slice(3, 6), time.localtime(now - 12*3600)) + self.roundtrip('%X', pos, time.localtime(now - 12*3600)) def test_percent(self): # Make sure % signs are handled properly diff --git a/Lib/test/test_time.py b/Lib/test/test_time.py index 921e4eea649d6b..ca40415f0cdd76 100644 --- a/Lib/test/test_time.py +++ b/Lib/test/test_time.py @@ -292,7 +292,7 @@ def test_strptime_exception_context(self): # additional check for IndexError branch (issue #19545) with self.assertRaises(ValueError) as e: time.strptime('19', '%Y %') - self.assertIs(e.exception.__suppress_context__, True) + self.assertIsNone(e.exception.__context__) def test_asctime(self): time.asctime(time.gmtime(self.t)) diff --git a/Misc/NEWS.d/next/Library/2024-10-13-20-21-35.gh-issue-53203.Rz1c8A.rst b/Misc/NEWS.d/next/Library/2024-10-13-20-21-35.gh-issue-53203.Rz1c8A.rst new file mode 100644 index 00000000000000..cdfa8c191e8242 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-10-13-20-21-35.gh-issue-53203.Rz1c8A.rst @@ -0,0 +1,2 @@ +Fix :func:`time.strptime` for ``%c``, ``%x`` and ``%X`` formats in many +locales that use non-ASCII digits, like Persian, Burmese, Odia and Shan. From 86296bbc178aa2f7094c8d7ab9eb4175f78d105f Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Mon, 14 Oct 2024 23:34:38 +0200 Subject: [PATCH 063/269] [3.12] Fix idlelib typos (GH-125484) (#125488) Fix idlelib typos (GH-125484) Propagate fixes in Doc/library/idle.rst to help.html. Change 'interruptable' to 'interruptible' in run.py. The latter was reported by ember91 in PR 125473. (cherry picked from commit 3fea1d000ef0a74062fd3fe218ad94618b08d9f2) Co-authored-by: Terry Jan Reedy --- Lib/idlelib/help.html | 21 ++++++++++++--------- Lib/idlelib/run.py | 12 ++++++------ 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/Lib/idlelib/help.html b/Lib/idlelib/help.html index 827d230b54e159..2a4adc6a4d395f 100644 --- a/Lib/idlelib/help.html +++ b/Lib/idlelib/help.html @@ -5,7 +5,7 @@ - IDLE — Python 3.13.0a2 documentation + IDLE — Python 3.14.0a0 documentation @@ -18,7 +18,7 @@ @@ -26,6 +26,7 @@ + @@ -45,6 +46,8 @@ + + @@ -184,7 +187,7 @@

Navigation

  • - 3.13.0a2 Documentation » + 3.14.0a0 Documentation »
  • @@ -554,7 +557,7 @@

    Key bindingsControl key on Windows and -Unix and the Command key on macOS. (And all such dicussions +Unix and the Command key on macOS. (And all such discussions assume that the keys have not been re-bound to something else.)

    @@ -694,7 +697,7 @@

    Shell window -
  • C-c attemps to interrupt statement execution (but may fail).

  • +
  • C-c attempts to interrupt statement execution (but may fail).

  • C-d closes Shell if typed at a >>> prompt.

  • Alt-p and Alt-n (C-p and C-n on macOS) retrieve to the current prompt the previous or next previously @@ -1136,7 +1139,7 @@

    Navigation

  • - 3.13.0a2 Documentation » + 3.14.0a0 Documentation »
  • @@ -1180,7 +1183,7 @@

    Navigation



    - Last updated on Jan 17, 2024 (06:57 UTC). + Last updated on Oct 14, 2024 (20:27 UTC). Found a bug?
    diff --git a/Lib/idlelib/run.py b/Lib/idlelib/run.py index 476a7b26c004b5..2faeba678c7262 100644 --- a/Lib/idlelib/run.py +++ b/Lib/idlelib/run.py @@ -101,11 +101,11 @@ def handle_tk_events(tcl=tcl): # Thread shared globals: Establish a queue between a subthread (which handles # the socket) and the main thread (which runs user code), plus global -# completion, exit and interruptable (the main thread) flags: +# completion, exit and interruptible (the main thread) flags: exit_now = False quitting = False -interruptable = False +interruptible = False def main(del_exitfunc=False): """Start the Python execution server in a subprocess @@ -575,14 +575,14 @@ def __init__(self, rpchandler): self.locals = {} def runcode(self, code): - global interruptable + global interruptible try: self.user_exc_info = None - interruptable = True + interruptible = True try: exec(code, self.locals) finally: - interruptable = False + interruptible = False except SystemExit as e: if e.args: # SystemExit called with an argument. ob = e.args[0] @@ -608,7 +608,7 @@ def runcode(self, code): flush_stdout() def interrupt_the_server(self): - if interruptable: + if interruptible: thread.interrupt_main() def start_the_debugger(self, gui_adap_oid): From 449f2c98bdd04694a987639a91f4fab366c2e9d4 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Tue, 15 Oct 2024 00:09:23 +0200 Subject: [PATCH 064/269] [3.12] gh-85453: Improve variable mark up for datetime.rst (GH-120702) (#125491) Variables and literals are marked up using backticks. (cherry picked from commit 2a5cdb251674ce8d9a824c102f7cd846d944cfa4) Co-authored-by: edson duarte --- Doc/library/datetime.rst | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/Doc/library/datetime.rst b/Doc/library/datetime.rst index 671554f2cf3d0d..54ab05e4e66549 100644 --- a/Doc/library/datetime.rst +++ b/Doc/library/datetime.rst @@ -180,19 +180,19 @@ Objects of the :class:`date` type are always naive. An object of type :class:`.time` or :class:`.datetime` may be aware or naive. -A :class:`.datetime` object *d* is aware if both of the following hold: +A :class:`.datetime` object ``d`` is aware if both of the following hold: 1. ``d.tzinfo`` is not ``None`` 2. ``d.tzinfo.utcoffset(d)`` does not return ``None`` -Otherwise, *d* is naive. +Otherwise, ``d`` is naive. -A :class:`.time` object *t* is aware if both of the following hold: +A :class:`.time` object ``t`` is aware if both of the following hold: 1. ``t.tzinfo`` is not ``None`` 2. ``t.tzinfo.utcoffset(None)`` does not return ``None``. -Otherwise, *t* is naive. +Otherwise, ``t`` is naive. The distinction between aware and naive doesn't apply to :class:`timedelta` objects. @@ -358,8 +358,8 @@ Supported operations: +--------------------------------+-----------------------------------------------+ | ``q, r = divmod(t1, t2)`` | Computes the quotient and the remainder: | | | ``q = t1 // t2`` (3) and ``r = t1 % t2``. | -| | q is an integer and r is a :class:`timedelta` | -| | object. | +| | ``q`` is an integer and ``r`` is a | +| | :class:`timedelta` object. | +--------------------------------+-----------------------------------------------+ | ``+t1`` | Returns a :class:`timedelta` object with the | | | same value. (2) | @@ -526,7 +526,7 @@ Other constructors, all class methods: January 1 of year 1 has ordinal 1. :exc:`ValueError` is raised unless ``1 <= ordinal <= - date.max.toordinal()``. For any date *d*, + date.max.toordinal()``. For any date ``d``, ``date.fromordinal(d.toordinal()) == d``. @@ -678,7 +678,7 @@ Instance methods: .. method:: date.toordinal() Return the proleptic Gregorian ordinal of the date, where January 1 of year 1 - has ordinal 1. For any :class:`date` object *d*, + has ordinal 1. For any :class:`date` object ``d``, ``date.fromordinal(d.toordinal()) == d``. @@ -730,7 +730,7 @@ Instance methods: .. method:: date.__str__() - For a date *d*, ``str(d)`` is equivalent to ``d.isoformat()``. + For a date ``d``, ``str(d)`` is equivalent to ``d.isoformat()``. .. method:: date.ctime() @@ -1011,7 +1011,7 @@ Other constructors, all class methods: is used. If the *date* argument is a :class:`.datetime` object, its time components and :attr:`.tzinfo` attributes are ignored. - For any :class:`.datetime` object *d*, + For any :class:`.datetime` object ``d``, ``d == datetime.combine(d.date(), d.time(), d.tzinfo)``. .. versionchanged:: 3.6 @@ -1200,11 +1200,11 @@ Supported operations: If both are naive, or both are aware and have the same :attr:`~.datetime.tzinfo` attribute, the :attr:`~.datetime.tzinfo` attributes are ignored, and the result is a :class:`timedelta` - object *t* such that ``datetime2 + t == datetime1``. No time zone adjustments + object ``t`` such that ``datetime2 + t == datetime1``. No time zone adjustments are done in this case. If both are aware and have different :attr:`~.datetime.tzinfo` attributes, ``a-b`` acts - as if *a* and *b* were first converted to naive UTC datetimes. The + as if ``a`` and ``b`` were first converted to naive UTC datetimes. The result is ``(a.replace(tzinfo=None) - a.utcoffset()) - (b.replace(tzinfo=None) - b.utcoffset())`` except that the implementation never overflows. @@ -1377,11 +1377,11 @@ Instance methods: .. method:: datetime.utctimetuple() - If :class:`.datetime` instance *d* is naive, this is the same as + If :class:`.datetime` instance ``d`` is naive, this is the same as ``d.timetuple()`` except that :attr:`~.time.struct_time.tm_isdst` is forced to 0 regardless of what ``d.dst()`` returns. DST is never in effect for a UTC time. - If *d* is aware, *d* is normalized to UTC time, by subtracting + If ``d`` is aware, ``d`` is normalized to UTC time, by subtracting ``d.utcoffset()``, and a :class:`time.struct_time` for the normalized time is returned. :attr:`!tm_isdst` is forced to 0. Note that an :exc:`OverflowError` may be raised if ``d.year`` was @@ -1529,7 +1529,7 @@ Instance methods: .. method:: datetime.__str__() - For a :class:`.datetime` instance *d*, ``str(d)`` is equivalent to + For a :class:`.datetime` instance ``d``, ``str(d)`` is equivalent to ``d.isoformat(' ')``. @@ -1776,7 +1776,7 @@ Instance attributes (read-only): .. versionadded:: 3.6 :class:`.time` objects support equality and order comparisons, -where *a* is considered less than *b* when *a* precedes *b* in time. +where ``a`` is considered less than ``b`` when ``a`` precedes ``b`` in time. Naive and aware :class:`!time` objects are never equal. Order comparison between naive and aware :class:`!time` objects raises @@ -1904,7 +1904,7 @@ Instance methods: .. method:: time.__str__() - For a time *t*, ``str(t)`` is equivalent to ``t.isoformat()``. + For a time ``t``, ``str(t)`` is equivalent to ``t.isoformat()``. .. method:: time.strftime(format) From 79422bc13b17331211619354b7d8acaccafcc79a Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Tue, 15 Oct 2024 09:36:17 +0200 Subject: [PATCH 065/269] [3.12] Doc: Update CVE URL (GH-125489) (#125504) Co-authored-by: Mariusz Felisiak --- Doc/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/conf.py b/Doc/conf.py index 36f6243969c8b2..fb55c5c65c2f35 100644 --- a/Doc/conf.py +++ b/Doc/conf.py @@ -593,7 +593,7 @@ # Sphinx 8.1 has in-built CVE and CWE roles. extlinks |= { "cve": ( - "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-%s", + "https://www.cve.org/CVERecord?id=CVE-%s", "CVE-%s", ), "cwe": ("https://cwe.mitre.org/data/definitions/%s.html", "CWE-%s"), From 26725d1756424b8abff896ffed7b017dd59241fc Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Tue, 15 Oct 2024 16:28:23 +0200 Subject: [PATCH 066/269] [3.12] gh-125514: fix bug in test_traceback utility. Specify exception types in except: clauses (GH-125516) (#125525) gh-125514: fix bug in test_traceback utility. Specify exception types in except: clauses (GH-125516) (cherry picked from commit 55c4f4c30b49734ce35dc88139b8b4fdc94c66fd) Co-authored-by: Irit Katriel <1055913+iritkatriel@users.noreply.github.com> --- Lib/test/test_traceback.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/Lib/test/test_traceback.py b/Lib/test/test_traceback.py index d12b559cf076d6..119143e4f3a009 100644 --- a/Lib/test/test_traceback.py +++ b/Lib/test/test_traceback.py @@ -138,7 +138,7 @@ def test_no_caret_with_no_debug_ranges_flag_python_traceback(self): import traceback try: x = 1 / 0 - except: + except ZeroDivisionError: traceback.print_exc() """) try: @@ -386,9 +386,10 @@ class PurePythonExceptionFormattingMixin: def get_exception(self, callable, slice_start=0, slice_end=-1): try: callable() - self.fail("No exception thrown.") - except: + except BaseException: return traceback.format_exc().splitlines()[slice_start:slice_end] + else: + self.fail("No exception thrown.") callable_line = get_exception.__code__.co_firstlineno + 2 @@ -1490,7 +1491,7 @@ def test_context_suppression(self): try: try: raise Exception - except: + except Exception: raise ZeroDivisionError from None except ZeroDivisionError as _: e = _ @@ -1838,9 +1839,9 @@ def exc(): try: try: raise EG("eg1", [ValueError(1), TypeError(2)]) - except: + except EG: raise EG("eg2", [ValueError(3), TypeError(4)]) - except: + except EG: raise ImportError(5) expected = ( @@ -1889,7 +1890,7 @@ def exc(): except Exception as e: exc = e raise EG("eg", [VE(1), exc, VE(4)]) - except: + except EG: raise EG("top", [VE(5)]) expected = (f' + Exception Group Traceback (most recent call last):\n' @@ -2642,7 +2643,7 @@ def test_long_context_chain(self): def f(): try: 1/0 - except: + except ZeroDivisionError: f() try: @@ -2731,7 +2732,7 @@ def test_comparison_params_variations(self): def raise_exc(): try: raise ValueError('bad value') - except: + except ValueError: raise def raise_with_locals(): From 0a82c4cbd8c0b74e6111370d3c498142fdf9f085 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Tue, 15 Oct 2024 17:26:41 +0200 Subject: [PATCH 067/269] [3.12] gh-125422: Don't set the caller's f_trace if it's botframe (GH-125427) (#125531) gh-125422: Don't set the caller's f_trace if it's botframe (GH-125427) (cherry picked from commit 703227dd021491ceb9343f69fa48f4b6a05adbb3) Co-authored-by: Tian Gao --- Lib/bdb.py | 5 +++-- Lib/test/test_bdb.py | 13 +++++++++++++ Lib/test/test_pdb.py | 14 ++++++++++++++ .../2024-10-14-04-44-12.gh-issue-125422.MlVuC6.rst | 1 + 4 files changed, 31 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-10-14-04-44-12.gh-issue-125422.MlVuC6.rst diff --git a/Lib/bdb.py b/Lib/bdb.py index 564d6c5e5324ed..196e6b178cb9fd 100644 --- a/Lib/bdb.py +++ b/Lib/bdb.py @@ -295,9 +295,10 @@ def _set_caller_tracefunc(self, current_frame): # Issue #13183: pdb skips frames after hitting a breakpoint and running # step commands. # Restore the trace function in the caller (that may not have been set - # for performance reasons) when returning from the current frame. + # for performance reasons) when returning from the current frame, unless + # the caller is the botframe. caller_frame = current_frame.f_back - if caller_frame and not caller_frame.f_trace: + if caller_frame and not caller_frame.f_trace and caller_frame is not self.botframe: caller_frame.f_trace = self.trace_dispatch # Derived classes and clients can call the following methods diff --git a/Lib/test/test_bdb.py b/Lib/test/test_bdb.py index 568c88e326c087..33e28592f59e1b 100644 --- a/Lib/test/test_bdb.py +++ b/Lib/test/test_bdb.py @@ -1203,6 +1203,19 @@ def main(): with TracerRun(self) as tracer: tracer.runcall(tfunc_import) + def test_next_to_botframe(self): + # gh-125422 + # Check that next command won't go to the bottom frame. + code = """ + lno = 2 + """ + self.expect_set = [ + ('line', 2, ''), ('step', ), + ('return', 2, ''), ('next', ), + ] + with TracerRun(self) as tracer: + tracer.run(compile(textwrap.dedent(code), '', 'exec')) + class TestRegressions(unittest.TestCase): def test_format_stack_entry_no_lineno(self): diff --git a/Lib/test/test_pdb.py b/Lib/test/test_pdb.py index 8a7e41b281165e..6da82fe04c0eaa 100644 --- a/Lib/test/test_pdb.py +++ b/Lib/test/test_pdb.py @@ -2283,6 +2283,20 @@ def test_issue26053(self): self.assertRegex(res, "Restarting .* with arguments:\na b c") self.assertRegex(res, "Restarting .* with arguments:\nd e f") + def test_step_into_botframe(self): + # gh-125422 + # pdb should not be able to step into the botframe (bdb.py) + script = "x = 1" + commands = """ + step + step + step + quit + """ + stdout, _ = self.run_pdb_script(script, commands) + self.assertIn("The program finished", stdout) + self.assertNotIn("bdb.py", stdout) + def test_pdbrc_basic(self): script = textwrap.dedent(""" a = 1 diff --git a/Misc/NEWS.d/next/Library/2024-10-14-04-44-12.gh-issue-125422.MlVuC6.rst b/Misc/NEWS.d/next/Library/2024-10-14-04-44-12.gh-issue-125422.MlVuC6.rst new file mode 100644 index 00000000000000..c890ecec8beaf8 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-10-14-04-44-12.gh-issue-125422.MlVuC6.rst @@ -0,0 +1 @@ +Fixed the bug where :mod:`pdb` and :mod:`bdb` can step into the bottom caller frame. From 1cab726622985a5596529521f471aea8588747c3 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Tue, 15 Oct 2024 18:09:55 +0200 Subject: [PATCH 068/269] [3.12] gh-89819: Add argument_default and conflict_handler to add_argument_group() docs (GH-125379) (GH-125539) (cherry picked from commit c9826c11db25e81b1a90c837f84074879f1b1126) Co-authored-by: Savannah Ostrowski --- Doc/library/argparse.rst | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Doc/library/argparse.rst b/Doc/library/argparse.rst index a1f08ea272934f..87d9a45539a1dd 100644 --- a/Doc/library/argparse.rst +++ b/Doc/library/argparse.rst @@ -1740,7 +1740,8 @@ FileType objects Argument groups ^^^^^^^^^^^^^^^ -.. method:: ArgumentParser.add_argument_group(title=None, description=None) +.. method:: ArgumentParser.add_argument_group(title=None, description=None, *, \ + [argument_default], [conflict_handler]) By default, :class:`ArgumentParser` groups command-line arguments into "positional arguments" and "options" when displaying help @@ -1785,6 +1786,11 @@ Argument groups --bar BAR bar help + The optional, keyword-only parameters argument_default_ and conflict_handler_ + allow for finer-grained control of the behavior of the argument group. These + parameters have the same meaning as in the :class:`ArgumentParser` constructor, + but apply specifically to the argument group rather than the entire parser. + Note that any arguments not in your user-defined groups will end up back in the usual "positional arguments" and "optional arguments" sections. From 90b1406b881aa0a57ce3a73c402b73b79138b7e0 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Tue, 15 Oct 2024 18:29:05 +0200 Subject: [PATCH 069/269] [3.12] gh-100141: Allow pdb to deal with empty file (GH-125425) (#125537) gh-100141: Allow pdb to deal with empty file (GH-125425) (cherry picked from commit bb9604b62ae7f043594ffea9287f9213067cc7fb) Co-authored-by: Tian Gao --- Lib/pdb.py | 3 +-- Lib/test/test_pdb.py | 10 ++++++++++ .../2024-10-14-02-27-03.gh-issue-100141.NuAcwa.rst | 1 + 3 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-10-14-02-27-03.gh-issue-100141.NuAcwa.rst diff --git a/Lib/pdb.py b/Lib/pdb.py index 89cf975164ac04..559517813cf600 100755 --- a/Lib/pdb.py +++ b/Lib/pdb.py @@ -321,8 +321,7 @@ def user_call(self, frame, argument_list): def user_line(self, frame): """This function is called when we stop or break at this line.""" if self._wait_for_mainpyfile: - if (self.mainpyfile != self.canonic(frame.f_code.co_filename) - or frame.f_lineno <= 0): + if (self.mainpyfile != self.canonic(frame.f_code.co_filename)): return self._wait_for_mainpyfile = False if self.bp_commands(frame): diff --git a/Lib/test/test_pdb.py b/Lib/test/test_pdb.py index 6da82fe04c0eaa..1542bb3bee1aea 100644 --- a/Lib/test/test_pdb.py +++ b/Lib/test/test_pdb.py @@ -2754,6 +2754,16 @@ def _create_fake_frozen_module(): # verify that pdb found the source of the "frozen" function self.assertIn('x = "Sentinel string for gh-93696"', stdout, "Sentinel statement not found") + def test_empty_file(self): + script = '' + commands = 'q\n' + # We check that pdb stopped at line 0, but anything reasonable + # is acceptable here, as long as it does not halt + stdout, _ = self.run_pdb_script(script, commands) + self.assertIn('main.py(0)', stdout) + stdout, _ = self.run_pdb_module(script, commands) + self.assertIn('__main__.py(0)', stdout) + def test_non_utf8_encoding(self): script_dir = os.path.join(os.path.dirname(__file__), 'encoded_modules') for filename in os.listdir(script_dir): diff --git a/Misc/NEWS.d/next/Library/2024-10-14-02-27-03.gh-issue-100141.NuAcwa.rst b/Misc/NEWS.d/next/Library/2024-10-14-02-27-03.gh-issue-100141.NuAcwa.rst new file mode 100644 index 00000000000000..c366b0ad4040d3 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-10-14-02-27-03.gh-issue-100141.NuAcwa.rst @@ -0,0 +1 @@ +Fixed the bug where :mod:`pdb` will be stuck in an infinite loop when debugging an empty file. From cbd50a4bdc7ec474221334324a09e5f2053adea6 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Wed, 16 Oct 2024 14:44:37 +0200 Subject: [PATCH 070/269] [3.12] gh-125041: test_zlib: For s390x HW acceleration, only skip checking the compressed bytes (GH-125042) (GH-125526) (cherry picked from commit cc5a225cdc2a5d4e035dd08d59cef39182c10a6c) Co-authored-by: Petr Viktorin --- Lib/test/support/__init__.py | 6 ++--- Lib/test/test_zlib.py | 25 ++++++++++++------- ...-10-07-14-13-38.gh-issue-125041.PKLWDf.rst | 3 +++ 3 files changed, 22 insertions(+), 12 deletions(-) create mode 100644 Misc/NEWS.d/next/Tests/2024-10-07-14-13-38.gh-issue-125041.PKLWDf.rst diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py index 78f410e1455852..ba57eb307c0b8f 100644 --- a/Lib/test/support/__init__.py +++ b/Lib/test/support/__init__.py @@ -2450,9 +2450,9 @@ def adjust_int_max_str_digits(max_digits): else: C_RECURSION_LIMIT = 10000 -#Windows doesn't have os.uname() but it doesn't support s390x. -skip_on_s390x = unittest.skipIf(hasattr(os, 'uname') and os.uname().machine == 's390x', - 'skipped on s390x') +# Windows doesn't have os.uname() but it doesn't support s390x. +is_s390x = hasattr(os, 'uname') and os.uname().machine == 's390x' +skip_on_s390x = unittest.skipIf(is_s390x, 'skipped on s390x') _BASE_COPY_SRC_DIR_IGNORED_NAMES = frozenset({ # SRC_DIR/.git diff --git a/Lib/test/test_zlib.py b/Lib/test/test_zlib.py index 0a13986a847f0a..8654b93ec64ac8 100644 --- a/Lib/test/test_zlib.py +++ b/Lib/test/test_zlib.py @@ -7,7 +7,7 @@ import pickle import random import sys -from test.support import bigmemtest, _1G, _4G, skip_on_s390x +from test.support import bigmemtest, _1G, _4G, is_s390x zlib = import_helper.import_module('zlib') @@ -34,8 +34,9 @@ def _zlib_runtime_version_tuple(zlib_version=zlib.ZLIB_RUNTIME_VERSION): ZLIB_RUNTIME_VERSION_TUPLE = _zlib_runtime_version_tuple() -# bpo-46623: On s390x, when a hardware accelerator is used, using different -# ways to compress data with zlib can produce different compressed data. +# bpo-46623: When a hardware accelerator is used (currently only on s390x), +# using different ways to compress data with zlib can produce different +# compressed data. # Simplified test_pair() code: # # def func1(data): @@ -58,8 +59,10 @@ def _zlib_runtime_version_tuple(zlib_version=zlib.ZLIB_RUNTIME_VERSION): # # zlib.decompress(func1(data)) == zlib.decompress(func2(data)) == data # -# Make the assumption that s390x always has an accelerator to simplify the skip -# condition. +# To simplify the skip condition, make the assumption that s390x always has an +# accelerator, and nothing else has it. +HW_ACCELERATED = is_s390x + class VersionTestCase(unittest.TestCase): @@ -224,12 +227,14 @@ def test_keywords(self): bufsize=zlib.DEF_BUF_SIZE), HAMLET_SCENE) - @skip_on_s390x def test_speech128(self): # compress more data data = HAMLET_SCENE * 128 x = zlib.compress(data) - self.assertEqual(zlib.compress(bytearray(data)), x) + # With hardware acceleration, the compressed bytes + # might not be identical. + if not HW_ACCELERATED: + self.assertEqual(zlib.compress(bytearray(data)), x) for ob in x, bytearray(x): self.assertEqual(zlib.decompress(ob), data) @@ -276,7 +281,6 @@ def test_64bit_compress(self, size): class CompressObjectTestCase(BaseCompressTestCase, unittest.TestCase): # Test compression object - @skip_on_s390x def test_pair(self): # straightforward compress/decompress objects datasrc = HAMLET_SCENE * 128 @@ -287,7 +291,10 @@ def test_pair(self): x1 = co.compress(data) x2 = co.flush() self.assertRaises(zlib.error, co.flush) # second flush should not work - self.assertEqual(x1 + x2, datazip) + # With hardware acceleration, the compressed bytes might not + # be identical. + if not HW_ACCELERATED: + self.assertEqual(x1 + x2, datazip) for v1, v2 in ((x1, x2), (bytearray(x1), bytearray(x2))): dco = zlib.decompressobj() y1 = dco.decompress(v1 + v2) diff --git a/Misc/NEWS.d/next/Tests/2024-10-07-14-13-38.gh-issue-125041.PKLWDf.rst b/Misc/NEWS.d/next/Tests/2024-10-07-14-13-38.gh-issue-125041.PKLWDf.rst new file mode 100644 index 00000000000000..c7181eb9c1f3a9 --- /dev/null +++ b/Misc/NEWS.d/next/Tests/2024-10-07-14-13-38.gh-issue-125041.PKLWDf.rst @@ -0,0 +1,3 @@ +Re-enable skipped tests for :mod:`zlib` on the s390x architecture: only skip +checks of the compressed bytes, which can be different between zlib's +software implementation and the hardware-accelerated implementation. From 4256847190e3f87ec357a1a4e8d9eb5c57367d5e Mon Sep 17 00:00:00 2001 From: Sam Gross Date: Wed, 16 Oct 2024 14:03:32 -0400 Subject: [PATCH 071/269] [3.12] gh-125451: Fix deadlock in ProcessPoolExecutor shutdown (GH-125492) (#125599) There was a deadlock when `ProcessPoolExecutor` shuts down at the same time that a queueing thread handles an error processing a task. Don't use `_shutdown_lock` to protect the `_ThreadWakeup` pipes -- use an internal lock instead. This fixes the ordering deadlock where the `ExecutorManagerThread` holds the `_shutdown_lock` and joins the queueing thread, while the queueing thread is attempting to acquire the `_shutdown_lock` while closing the `_ThreadWakeup`. (cherry picked from commit 760872efecb95017db8e38a8eda614bf23d2a22c) --- Lib/concurrent/futures/process.py | 52 ++++++++----------- ...-10-14-17-29-34.gh-issue-125451.fmP3T9.rst | 2 + 2 files changed, 24 insertions(+), 30 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-10-14-17-29-34.gh-issue-125451.fmP3T9.rst diff --git a/Lib/concurrent/futures/process.py b/Lib/concurrent/futures/process.py index 0e452883963c17..ff7c17efaab694 100644 --- a/Lib/concurrent/futures/process.py +++ b/Lib/concurrent/futures/process.py @@ -68,27 +68,31 @@ class _ThreadWakeup: def __init__(self): self._closed = False + self._lock = threading.Lock() self._reader, self._writer = mp.Pipe(duplex=False) def close(self): - # Please note that we do not take the shutdown lock when + # Please note that we do not take the self._lock when # calling clear() (to avoid deadlocking) so this method can # only be called safely from the same thread as all calls to - # clear() even if you hold the shutdown lock. Otherwise we + # clear() even if you hold the lock. Otherwise we # might try to read from the closed pipe. - if not self._closed: - self._closed = True - self._writer.close() - self._reader.close() + with self._lock: + if not self._closed: + self._closed = True + self._writer.close() + self._reader.close() def wakeup(self): - if not self._closed: - self._writer.send_bytes(b"") + with self._lock: + if not self._closed: + self._writer.send_bytes(b"") def clear(self): - if not self._closed: - while self._reader.poll(): - self._reader.recv_bytes() + if self._closed: + raise RuntimeError('operation on closed _ThreadWakeup') + while self._reader.poll(): + self._reader.recv_bytes() def _python_exit(): @@ -167,10 +171,8 @@ def __init__(self, work_id, fn, args, kwargs): class _SafeQueue(Queue): """Safe Queue set exception to the future object linked to a job""" - def __init__(self, max_size=0, *, ctx, pending_work_items, shutdown_lock, - thread_wakeup): + def __init__(self, max_size=0, *, ctx, pending_work_items, thread_wakeup): self.pending_work_items = pending_work_items - self.shutdown_lock = shutdown_lock self.thread_wakeup = thread_wakeup super().__init__(max_size, ctx=ctx) @@ -179,8 +181,7 @@ def _on_queue_feeder_error(self, e, obj): tb = format_exception(type(e), e, e.__traceback__) e.__cause__ = _RemoteTraceback('\n"""\n{}"""'.format(''.join(tb))) work_item = self.pending_work_items.pop(obj.work_id, None) - with self.shutdown_lock: - self.thread_wakeup.wakeup() + self.thread_wakeup.wakeup() # work_item can be None if another process terminated. In this # case, the executor_manager_thread fails all work_items # with BrokenProcessPool @@ -305,12 +306,10 @@ def __init__(self, executor): # will wake up the queue management thread so that it can terminate # if there is no pending work item. def weakref_cb(_, - thread_wakeup=self.thread_wakeup, - shutdown_lock=self.shutdown_lock): + thread_wakeup=self.thread_wakeup): mp.util.debug('Executor collected: triggering callback for' ' QueueManager wakeup') - with shutdown_lock: - thread_wakeup.wakeup() + thread_wakeup.wakeup() self.executor_reference = weakref.ref(executor, weakref_cb) @@ -438,11 +437,6 @@ def wait_result_broken_or_wakeup(self): elif wakeup_reader in ready: is_broken = False - # No need to hold the _shutdown_lock here because: - # 1. we're the only thread to use the wakeup reader - # 2. we're also the only thread to call thread_wakeup.close() - # 3. we want to avoid a possible deadlock when both reader and writer - # would block (gh-105829) self.thread_wakeup.clear() return result_item, is_broken, cause @@ -740,10 +734,9 @@ def __init__(self, max_workers=None, mp_context=None, # as it could result in a deadlock if a worker process dies with the # _result_queue write lock still acquired. # - # _shutdown_lock must be locked to access _ThreadWakeup.close() and - # .wakeup(). Care must also be taken to not call clear or close from - # more than one thread since _ThreadWakeup.clear() is not protected by - # the _shutdown_lock + # Care must be taken to only call clear and close from the + # executor_manager_thread, since _ThreadWakeup.clear() is not protected + # by a lock. self._executor_manager_thread_wakeup = _ThreadWakeup() # Create communication channels for the executor @@ -754,7 +747,6 @@ def __init__(self, max_workers=None, mp_context=None, self._call_queue = _SafeQueue( max_size=queue_size, ctx=self._mp_context, pending_work_items=self._pending_work_items, - shutdown_lock=self._shutdown_lock, thread_wakeup=self._executor_manager_thread_wakeup) # Killed worker processes can produce spurious "broken pipe" # tracebacks in the queue's own worker thread. But we detect killed diff --git a/Misc/NEWS.d/next/Library/2024-10-14-17-29-34.gh-issue-125451.fmP3T9.rst b/Misc/NEWS.d/next/Library/2024-10-14-17-29-34.gh-issue-125451.fmP3T9.rst new file mode 100644 index 00000000000000..589988d4d6273f --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-10-14-17-29-34.gh-issue-125451.fmP3T9.rst @@ -0,0 +1,2 @@ +Fix deadlock when :class:`concurrent.futures.ProcessPoolExecutor` shuts down +concurrently with an error when feeding a job to a worker process. From 42b8e52de41fc82f2985ddda9d40112b6e28a80c Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Thu, 17 Oct 2024 00:25:16 +0200 Subject: [PATCH 072/269] gh-125550: Enable py.exe to detect Store installs of 3.14 (GH-125551) (cherry picked from commit 8e7b2a1161744c7d3d90966a65ed6ae1019a65cb) Co-authored-by: Steve Dower --- .../2024-10-15-21-28-43.gh-issue-125550.hmGWCP.rst | 2 ++ PC/launcher2.c | 9 ++++++--- 2 files changed, 8 insertions(+), 3 deletions(-) create mode 100644 Misc/NEWS.d/next/Windows/2024-10-15-21-28-43.gh-issue-125550.hmGWCP.rst diff --git a/Misc/NEWS.d/next/Windows/2024-10-15-21-28-43.gh-issue-125550.hmGWCP.rst b/Misc/NEWS.d/next/Windows/2024-10-15-21-28-43.gh-issue-125550.hmGWCP.rst new file mode 100644 index 00000000000000..c3ae00c74b3d91 --- /dev/null +++ b/Misc/NEWS.d/next/Windows/2024-10-15-21-28-43.gh-issue-125550.hmGWCP.rst @@ -0,0 +1,2 @@ +Enable the :ref:`launcher` to detect Python 3.14 installs from the Windows +Store. diff --git a/PC/launcher2.c b/PC/launcher2.c index f331aab3f51e56..7a78ed1655a9ee 100644 --- a/PC/launcher2.c +++ b/PC/launcher2.c @@ -1938,6 +1938,7 @@ struct AppxSearchInfo { struct AppxSearchInfo APPX_SEARCH[] = { // Releases made through the Store + { L"PythonSoftwareFoundation.Python.3.14_qbz5n2kfra8p0", L"3.14", 10 }, { L"PythonSoftwareFoundation.Python.3.13_qbz5n2kfra8p0", L"3.13", 10 }, { L"PythonSoftwareFoundation.Python.3.12_qbz5n2kfra8p0", L"3.12", 10 }, { L"PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0", L"3.11", 10 }, @@ -1946,8 +1947,9 @@ struct AppxSearchInfo APPX_SEARCH[] = { { L"PythonSoftwareFoundation.Python.3.8_qbz5n2kfra8p0", L"3.8", 10 }, // Side-loadable releases. Note that the publisher ID changes whenever we - // renew our code-signing certificate, so the newer ID has a higher - // priority (lower sortKey) + // change our code signing certificate subject, so the newer IDs have higher + // priorities (lower sortKey) + { L"PythonSoftwareFoundation.Python.3.14_3847v3x7pw1km", L"3.14", 11 }, { L"PythonSoftwareFoundation.Python.3.13_3847v3x7pw1km", L"3.13", 11 }, { L"PythonSoftwareFoundation.Python.3.12_3847v3x7pw1km", L"3.12", 11 }, { L"PythonSoftwareFoundation.Python.3.11_3847v3x7pw1km", L"3.11", 11 }, @@ -2030,7 +2032,8 @@ struct StoreSearchInfo { struct StoreSearchInfo STORE_SEARCH[] = { - { L"3", /* 3.12 */ L"9NCVDN91XZQP" }, + { L"3", /* 3.13 */ L"9PNRBTZXMB4Z" }, + { L"3.14", L"9NTRHQCBBPR8" }, { L"3.13", L"9PNRBTZXMB4Z" }, { L"3.12", L"9NCVDN91XZQP" }, { L"3.11", L"9NRWMJP3717K" }, From 32d457941e8b39c0300e02632f932d1556b7beee Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Thu, 17 Oct 2024 05:45:59 +0100 Subject: [PATCH 073/269] [3.12] gh-124958: fix asyncio.TaskGroup and _PyFuture refcycles (#124959) (#125466) gh-124958: fix asyncio.TaskGroup and _PyFuture refcycles (#124959) --- Lib/asyncio/futures.py | 6 +- Lib/asyncio/taskgroups.py | 41 +++++++-- Lib/test/test_asyncio/test_futures.py | 22 +++++ Lib/test/test_asyncio/test_taskgroups.py | 92 ++++++++++++++++++- ...-10-04-08-46-00.gh-issue-124958.rea9-x.rst | 1 + 5 files changed, 147 insertions(+), 15 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-10-04-08-46-00.gh-issue-124958.rea9-x.rst diff --git a/Lib/asyncio/futures.py b/Lib/asyncio/futures.py index fd486f02c67c8e..0c530bbdbcf2d8 100644 --- a/Lib/asyncio/futures.py +++ b/Lib/asyncio/futures.py @@ -194,8 +194,7 @@ def result(self): the future is done and has an exception set, this exception is raised. """ if self._state == _CANCELLED: - exc = self._make_cancelled_error() - raise exc + raise self._make_cancelled_error() if self._state != _FINISHED: raise exceptions.InvalidStateError('Result is not ready.') self.__log_traceback = False @@ -212,8 +211,7 @@ def exception(self): InvalidStateError. """ if self._state == _CANCELLED: - exc = self._make_cancelled_error() - raise exc + raise self._make_cancelled_error() if self._state != _FINISHED: raise exceptions.InvalidStateError('Exception is not set.') self.__log_traceback = False diff --git a/Lib/asyncio/taskgroups.py b/Lib/asyncio/taskgroups.py index d264e51f1fd4e6..aada3ffa8e0f29 100644 --- a/Lib/asyncio/taskgroups.py +++ b/Lib/asyncio/taskgroups.py @@ -66,6 +66,20 @@ async def __aenter__(self): return self async def __aexit__(self, et, exc, tb): + tb = None + try: + return await self._aexit(et, exc) + finally: + # Exceptions are heavy objects that can have object + # cycles (bad for GC); let's not keep a reference to + # a bunch of them. It would be nicer to use a try/finally + # in __aexit__ directly but that introduced some diff noise + self._parent_task = None + self._errors = None + self._base_error = None + exc = None + + async def _aexit(self, et, exc): self._exiting = True if (exc is not None and @@ -126,25 +140,34 @@ async def __aexit__(self, et, exc, tb): assert not self._tasks if self._base_error is not None: - raise self._base_error + try: + raise self._base_error + finally: + exc = None # Propagate CancelledError if there is one, except if there # are other errors -- those have priority. - if propagate_cancellation_error and not self._errors: - raise propagate_cancellation_error + try: + if propagate_cancellation_error and not self._errors: + try: + raise propagate_cancellation_error + finally: + exc = None + finally: + propagate_cancellation_error = None if et is not None and et is not exceptions.CancelledError: self._errors.append(exc) if self._errors: - # Exceptions are heavy objects that can have object - # cycles (bad for GC); let's not keep a reference to - # a bunch of them. try: - me = BaseExceptionGroup('unhandled errors in a TaskGroup', self._errors) - raise me from None + raise BaseExceptionGroup( + 'unhandled errors in a TaskGroup', + self._errors, + ) from None finally: - self._errors = None + exc = None + def create_task(self, coro, *, name=None, context=None): """Create a new task in this group and return it. diff --git a/Lib/test/test_asyncio/test_futures.py b/Lib/test/test_asyncio/test_futures.py index 47daa0e9f410a8..050d33f4fab3ed 100644 --- a/Lib/test/test_asyncio/test_futures.py +++ b/Lib/test/test_asyncio/test_futures.py @@ -640,6 +640,28 @@ def __del__(self): fut = self._new_future(loop=self.loop) fut.set_result(Evil()) + def test_future_cancelled_result_refcycles(self): + f = self._new_future(loop=self.loop) + f.cancel() + exc = None + try: + f.result() + except asyncio.CancelledError as e: + exc = e + self.assertIsNotNone(exc) + self.assertListEqual(gc.get_referrers(exc), []) + + def test_future_cancelled_exception_refcycles(self): + f = self._new_future(loop=self.loop) + f.cancel() + exc = None + try: + f.exception() + except asyncio.CancelledError as e: + exc = e + self.assertIsNotNone(exc) + self.assertListEqual(gc.get_referrers(exc), []) + @unittest.skipUnless(hasattr(futures, '_CFuture'), 'requires the C _asyncio module') diff --git a/Lib/test/test_asyncio/test_taskgroups.py b/Lib/test/test_asyncio/test_taskgroups.py index 7a18362b54e469..236bfaaccf88fa 100644 --- a/Lib/test/test_asyncio/test_taskgroups.py +++ b/Lib/test/test_asyncio/test_taskgroups.py @@ -1,7 +1,7 @@ # Adapted with permission from the EdgeDB project; # license: PSFL. - +import gc import asyncio import contextvars import contextlib @@ -10,7 +10,6 @@ from test.test_asyncio.utils import await_without_task - # To prevent a warning "test altered the execution environment" def tearDownModule(): asyncio.set_event_loop_policy(None) @@ -824,6 +823,95 @@ async def test_taskgroup_without_parent_task(self): # We still have to await coro to avoid a warning await coro + async def test_exception_refcycles_direct(self): + """Test that TaskGroup doesn't keep a reference to the raised ExceptionGroup""" + tg = asyncio.TaskGroup() + exc = None + + class _Done(Exception): + pass + + try: + async with tg: + raise _Done + except ExceptionGroup as e: + exc = e + + self.assertIsNotNone(exc) + self.assertListEqual(gc.get_referrers(exc), []) + + + async def test_exception_refcycles_errors(self): + """Test that TaskGroup deletes self._errors, and __aexit__ args""" + tg = asyncio.TaskGroup() + exc = None + + class _Done(Exception): + pass + + try: + async with tg: + raise _Done + except* _Done as excs: + exc = excs.exceptions[0] + + self.assertIsInstance(exc, _Done) + self.assertListEqual(gc.get_referrers(exc), []) + + + async def test_exception_refcycles_parent_task(self): + """Test that TaskGroup deletes self._parent_task""" + tg = asyncio.TaskGroup() + exc = None + + class _Done(Exception): + pass + + async def coro_fn(): + async with tg: + raise _Done + + try: + async with asyncio.TaskGroup() as tg2: + tg2.create_task(coro_fn()) + except* _Done as excs: + exc = excs.exceptions[0].exceptions[0] + + self.assertIsInstance(exc, _Done) + self.assertListEqual(gc.get_referrers(exc), []) + + async def test_exception_refcycles_propagate_cancellation_error(self): + """Test that TaskGroup deletes propagate_cancellation_error""" + tg = asyncio.TaskGroup() + exc = None + + try: + async with asyncio.timeout(-1): + async with tg: + await asyncio.sleep(0) + except TimeoutError as e: + exc = e.__cause__ + + self.assertIsInstance(exc, asyncio.CancelledError) + self.assertListEqual(gc.get_referrers(exc), []) + + async def test_exception_refcycles_base_error(self): + """Test that TaskGroup deletes self._base_error""" + class MyKeyboardInterrupt(KeyboardInterrupt): + pass + + tg = asyncio.TaskGroup() + exc = None + + try: + async with tg: + raise MyKeyboardInterrupt + except MyKeyboardInterrupt as e: + exc = e + + self.assertIsNotNone(exc) + self.assertListEqual(gc.get_referrers(exc), []) + if __name__ == "__main__": unittest.main() diff --git a/Misc/NEWS.d/next/Library/2024-10-04-08-46-00.gh-issue-124958.rea9-x.rst b/Misc/NEWS.d/next/Library/2024-10-04-08-46-00.gh-issue-124958.rea9-x.rst new file mode 100644 index 00000000000000..534d5bb8c898da --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-10-04-08-46-00.gh-issue-124958.rea9-x.rst @@ -0,0 +1 @@ +Fix refcycles in exceptions raised from :class:`asyncio.TaskGroup` and the python implementation of :class:`asyncio.Future` From fb1b92b3279ba5741bc9dc9809b10e995c3dab4d Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Thu, 17 Oct 2024 14:42:55 +0200 Subject: [PATCH 074/269] [3.12] gh-95836: Add custom type converter examples to argparse tutorial (GH-125376) (GH-125642) (cherry picked from commit dbcc5ac4709dfd8dfaf323d51f135f2218d14068) Co-authored-by: Savannah Ostrowski --- Doc/howto/argparse.rst | 47 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/Doc/howto/argparse.rst b/Doc/howto/argparse.rst index ae5bab90bf8131..ac2a0465b2283b 100644 --- a/Doc/howto/argparse.rst +++ b/Doc/howto/argparse.rst @@ -841,6 +841,53 @@ translated messages. To translate your own strings in the :mod:`argparse` output, use :mod:`gettext`. +Custom type converters +====================== + +The :mod:`argparse` module allows you to specify custom type converters for +your command-line arguments. This allows you to modify user input before it's +stored in the :class:`argparse.Namespace`. This can be useful when you need to +pre-process the input before it is used in your program. + +When using a custom type converter, you can use any callable that takes a +single string argument (the argument value) and returns the converted value. +However, if you need to handle more complex scenarios, you can use a custom +action class with the **action** parameter instead. + +For example, let's say you want to handle arguments with different prefixes and +process them accordingly:: + + import argparse + + parser = argparse.ArgumentParser(prefix_chars='-+') + + parser.add_argument('-a', metavar='', action='append', + type=lambda x: ('-', x)) + parser.add_argument('+a', metavar='', action='append', + type=lambda x: ('+', x)) + + args = parser.parse_args() + print(args) + +Output: + +.. code-block:: shell-session + + $ python prog.py -a value1 +a value2 + Namespace(a=[('-', 'value1'), ('+', 'value2')]) + +In this example, we: + +* Created a parser with custom prefix characters using the ``prefix_chars`` + parameter. + +* Defined two arguments, ``-a`` and ``+a``, which used the ``type`` parameter to + create custom type converters to store the value in a tuple with the prefix. + +Without the custom type converters, the arguments would have treated the ``-a`` +and ``+a`` as the same argument, which would have been undesirable. By using custom +type converters, we were able to differentiate between the two arguments. + Conclusion ========== From 8f72f33d69dbf74728c713356ec4d160fd7d79f5 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Thu, 17 Oct 2024 18:01:55 +0200 Subject: [PATCH 075/269] [3.12] gh-125625: Check for `py -3.13` in PCbuild/find_python.bat (GH-125649) (cherry picked from commit 0cb20f2e7e867d5c34fc17dd5b8e51e8b0020bb3) Co-authored-by: Wulian --- PCbuild/find_python.bat | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PCbuild/find_python.bat b/PCbuild/find_python.bat index d8c7cec15ef5fe..0af367a3efafad 100644 --- a/PCbuild/find_python.bat +++ b/PCbuild/find_python.bat @@ -44,7 +44,7 @@ @rem If py.exe finds a recent enough version, use that one @rem It is fine to add new versions to this list when they have released, @rem but we do not use prerelease builds here. -@for %%p in (3.12 3.11 3.10) do @py -%%p -EV >nul 2>&1 && (set PYTHON=py -%%p) && (set _Py_Python_Source=found %%p with py.exe) && goto :found +@for %%p in (3.13 3.12 3.11 3.10) do @py -%%p -EV >nul 2>&1 && (set PYTHON=py -%%p) && (set _Py_Python_Source=found %%p with py.exe) && goto :found @if NOT exist "%_Py_EXTERNALS_DIR%" mkdir "%_Py_EXTERNALS_DIR%" @set _Py_NUGET=%NUGET% From dc0a1763218f251d286912a6736b27c9ea887668 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Thu, 17 Oct 2024 18:59:01 +0200 Subject: [PATCH 076/269] [3.12] gh-113570: reprlib.repr does not use builtin __repr__ for reshadowed builtins (GH-113577) (GH-125655) (cherry picked from commit 04d6dd23e2d8a3132772cf7ce928676e26313585) Co-authored-by: George Pittock <66332098+georgepittock@users.noreply.github.com> --- Lib/reprlib.py | 31 ++++++++++--- Lib/test/test_reprlib.py | 44 +++++++++++++++++++ ...-12-30-00-21-45.gh-issue-113570._XQgsW.rst | 1 + 3 files changed, 71 insertions(+), 5 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2023-12-30-00-21-45.gh-issue-113570._XQgsW.rst diff --git a/Lib/reprlib.py b/Lib/reprlib.py index a7b37630a4edb9..85c1b94a0ea6a9 100644 --- a/Lib/reprlib.py +++ b/Lib/reprlib.py @@ -35,6 +35,17 @@ def wrapper(self): return decorating_function class Repr: + _lookup = { + 'tuple': 'builtins', + 'list': 'builtins', + 'array': 'array', + 'set': 'builtins', + 'frozenset': 'builtins', + 'deque': 'collections', + 'dict': 'builtins', + 'str': 'builtins', + 'int': 'builtins' + } def __init__( self, *, maxlevel=6, maxtuple=6, maxlist=6, maxarray=5, maxdict=4, @@ -59,14 +70,24 @@ def repr(self, x): return self.repr1(x, self.maxlevel) def repr1(self, x, level): - typename = type(x).__name__ + cls = type(x) + typename = cls.__name__ + if ' ' in typename: parts = typename.split() typename = '_'.join(parts) - if hasattr(self, 'repr_' + typename): - return getattr(self, 'repr_' + typename)(x, level) - else: - return self.repr_instance(x, level) + + method = getattr(self, 'repr_' + typename, None) + if method: + # not defined in this class + if typename not in self._lookup: + return method(x, level) + module = getattr(cls, '__module__', None) + # defined in this class and is the module intended + if module == self._lookup[typename]: + return method(x, level) + + return self.repr_instance(x, level) def _join(self, pieces, level): if self.indent is None: diff --git a/Lib/test/test_reprlib.py b/Lib/test/test_reprlib.py index 4a896db2002047..e2a67ef344d920 100644 --- a/Lib/test/test_reprlib.py +++ b/Lib/test/test_reprlib.py @@ -580,6 +580,50 @@ def test_invalid_indent(self): with self.assertRaisesRegex(expected_error, expected_msg): r.repr(test_object) + def test_shadowed_stdlib_array(self): + # Issue #113570: repr() should not be fooled by an array + class array: + def __repr__(self): + return "not array.array" + + self.assertEqual(r(array()), "not array.array") + + def test_shadowed_builtin(self): + # Issue #113570: repr() should not be fooled + # by a shadowed builtin function + class list: + def __repr__(self): + return "not builtins.list" + + self.assertEqual(r(list()), "not builtins.list") + + def test_custom_repr(self): + class MyRepr(Repr): + + def repr_TextIOWrapper(self, obj, level): + if obj.name in {'', '', ''}: + return obj.name + return repr(obj) + + aRepr = MyRepr() + self.assertEqual(aRepr.repr(sys.stdin), "") + + def test_custom_repr_class_with_spaces(self): + class TypeWithSpaces: + pass + + t = TypeWithSpaces() + type(t).__name__ = "type with spaces" + self.assertEqual(type(t).__name__, "type with spaces") + + class MyRepr(Repr): + def repr_type_with_spaces(self, obj, level): + return "Type With Spaces" + + + aRepr = MyRepr() + self.assertEqual(aRepr.repr(t), "Type With Spaces") + def write_file(path, text): with open(path, 'w', encoding='ASCII') as fp: fp.write(text) diff --git a/Misc/NEWS.d/next/Core and Builtins/2023-12-30-00-21-45.gh-issue-113570._XQgsW.rst b/Misc/NEWS.d/next/Core and Builtins/2023-12-30-00-21-45.gh-issue-113570._XQgsW.rst new file mode 100644 index 00000000000000..6e0f0afe05369b --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2023-12-30-00-21-45.gh-issue-113570._XQgsW.rst @@ -0,0 +1 @@ +Fixed a bug in ``reprlib.repr`` where it incorrectly called the repr method on shadowed Python built-in types. From 5e62d9bd54b5657b7e50da9966e4cbc6ed3b520b Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Thu, 17 Oct 2024 19:09:05 +0200 Subject: [PATCH 077/269] [3.12] [3.13] gh-123370: Fix the canvas not clearing after running turtledemo.clock (gh-123457) (GH-125653) (#125656) Rewriting the day and date every tick somehow prevented them from being removed either by clicking STOP or loading another example. The solution is to rewrite them only when they change. (cherry picked from commit c124577ebe915a00de4033c0f7fa7c47621d79e0) (cherry picked from commit 30d7e9e721e8201bc7fb23f08a7d88d51eed827d) Co-authored-by: Terry Jan Reedy Co-authored-by: Wulian --- Lib/turtledemo/clock.py | 33 +++++++++++-------- ...-08-28-19-27-35.gh-issue-123370.SPZ9Ux.rst | 1 + 2 files changed, 21 insertions(+), 13 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-08-28-19-27-35.gh-issue-123370.SPZ9Ux.rst diff --git a/Lib/turtledemo/clock.py b/Lib/turtledemo/clock.py index 9f8585bd11e053..318f126006333f 100755 --- a/Lib/turtledemo/clock.py +++ b/Lib/turtledemo/clock.py @@ -1,8 +1,7 @@ #!/usr/bin/env python3 -# -*- coding: cp1252 -*- """ turtle-example-suite: - tdemo_clock.py + turtledemo/clock.py Enhanced clock-program, showing date and time @@ -13,6 +12,9 @@ from turtle import * from datetime import datetime +dtfont = "TkFixedFont", 14, "bold" +current_day = None + def jump(distanz, winkel=0): penup() right(winkel) @@ -53,11 +55,23 @@ def clockface(radius): jump(-radius) rt(6) +def display_date_time(): + global current_day + writer.clear() + now = datetime.now() + current_day = now.day + writer.home() + writer.forward(distance=65) + writer.write(wochentag(now), align="center", font=dtfont) + writer.back(distance=150) + writer.write(datum(now), align="center", font=dtfont) + writer.forward(distance=85) + def setup(): global second_hand, minute_hand, hour_hand, writer mode("logo") make_hand_shape("second_hand", 125, 25) - make_hand_shape("minute_hand", 130, 25) + make_hand_shape("minute_hand", 115, 25) make_hand_shape("hour_hand", 90, 25) clockface(160) second_hand = Turtle() @@ -75,10 +89,10 @@ def setup(): hand.speed(0) ht() writer = Turtle() - #writer.mode("logo") writer.ht() writer.pu() writer.bk(85) + display_date_time() def wochentag(t): wochentag = ["Monday", "Tuesday", "Wednesday", @@ -100,18 +114,11 @@ def tick(): stunde = t.hour + minute/60.0 try: tracer(False) # Terminator can occur here - writer.clear() - writer.home() - writer.forward(65) - writer.write(wochentag(t), - align="center", font=("Courier", 14, "bold")) - writer.back(150) - writer.write(datum(t), - align="center", font=("Courier", 14, "bold")) - writer.forward(85) second_hand.setheading(6*sekunde) # or here minute_hand.setheading(6*minute) hour_hand.setheading(30*stunde) + if t.day != current_day: + display_date_time() tracer(True) ontimer(tick, 100) except Terminator: diff --git a/Misc/NEWS.d/next/Library/2024-08-28-19-27-35.gh-issue-123370.SPZ9Ux.rst b/Misc/NEWS.d/next/Library/2024-08-28-19-27-35.gh-issue-123370.SPZ9Ux.rst new file mode 100644 index 00000000000000..1fd5cc54eaf3e7 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-08-28-19-27-35.gh-issue-123370.SPZ9Ux.rst @@ -0,0 +1 @@ +Fix the canvas not clearing after running turtledemo clock. From 232b303e4ca47892f544294bf42e31dc34f0ec72 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Thu, 17 Oct 2024 22:04:48 +0200 Subject: [PATCH 078/269] [3.12] gh-52551: Fix encoding issues in strftime() (GH-125193) (GH-125657) (GH-125661) Fix time.strftime(), the strftime() method and formatting of the datetime classes datetime, date and time. * Characters not encodable in the current locale are now acceptable in the format string. * Surrogate pairs and sequence of surrogatescape-encoded bytes are no longer recombinated. * Embedded null character no longer terminates the format string. This fixes also gh-78662 and gh-124531. (cherry picked from commit 08ccbb9b3f5e20a7a0c4cf9995e172b59fb6067b) (cherry picked from commit ad3eac1963a5f195ef9b2c1dbb5e44fa3cce4c72) Co-authored-by: Serhiy Storchaka --- Lib/test/datetimetester.py | 63 ++++- Lib/test/test_time.py | 29 ++- ...4-10-09-17-07-33.gh-issue-52551.PBakSY.rst | 8 + Modules/_datetimemodule.c | 169 +++++-------- Modules/timemodule.c | 235 ++++++++++-------- 5 files changed, 293 insertions(+), 211 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-10-09-17-07-33.gh-issue-52551.PBakSY.rst diff --git a/Lib/test/datetimetester.py b/Lib/test/datetimetester.py index 0528e0701fa982..903a43aad81994 100644 --- a/Lib/test/datetimetester.py +++ b/Lib/test/datetimetester.py @@ -2816,11 +2816,32 @@ def test_more_strftime(self): self.assertEqual(t.strftime("%z"), "-0200" + z) self.assertEqual(t.strftime("%:z"), "-02:00:" + z) - # bpo-34482: Check that surrogates don't cause a crash. - try: - t.strftime('%y\ud800%m %H\ud800%M') - except UnicodeEncodeError: - pass + def test_strftime_special(self): + t = self.theclass(2004, 12, 31, 6, 22, 33, 47) + s1 = t.strftime('%c') + s2 = t.strftime('%B') + # gh-52551, gh-78662: Unicode strings should pass through strftime, + # independently from locale. + self.assertEqual(t.strftime('\U0001f40d'), '\U0001f40d') + self.assertEqual(t.strftime('\U0001f4bb%c\U0001f40d%B'), f'\U0001f4bb{s1}\U0001f40d{s2}') + self.assertEqual(t.strftime('%c\U0001f4bb%B\U0001f40d'), f'{s1}\U0001f4bb{s2}\U0001f40d') + # Lone surrogates should pass through. + self.assertEqual(t.strftime('\ud83d'), '\ud83d') + self.assertEqual(t.strftime('\udc0d'), '\udc0d') + self.assertEqual(t.strftime('\ud83d%c\udc0d%B'), f'\ud83d{s1}\udc0d{s2}') + self.assertEqual(t.strftime('%c\ud83d%B\udc0d'), f'{s1}\ud83d{s2}\udc0d') + self.assertEqual(t.strftime('%c\udc0d%B\ud83d'), f'{s1}\udc0d{s2}\ud83d') + # Surrogate pairs should not recombine. + self.assertEqual(t.strftime('\ud83d\udc0d'), '\ud83d\udc0d') + self.assertEqual(t.strftime('%c\ud83d\udc0d%B'), f'{s1}\ud83d\udc0d{s2}') + # Surrogate-escaped bytes should not recombine. + self.assertEqual(t.strftime('\udcf0\udc9f\udc90\udc8d'), '\udcf0\udc9f\udc90\udc8d') + self.assertEqual(t.strftime('%c\udcf0\udc9f\udc90\udc8d%B'), f'{s1}\udcf0\udc9f\udc90\udc8d{s2}') + # gh-124531: The null character should not terminate the format string. + self.assertEqual(t.strftime('\0'), '\0') + self.assertEqual(t.strftime('\0'*1000), '\0'*1000) + self.assertEqual(t.strftime('\0%c\0%B'), f'\0{s1}\0{s2}') + self.assertEqual(t.strftime('%c\0%B\0'), f'{s1}\0{s2}\0') def test_extract(self): dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234) @@ -3573,6 +3594,33 @@ def test_strftime(self): # gh-85432: The parameter was named "fmt" in the pure-Python impl. t.strftime(format="%f") + def test_strftime_special(self): + t = self.theclass(1, 2, 3, 4) + s1 = t.strftime('%I%p%Z') + s2 = t.strftime('%X') + # gh-52551, gh-78662: Unicode strings should pass through strftime, + # independently from locale. + self.assertEqual(t.strftime('\U0001f40d'), '\U0001f40d') + self.assertEqual(t.strftime('\U0001f4bb%I%p%Z\U0001f40d%X'), f'\U0001f4bb{s1}\U0001f40d{s2}') + self.assertEqual(t.strftime('%I%p%Z\U0001f4bb%X\U0001f40d'), f'{s1}\U0001f4bb{s2}\U0001f40d') + # Lone surrogates should pass through. + self.assertEqual(t.strftime('\ud83d'), '\ud83d') + self.assertEqual(t.strftime('\udc0d'), '\udc0d') + self.assertEqual(t.strftime('\ud83d%I%p%Z\udc0d%X'), f'\ud83d{s1}\udc0d{s2}') + self.assertEqual(t.strftime('%I%p%Z\ud83d%X\udc0d'), f'{s1}\ud83d{s2}\udc0d') + self.assertEqual(t.strftime('%I%p%Z\udc0d%X\ud83d'), f'{s1}\udc0d{s2}\ud83d') + # Surrogate pairs should not recombine. + self.assertEqual(t.strftime('\ud83d\udc0d'), '\ud83d\udc0d') + self.assertEqual(t.strftime('%I%p%Z\ud83d\udc0d%X'), f'{s1}\ud83d\udc0d{s2}') + # Surrogate-escaped bytes should not recombine. + self.assertEqual(t.strftime('\udcf0\udc9f\udc90\udc8d'), '\udcf0\udc9f\udc90\udc8d') + self.assertEqual(t.strftime('%I%p%Z\udcf0\udc9f\udc90\udc8d%X'), f'{s1}\udcf0\udc9f\udc90\udc8d{s2}') + # gh-124531: The null character should not terminate the format string. + self.assertEqual(t.strftime('\0'), '\0') + self.assertEqual(t.strftime('\0'*1000), '\0'*1000) + self.assertEqual(t.strftime('\0%I%p%Z\0%X'), f'\0{s1}\0{s2}') + self.assertEqual(t.strftime('%I%p%Z\0%X\0'), f'{s1}\0{s2}\0') + def test_format(self): t = self.theclass(1, 2, 3, 4) self.assertEqual(t.__format__(''), str(t)) @@ -4002,9 +4050,8 @@ def tzname(self, dt): return self.tz self.assertRaises(TypeError, t.strftime, "%Z") # Issue #6697: - if '_Fast' in self.__class__.__name__: - Badtzname.tz = '\ud800' - self.assertRaises(ValueError, t.strftime, "%Z") + Badtzname.tz = '\ud800' + self.assertEqual(t.strftime("%Z"), '\ud800') def test_hash_edge_cases(self): # Offsets that overflow a basic time. diff --git a/Lib/test/test_time.py b/Lib/test/test_time.py index ca40415f0cdd76..b020787dacac1c 100644 --- a/Lib/test/test_time.py +++ b/Lib/test/test_time.py @@ -177,8 +177,33 @@ def test_strftime(self): self.fail('conversion specifier: %r failed.' % format) self.assertRaises(TypeError, time.strftime, b'%S', tt) - # embedded null character - self.assertRaises(ValueError, time.strftime, '%S\0', tt) + + def test_strftime_special(self): + tt = time.gmtime(self.t) + s1 = time.strftime('%c', tt) + s2 = time.strftime('%B', tt) + # gh-52551, gh-78662: Unicode strings should pass through strftime, + # independently from locale. + self.assertEqual(time.strftime('\U0001f40d', tt), '\U0001f40d') + self.assertEqual(time.strftime('\U0001f4bb%c\U0001f40d%B', tt), f'\U0001f4bb{s1}\U0001f40d{s2}') + self.assertEqual(time.strftime('%c\U0001f4bb%B\U0001f40d', tt), f'{s1}\U0001f4bb{s2}\U0001f40d') + # Lone surrogates should pass through. + self.assertEqual(time.strftime('\ud83d', tt), '\ud83d') + self.assertEqual(time.strftime('\udc0d', tt), '\udc0d') + self.assertEqual(time.strftime('\ud83d%c\udc0d%B', tt), f'\ud83d{s1}\udc0d{s2}') + self.assertEqual(time.strftime('%c\ud83d%B\udc0d', tt), f'{s1}\ud83d{s2}\udc0d') + self.assertEqual(time.strftime('%c\udc0d%B\ud83d', tt), f'{s1}\udc0d{s2}\ud83d') + # Surrogate pairs should not recombine. + self.assertEqual(time.strftime('\ud83d\udc0d', tt), '\ud83d\udc0d') + self.assertEqual(time.strftime('%c\ud83d\udc0d%B', tt), f'{s1}\ud83d\udc0d{s2}') + # Surrogate-escaped bytes should not recombine. + self.assertEqual(time.strftime('\udcf0\udc9f\udc90\udc8d', tt), '\udcf0\udc9f\udc90\udc8d') + self.assertEqual(time.strftime('%c\udcf0\udc9f\udc90\udc8d%B', tt), f'{s1}\udcf0\udc9f\udc90\udc8d{s2}') + # gh-124531: The null character should not terminate the format string. + self.assertEqual(time.strftime('\0', tt), '\0') + self.assertEqual(time.strftime('\0'*1000, tt), '\0'*1000) + self.assertEqual(time.strftime('\0%c\0%B', tt), f'\0{s1}\0{s2}') + self.assertEqual(time.strftime('%c\0%B\0', tt), f'{s1}\0{s2}\0') def _bounds_checking(self, func): # Make sure that strftime() checks the bounds of the various parts diff --git a/Misc/NEWS.d/next/Library/2024-10-09-17-07-33.gh-issue-52551.PBakSY.rst b/Misc/NEWS.d/next/Library/2024-10-09-17-07-33.gh-issue-52551.PBakSY.rst new file mode 100644 index 00000000000000..edc9ac5bb23117 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-10-09-17-07-33.gh-issue-52551.PBakSY.rst @@ -0,0 +1,8 @@ +Fix encoding issues in :func:`time.strftime`, the +:meth:`~datetime.datetime.strftime` method of the :mod:`datetime` classes +:class:`~datetime.datetime`, :class:`~datetime.date` and +:class:`~datetime.time` and formatting of these classes. Characters not +encodable in the current locale are now acceptable in the format string. +Surrogate pairs and sequence of surrogatescape-encoded bytes are no longer +recombinated. Embedded null character no longer terminates the format +string. diff --git a/Modules/_datetimemodule.c b/Modules/_datetimemodule.c index 5a062b9c8c0e2c..8535811a61b9e6 100644 --- a/Modules/_datetimemodule.c +++ b/Modules/_datetimemodule.c @@ -1501,7 +1501,7 @@ make_somezreplacement(PyObject *object, char *sep, PyObject *tzinfoarg) PyObject *tzinfo = get_tzinfo_member(object); if (tzinfo == Py_None || tzinfo == NULL) { - return PyBytes_FromStringAndSize(NULL, 0); + return PyUnicode_FromStringAndSize(NULL, 0); } assert(tzinfoarg != NULL); @@ -1512,7 +1512,7 @@ make_somezreplacement(PyObject *object, char *sep, PyObject *tzinfoarg) tzinfoarg) < 0) return NULL; - return PyBytes_FromStringAndSize(buf, strlen(buf)); + return PyUnicode_FromString(buf); } static PyObject * @@ -1569,7 +1569,7 @@ make_freplacement(PyObject *object) else sprintf(freplacement, "%06d", 0); - return PyBytes_FromStringAndSize(freplacement, strlen(freplacement)); + return PyUnicode_FromString(freplacement); } /* I sure don't want to reproduce the strftime code from the time module, @@ -1590,79 +1590,60 @@ wrap_strftime(PyObject *object, PyObject *format, PyObject *timetuple, PyObject *Zreplacement = NULL; /* py string, replacement for %Z */ PyObject *freplacement = NULL; /* py string, replacement for %f */ - const char *pin; /* pointer to next char in input format */ - Py_ssize_t flen; /* length of input format */ - char ch; /* next char in input format */ - - PyObject *newfmt = NULL; /* py string, the output format */ - char *pnew; /* pointer to available byte in output format */ - size_t totalnew; /* number bytes total in output format buffer, - exclusive of trailing \0 */ - size_t usednew; /* number bytes used so far in output format buffer */ - - const char *ptoappend; /* ptr to string to append to output buffer */ - Py_ssize_t ntoappend; /* # of bytes to append to output buffer */ - assert(object && format && timetuple); assert(PyUnicode_Check(format)); - /* Convert the input format to a C string and size */ - pin = PyUnicode_AsUTF8AndSize(format, &flen); - if (!pin) + + PyObject *strftime = _PyImport_GetModuleAttrString("time", "strftime"); + if (strftime == NULL) { return NULL; + } /* Scan the input format, looking for %z/%Z/%f escapes, building * a new format. Since computing the replacements for those codes * is expensive, don't unless they're actually used. */ - if (flen > INT_MAX - 1) { - PyErr_NoMemory(); - goto Done; - } - totalnew = flen + 1; /* realistic if no %z/%Z */ - newfmt = PyBytes_FromStringAndSize(NULL, totalnew); - if (newfmt == NULL) goto Done; - pnew = PyBytes_AsString(newfmt); - usednew = 0; - - while ((ch = *pin++) != '\0') { - if (ch != '%') { - ptoappend = pin - 1; - ntoappend = 1; + _PyUnicodeWriter writer; + _PyUnicodeWriter_Init(&writer); + writer.overallocate = 1; + + Py_ssize_t flen = PyUnicode_GET_LENGTH(format); + Py_ssize_t i = 0; + Py_ssize_t start = 0; + Py_ssize_t end = 0; + while (i != flen) { + i = PyUnicode_FindChar(format, '%', i, flen, 1); + if (i < 0) { + assert(!PyErr_Occurred()); + break; } - else if ((ch = *pin++) == '\0') { - /* Null byte follows %, copy only '%'. - * - * Back the pin up one char so that we catch the null check - * the next time through the loop.*/ - pin--; - ptoappend = pin - 1; - ntoappend = 1; + end = i; + i++; + if (i == flen) { + break; } + Py_UCS4 ch = PyUnicode_READ_CHAR(format, i); + i++; /* A % has been seen and ch is the character after it. */ - else if (ch == 'z') { + PyObject *replacement = NULL; + if (ch == 'z') { /* %z -> +HHMM */ if (zreplacement == NULL) { zreplacement = make_somezreplacement(object, "", tzinfoarg); if (zreplacement == NULL) - goto Done; + goto Error; } - assert(zreplacement != NULL); - assert(PyBytes_Check(zreplacement)); - ptoappend = PyBytes_AS_STRING(zreplacement); - ntoappend = PyBytes_GET_SIZE(zreplacement); + replacement = zreplacement; } - else if (ch == ':' && *pin == 'z' && pin++) { + else if (ch == ':' && i < flen && PyUnicode_READ_CHAR(format, i) == 'z') { /* %:z -> +HH:MM */ + i++; if (colonzreplacement == NULL) { colonzreplacement = make_somezreplacement(object, ":", tzinfoarg); if (colonzreplacement == NULL) - goto Done; + goto Error; } - assert(colonzreplacement != NULL); - assert(PyBytes_Check(colonzreplacement)); - ptoappend = PyBytes_AS_STRING(colonzreplacement); - ntoappend = PyBytes_GET_SIZE(colonzreplacement); + replacement = colonzreplacement; } else if (ch == 'Z') { /* format tzname */ @@ -1670,79 +1651,63 @@ wrap_strftime(PyObject *object, PyObject *format, PyObject *timetuple, Zreplacement = make_Zreplacement(object, tzinfoarg); if (Zreplacement == NULL) - goto Done; + goto Error; } - assert(Zreplacement != NULL); - assert(PyUnicode_Check(Zreplacement)); - ptoappend = PyUnicode_AsUTF8AndSize(Zreplacement, - &ntoappend); - if (ptoappend == NULL) - goto Done; + replacement = Zreplacement; } else if (ch == 'f') { /* format microseconds */ if (freplacement == NULL) { freplacement = make_freplacement(object); if (freplacement == NULL) - goto Done; + goto Error; } - assert(freplacement != NULL); - assert(PyBytes_Check(freplacement)); - ptoappend = PyBytes_AS_STRING(freplacement); - ntoappend = PyBytes_GET_SIZE(freplacement); + replacement = freplacement; } else { /* percent followed by something else */ - ptoappend = pin - 2; - ntoappend = 2; - } - - /* Append the ntoappend chars starting at ptoappend to - * the new format. - */ - if (ntoappend == 0) continue; - assert(ptoappend != NULL); - assert(ntoappend > 0); - while (usednew + ntoappend > totalnew) { - if (totalnew > (PY_SSIZE_T_MAX >> 1)) { /* overflow */ - PyErr_NoMemory(); - goto Done; - } - totalnew <<= 1; - if (_PyBytes_Resize(&newfmt, totalnew) < 0) - goto Done; - pnew = PyBytes_AsString(newfmt) + usednew; } - memcpy(pnew, ptoappend, ntoappend); - pnew += ntoappend; - usednew += ntoappend; - assert(usednew <= totalnew); + assert(replacement != NULL); + assert(PyUnicode_Check(replacement)); + if (_PyUnicodeWriter_WriteSubstring(&writer, format, start, end) < 0) { + goto Error; + } + start = i; + if (_PyUnicodeWriter_WriteStr(&writer, replacement) < 0) { + goto Error; + } } /* end while() */ - if (_PyBytes_Resize(&newfmt, usednew) < 0) - goto Done; - { - PyObject *format; - PyObject *strftime = _PyImport_GetModuleAttrString("time", "strftime"); - - if (strftime == NULL) + PyObject *newformat; + if (start == 0) { + _PyUnicodeWriter_Dealloc(&writer); + newformat = Py_NewRef(format); + } + else { + if (_PyUnicodeWriter_WriteSubstring(&writer, format, start, flen) < 0) { + goto Error; + } + newformat = _PyUnicodeWriter_Finish(&writer); + if (newformat == NULL) { goto Done; - format = PyUnicode_FromString(PyBytes_AS_STRING(newfmt)); - if (format != NULL) { - result = PyObject_CallFunctionObjArgs(strftime, - format, timetuple, NULL); - Py_DECREF(format); } - Py_DECREF(strftime); } + result = PyObject_CallFunctionObjArgs(strftime, + newformat, timetuple, NULL); + Py_DECREF(newformat); + Done: Py_XDECREF(freplacement); Py_XDECREF(zreplacement); Py_XDECREF(colonzreplacement); Py_XDECREF(Zreplacement); - Py_XDECREF(newfmt); + Py_XDECREF(strftime); return result; + + Error: + _PyUnicodeWriter_Dealloc(&writer); + goto Done; } /* --------------------------------------------------------------------------- diff --git a/Modules/timemodule.c b/Modules/timemodule.c index b10f8c7c939550..59078263571f7e 100644 --- a/Modules/timemodule.c +++ b/Modules/timemodule.c @@ -795,27 +795,100 @@ the C library strftime function.\n" #endif static PyObject * -time_strftime(PyObject *module, PyObject *args) +time_strftime1(time_char **outbuf, size_t *bufsize, + time_char *format, size_t fmtlen, + struct tm *tm) { - PyObject *tup = NULL; - struct tm buf; - const time_char *fmt; + size_t buflen; +#if defined(MS_WINDOWS) && !defined(HAVE_WCSFTIME) + /* check that the format string contains only valid directives */ + for (const time_char *f = strchr(format, '%'); + f != NULL; + f = strchr(f + 2, '%')) + { + if (f[1] == '#') + ++f; /* not documented by python, */ + if (f[1] == '\0') + break; + if ((f[1] == 'y') && tm->tm_year < 0) { + PyErr_SetString(PyExc_ValueError, + "format %y requires year >= 1900 on Windows"); + return NULL; + } + } +#elif (defined(_AIX) || (defined(__sun) && defined(__SVR4))) && defined(HAVE_WCSFTIME) + for (const time_char *f = wcschr(format, '%'); + f != NULL; + f = wcschr(f + 2, '%')) + { + if (f[1] == L'\0') + break; + /* Issue #19634: On AIX, wcsftime("y", (1899, 1, 1, 0, 0, 0, 0, 0, 0)) + returns "0/" instead of "99" */ + if (f[1] == L'y' && tm->tm_year < 0) { + PyErr_SetString(PyExc_ValueError, + "format %y requires year >= 1900 on AIX"); + return NULL; + } + } +#endif + + /* I hate these functions that presume you know how big the output + * will be ahead of time... + */ + while (1) { + if (*bufsize > PY_SSIZE_T_MAX/sizeof(time_char)) { + PyErr_NoMemory(); + return NULL; + } + *outbuf = (time_char *)PyMem_Realloc(*outbuf, + *bufsize*sizeof(time_char)); + if (*outbuf == NULL) { + PyErr_NoMemory(); + return NULL; + } +#if defined _MSC_VER && _MSC_VER >= 1400 && defined(__STDC_SECURE_LIB__) + errno = 0; +#endif + _Py_BEGIN_SUPPRESS_IPH + buflen = format_time(*outbuf, *bufsize, format, tm); + _Py_END_SUPPRESS_IPH +#if defined _MSC_VER && _MSC_VER >= 1400 && defined(__STDC_SECURE_LIB__) + /* VisualStudio .NET 2005 does this properly */ + if (buflen == 0 && errno == EINVAL) { + PyErr_SetString(PyExc_ValueError, "Invalid format string"); + return NULL; + } +#endif + if (buflen == 0 && *bufsize < 256 * fmtlen) { + *bufsize += *bufsize; + continue; + } + /* If the buffer is 256 times as long as the format, + it's probably not failing for lack of room! + More likely, the format yields an empty result, + e.g. an empty format, or %Z when the timezone + is unknown. */ #ifdef HAVE_WCSFTIME - wchar_t *format; + return PyUnicode_FromWideChar(*outbuf, buflen); #else - PyObject *format; + return PyUnicode_DecodeLocaleAndSize(*outbuf, buflen, "surrogateescape"); #endif + } +} + +static PyObject * +time_strftime(PyObject *module, PyObject *args) +{ + PyObject *tup = NULL; + struct tm buf; PyObject *format_arg; - size_t fmtlen, buflen; - time_char *outbuf = NULL; - size_t i; - PyObject *ret = NULL; + Py_ssize_t format_size; + time_char *format, *outbuf = NULL; + size_t fmtlen, bufsize = 1024; memset((void *) &buf, '\0', sizeof(buf)); - /* Will always expect a unicode string to be passed as format. - Given that there's no str type anymore in py3k this seems safe. - */ if (!PyArg_ParseTuple(args, "U|O:strftime", &format_arg, &tup)) return NULL; @@ -848,101 +921,65 @@ time_strftime(PyObject *module, PyObject *args) else if (buf.tm_isdst > 1) buf.tm_isdst = 1; -#ifdef HAVE_WCSFTIME - format = PyUnicode_AsWideCharString(format_arg, NULL); - if (format == NULL) + format_size = PyUnicode_GET_LENGTH(format_arg); + if ((size_t)format_size > PY_SSIZE_T_MAX/sizeof(time_char) - 1) { + PyErr_NoMemory(); return NULL; - fmt = format; -#else - /* Convert the unicode string to an ascii one */ - format = PyUnicode_EncodeLocale(format_arg, "surrogateescape"); - if (format == NULL) + } + format = PyMem_Malloc((format_size + 1)*sizeof(time_char)); + if (format == NULL) { + PyErr_NoMemory(); return NULL; - fmt = PyBytes_AS_STRING(format); -#endif - -#if defined(MS_WINDOWS) && !defined(HAVE_WCSFTIME) - /* check that the format string contains only valid directives */ - for (outbuf = strchr(fmt, '%'); - outbuf != NULL; - outbuf = strchr(outbuf+2, '%')) - { - if (outbuf[1] == '#') - ++outbuf; /* not documented by python, */ - if (outbuf[1] == '\0') - break; - if ((outbuf[1] == 'y') && buf.tm_year < 0) { - PyErr_SetString(PyExc_ValueError, - "format %y requires year >= 1900 on Windows"); - Py_DECREF(format); - return NULL; - } } -#elif (defined(_AIX) || (defined(__sun) && defined(__SVR4))) && defined(HAVE_WCSFTIME) - for (outbuf = wcschr(fmt, '%'); - outbuf != NULL; - outbuf = wcschr(outbuf+2, '%')) - { - if (outbuf[1] == L'\0') - break; - /* Issue #19634: On AIX, wcsftime("y", (1899, 1, 1, 0, 0, 0, 0, 0, 0)) - returns "0/" instead of "99" */ - if (outbuf[1] == L'y' && buf.tm_year < 0) { - PyErr_SetString(PyExc_ValueError, - "format %y requires year >= 1900 on AIX"); - PyMem_Free(format); - return NULL; + _PyUnicodeWriter writer; + _PyUnicodeWriter_Init(&writer); + writer.overallocate = 1; + Py_ssize_t i = 0; + while (i < format_size) { + fmtlen = 0; + for (; i < format_size; i++) { + Py_UCS4 c = PyUnicode_READ_CHAR(format_arg, i); + if (!c || c > 127) { + break; + } + format[fmtlen++] = (char)c; } - } -#endif - - fmtlen = time_strlen(fmt); - - /* I hate these functions that presume you know how big the output - * will be ahead of time... - */ - for (i = 1024; ; i += i) { - outbuf = (time_char *)PyMem_Malloc(i*sizeof(time_char)); - if (outbuf == NULL) { - PyErr_NoMemory(); - break; + if (fmtlen) { + format[fmtlen] = 0; + PyObject *unicode = time_strftime1(&outbuf, &bufsize, + format, fmtlen, &buf); + if (unicode == NULL) { + goto error; + } + if (_PyUnicodeWriter_WriteStr(&writer, unicode) < 0) { + Py_DECREF(unicode); + goto error; + } + Py_DECREF(unicode); } -#if defined _MSC_VER && _MSC_VER >= 1400 && defined(__STDC_SECURE_LIB__) - errno = 0; -#endif - _Py_BEGIN_SUPPRESS_IPH - buflen = format_time(outbuf, i, fmt, &buf); - _Py_END_SUPPRESS_IPH -#if defined _MSC_VER && _MSC_VER >= 1400 && defined(__STDC_SECURE_LIB__) - /* VisualStudio .NET 2005 does this properly */ - if (buflen == 0 && errno == EINVAL) { - PyErr_SetString(PyExc_ValueError, "Invalid format string"); - PyMem_Free(outbuf); - break; + + Py_ssize_t start = i; + for (; i < format_size; i++) { + Py_UCS4 c = PyUnicode_READ_CHAR(format_arg, i); + if (c == '%') { + break; + } } -#endif - if (buflen > 0 || i >= 256 * fmtlen) { - /* If the buffer is 256 times as long as the format, - it's probably not failing for lack of room! - More likely, the format yields an empty result, - e.g. an empty format, or %Z when the timezone - is unknown. */ -#ifdef HAVE_WCSFTIME - ret = PyUnicode_FromWideChar(outbuf, buflen); -#else - ret = PyUnicode_DecodeLocaleAndSize(outbuf, buflen, "surrogateescape"); -#endif - PyMem_Free(outbuf); - break; + if (start < i) { + if (_PyUnicodeWriter_WriteSubstring(&writer, format_arg, start, i) < 0) { + goto error; + } } - PyMem_Free(outbuf); } -#ifdef HAVE_WCSFTIME + + PyMem_Free(outbuf); PyMem_Free(format); -#else - Py_DECREF(format); -#endif - return ret; + return _PyUnicodeWriter_Finish(&writer); +error: + PyMem_Free(outbuf); + PyMem_Free(format); + _PyUnicodeWriter_Dealloc(&writer); + return NULL; } #undef time_char From aa9faee686d1a3773778c087e39b146271deed24 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Fri, 18 Oct 2024 04:25:00 +0200 Subject: [PATCH 079/269] [3.12] gh-125620: Skip check_resource_tracker_death on NetBSD due to long wait for SIGKILL process termination (GH-125621) (#125673) gh-125620: Skip check_resource_tracker_death on NetBSD due to long wait for SIGKILL process termination (GH-125621) * Skip test_resource_tracker_sigkill on NetBSD (cherry picked from commit a0f5c8e6272a1fd5422892d773923b138e77ae5f) Co-authored-by: Furkan Onder --- Lib/test/_test_multiprocessing.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Lib/test/_test_multiprocessing.py b/Lib/test/_test_multiprocessing.py index aac74ea311f19b..607bfc02b12303 100644 --- a/Lib/test/_test_multiprocessing.py +++ b/Lib/test/_test_multiprocessing.py @@ -5655,6 +5655,8 @@ def test_resource_tracker_sigterm(self): # Catchable signal (ignored by semaphore tracker) self.check_resource_tracker_death(signal.SIGTERM, False) + @unittest.skipIf(sys.platform.startswith("netbsd"), + "gh-125620: Skip on NetBSD due to long wait for SIGKILL process termination.") def test_resource_tracker_sigkill(self): # Uncatchable signal. self.check_resource_tracker_death(signal.SIGKILL, True) From 93933782d999ef58682a8713e5cd3cf92166048e Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Fri, 18 Oct 2024 16:13:31 +0200 Subject: [PATCH 080/269] [3.12] Add tests for time.strftime() with invalid format string (GH-125696) (GH-125701) (cherry picked from commit 2e950e341930ea79549137d4d3771d5edb940e65) Co-authored-by: Serhiy Storchaka --- Lib/test/test_time.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_time.py b/Lib/test/test_time.py index b020787dacac1c..9463adda88db0d 100644 --- a/Lib/test/test_time.py +++ b/Lib/test/test_time.py @@ -14,7 +14,7 @@ except ImportError: _testcapi = None -from test.support import skip_if_buggy_ucrt_strfptime +from test.support import skip_if_buggy_ucrt_strfptime, SuppressCrashReport # Max year is only limited by the size of C int. SIZEOF_INT = sysconfig.get_config_var('SIZEOF_INT') or 4 @@ -178,6 +178,17 @@ def test_strftime(self): self.assertRaises(TypeError, time.strftime, b'%S', tt) + def test_strftime_invalid_format(self): + tt = time.gmtime(self.t) + with SuppressCrashReport(): + for i in range(1, 128): + format = ' %' + chr(i) + with self.subTest(format=format): + try: + time.strftime(format, tt) + except ValueError as exc: + self.assertEqual(str(exc), 'Invalid format string') + def test_strftime_special(self): tt = time.gmtime(self.t) s1 = time.strftime('%c', tt) From 2ce10b17293c67e2ceeef9ced3719a49c61aa80c Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Sat, 19 Oct 2024 06:27:44 +0200 Subject: [PATCH 081/269] [3.12] GH-125277: Increase minimum supported Sphinx to 7.2.6 (GH-125368) (#125721) --- .github/workflows/reusable-docs.yml | 2 +- Doc/conf.py | 2 +- Doc/requirements-oldest-sphinx.txt | 30 +++++++++---------- ...-10-10-23-46-54.gh-issue-125277.QAby09.rst | 2 ++ 4 files changed, 19 insertions(+), 17 deletions(-) create mode 100644 Misc/NEWS.d/next/Documentation/2024-10-10-23-46-54.gh-issue-125277.QAby09.rst diff --git a/.github/workflows/reusable-docs.yml b/.github/workflows/reusable-docs.yml index 4d1dc04e8b638a..d575963e3cec4a 100644 --- a/.github/workflows/reusable-docs.yml +++ b/.github/workflows/reusable-docs.yml @@ -81,7 +81,7 @@ jobs: - name: 'Set up Python' uses: actions/setup-python@v5 with: - python-version: '3.12' # known to work with Sphinx 6.2.1 + python-version: '3.13' # known to work with Sphinx 7.2.6 cache: 'pip' cache-dependency-path: 'Doc/requirements-oldest-sphinx.txt' - name: 'Install build dependencies' diff --git a/Doc/conf.py b/Doc/conf.py index fb55c5c65c2f35..f8e13cd109d7f4 100644 --- a/Doc/conf.py +++ b/Doc/conf.py @@ -82,7 +82,7 @@ highlight_language = 'python3' # Minimum version of sphinx required -needs_sphinx = '6.2.1' +needs_sphinx = '7.2.6' # Create table of contents entries for domain objects (e.g. functions, classes, # attributes, etc.). Default is True. diff --git a/Doc/requirements-oldest-sphinx.txt b/Doc/requirements-oldest-sphinx.txt index 068fe0cb426ecd..3483faea6b56cb 100644 --- a/Doc/requirements-oldest-sphinx.txt +++ b/Doc/requirements-oldest-sphinx.txt @@ -7,29 +7,29 @@ blurb python-docs-theme>=2022.1 # Generated from: -# pip install "Sphinx~=6.2.1" +# pip install "Sphinx~=7.2.6" # pip freeze # -# Sphinx 6.2.1 comes from ``needs_sphinx = '6.2.1'`` in ``Doc/conf.py``. +# Sphinx 7.2.6 comes from ``needs_sphinx = '7.2.6'`` in ``Doc/conf.py``. alabaster==0.7.16 -Babel==2.15.0 -certifi==2024.7.4 -charset-normalizer==3.3.2 -docutils==0.19 -idna==3.7 +Babel==2.16.0 +certifi==2024.8.30 +charset-normalizer==3.4.0 +docutils==0.20.1 +idna==3.10 imagesize==1.4.1 Jinja2==3.1.4 -MarkupSafe==2.1.5 +MarkupSafe==3.0.1 packaging==24.1 Pygments==2.18.0 requests==2.32.3 snowballstemmer==2.2.0 -Sphinx==6.2.1 -sphinxcontrib-applehelp==1.0.8 -sphinxcontrib-devhelp==1.0.6 -sphinxcontrib-htmlhelp==2.0.5 +Sphinx==7.2.6 +sphinxcontrib-applehelp==2.0.0 +sphinxcontrib-devhelp==2.0.0 +sphinxcontrib-htmlhelp==2.1.0 sphinxcontrib-jsmath==1.0.1 -sphinxcontrib-qthelp==1.0.7 -sphinxcontrib-serializinghtml==1.1.10 -urllib3==2.2.2 +sphinxcontrib-qthelp==2.0.0 +sphinxcontrib-serializinghtml==2.0.0 +urllib3==2.2.3 diff --git a/Misc/NEWS.d/next/Documentation/2024-10-10-23-46-54.gh-issue-125277.QAby09.rst b/Misc/NEWS.d/next/Documentation/2024-10-10-23-46-54.gh-issue-125277.QAby09.rst new file mode 100644 index 00000000000000..fcd6e22c27b5f4 --- /dev/null +++ b/Misc/NEWS.d/next/Documentation/2024-10-10-23-46-54.gh-issue-125277.QAby09.rst @@ -0,0 +1,2 @@ +Require Sphinx 7.2.6 or later to build the Python documentation. +Patch by Adam Turner. From 5f6e1120e3ba21d5140e91737386e46a22ffb5ea Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Sat, 19 Oct 2024 14:07:16 +0200 Subject: [PATCH 082/269] [3.12] gh-125522: Fix bare except in test_math.testTan (GH-125544) (#125727) gh-125522: Fix bare except in test_math.testTan (GH-125544) (cherry picked from commit 4b421e8aca7f2dccc5ac8604b78589941dd7974c) Co-authored-by: Irit Katriel <1055913+iritkatriel@users.noreply.github.com> --- Lib/test/test_math.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_math.py b/Lib/test/test_math.py index 7e403f7ef25891..600946eeb8272d 100644 --- a/Lib/test/test_math.py +++ b/Lib/test/test_math.py @@ -1886,7 +1886,7 @@ def testTan(self): try: self.assertTrue(math.isnan(math.tan(INF))) self.assertTrue(math.isnan(math.tan(NINF))) - except: + except ValueError: self.assertRaises(ValueError, math.tan, INF) self.assertRaises(ValueError, math.tan, NINF) self.assertTrue(math.isnan(math.tan(NAN))) From 9a22ec735fb2f0ec850e70dd119e8422c97567e5 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Mon, 21 Oct 2024 02:43:26 +0200 Subject: [PATCH 083/269] [3.12] gh-99030: Added documentation links for types and exceptions (GH-123857) (GH-125765) gh-99030: Added documentation links for types and exceptions (GH-123857) * Added documentation links for types and exceptions * Shortened description sentences * Change content * Change documentation * Move seealso * Add a spaces (cherry picked from commit 9256be7ff0ab035cfd262127d893c9bc88b3c84c) Co-authored-by: RUANG (Roy James) --- Doc/library/builtins.rst | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Doc/library/builtins.rst b/Doc/library/builtins.rst index 644344e7fef29a..c4979db52d2aed 100644 --- a/Doc/library/builtins.rst +++ b/Doc/library/builtins.rst @@ -7,10 +7,7 @@ -------------- This module provides direct access to all 'built-in' identifiers of Python; for -example, ``builtins.open`` is the full name for the built-in function -:func:`open`. See :ref:`built-in-funcs` and :ref:`built-in-consts` for -documentation. - +example, ``builtins.open`` is the full name for the built-in function :func:`open`. This module is not normally accessed explicitly by most applications, but can be useful in modules that provide objects with the same name as a built-in value, @@ -40,3 +37,10 @@ available as part of their globals. The value of ``__builtins__`` is normally either this module or the value of this module's :attr:`~object.__dict__` attribute. Since this is an implementation detail, it may not be used by alternate implementations of Python. + +.. seealso:: + + * :ref:`built-in-consts` + * :ref:`bltin-exceptions` + * :ref:`built-in-funcs` + * :ref:`bltin-types` From 8cc8d7d61ff15b077fc389fbcb0bee60979cea82 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Mon, 21 Oct 2024 10:20:42 +0200 Subject: [PATCH 084/269] [3.12] gh-125519: Improve traceback if `importlib.reload()` is called with a non-module object (GH-125520) (#125769) gh-125519: Improve traceback if `importlib.reload()` is called with a non-module object (GH-125520) (cherry picked from commit c5c21fee7ae1ea689a351caa454c98e716a6e537) Co-authored-by: Alex Waygood --- Lib/importlib/__init__.py | 2 +- Lib/test/test_importlib/test_api.py | 16 ++++++++++++++++ ...024-10-15-14-01-03.gh-issue-125519.TqGh6a.rst | 2 ++ 3 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2024-10-15-14-01-03.gh-issue-125519.TqGh6a.rst diff --git a/Lib/importlib/__init__.py b/Lib/importlib/__init__.py index 707c081cb2c5b6..5b92442fc379e0 100644 --- a/Lib/importlib/__init__.py +++ b/Lib/importlib/__init__.py @@ -105,7 +105,7 @@ def reload(module): try: name = module.__name__ except AttributeError: - raise TypeError("reload() argument must be a module") + raise TypeError("reload() argument must be a module") from None if sys.modules.get(name) is not module: raise ImportError(f"module {name} not in sys.modules", name=name) diff --git a/Lib/test/test_importlib/test_api.py b/Lib/test/test_importlib/test_api.py index ecf2c47c462e23..ef01a3f6de09bf 100644 --- a/Lib/test/test_importlib/test_api.py +++ b/Lib/test/test_importlib/test_api.py @@ -8,6 +8,8 @@ import sys from test.support import import_helper from test.support import os_helper +from test import support +import traceback import types import unittest import warnings @@ -354,6 +356,20 @@ def test_module_missing_spec(self): with self.assertRaises(ModuleNotFoundError): self.init.reload(module) + def test_reload_traceback_with_non_str(self): + # gh-125519 + with support.captured_stdout() as stdout: + try: + self.init.reload("typing") + except TypeError as exc: + traceback.print_exception(exc, file=stdout) + else: + self.fail("Expected TypeError to be raised") + printed_traceback = stdout.getvalue() + self.assertIn("TypeError", printed_traceback) + self.assertNotIn("AttributeError", printed_traceback) + self.assertNotIn("module.__spec__.name", printed_traceback) + (Frozen_ReloadTests, Source_ReloadTests diff --git a/Misc/NEWS.d/next/Library/2024-10-15-14-01-03.gh-issue-125519.TqGh6a.rst b/Misc/NEWS.d/next/Library/2024-10-15-14-01-03.gh-issue-125519.TqGh6a.rst new file mode 100644 index 00000000000000..e6062625104590 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-10-15-14-01-03.gh-issue-125519.TqGh6a.rst @@ -0,0 +1,2 @@ +Improve traceback if :func:`importlib.reload` is called with an object that +is not a module. Patch by Alex Waygood. From 2bc2aae386f7d426022441bf9b1ce8b85bfbb46d Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Mon, 21 Oct 2024 12:23:41 +0200 Subject: [PATCH 085/269] [3.12] gh-125741: Update check_generated_files CI to use our published container image (gh-125744) (#125760) (cherry picked from commit ed24702bd0f9925908ce48584c31dfad732208b2) Co-authored-by: Donghee Na --- .github/workflows/build.yml | 53 ++++++++++++++++++++++++++++++------- 1 file changed, 44 insertions(+), 9 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4c8fed552b5d04..a6daf0a436a459 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -91,6 +91,50 @@ jobs: name: abi-data path: ./Doc/data/*.abi + check_autoconf_regen: + name: 'Check if Autoconf files are up to date' + # Don't use ubuntu-latest but a specific version to make the job + # reproducible: to get the same tools versions (autoconf, aclocal, ...) + runs-on: ubuntu-24.04 + container: + image: ghcr.io/python/autoconf:2024.10.11.11293396815 + timeout-minutes: 60 + needs: check_source + if: needs.check_source.outputs.run_tests == 'true' + steps: + - name: Install Git + run: | + apt install git -yq + git config --global --add safe.directory "$GITHUB_WORKSPACE" + - uses: actions/checkout@v4 + with: + fetch-depth: 1 + - name: Runner image version + run: echo "IMAGE_VERSION=${ImageVersion}" >> $GITHUB_ENV + - name: Check Autoconf and aclocal versions + run: | + grep "Generated by GNU Autoconf 2.71" configure + grep "aclocal 1.16.5" aclocal.m4 + grep -q "runstatedir" configure + grep -q "PKG_PROG_PKG_CONFIG" aclocal.m4 + - name: Regenerate autoconf files + # Same command used by Tools/build/regen-configure.sh ($AUTORECONF) + run: autoreconf -ivf -Werror + - name: Check for changes + run: | + git add -u + changes=$(git status --porcelain) + # Check for changes in regenerated files + if test -n "$changes"; then + echo "Generated files not up to date." + echo "Perhaps you forgot to run make regen-all or build.bat --regen. ;)" + echo "configure files must be regenerated with a specific version of autoconf." + echo "$changes" + echo "" + git diff --staged || true + exit 1 + fi + check_generated_files: name: 'Check if generated files are up to date' # Don't use ubuntu-latest but a specific version to make the job @@ -119,19 +163,10 @@ jobs: uses: hendrikmuhs/ccache-action@v1.2 with: save: false - - name: Check Autoconf and aclocal versions - run: | - grep "Generated by GNU Autoconf 2.71" configure - grep "aclocal 1.16.5" aclocal.m4 - grep -q "runstatedir" configure - grep -q "PKG_PROG_PKG_CONFIG" aclocal.m4 - name: Configure CPython run: | # Build Python with the libpython dynamic library ./configure --config-cache --with-pydebug --enable-shared - - name: Regenerate autoconf files - # Same command used by Tools/build/regen-configure.sh ($AUTORECONF) - run: autoreconf -ivf -Werror - name: Build CPython run: | # Deepfreeze will usually cause global objects to be added or removed, From 18196fea89af7d1f68386e5f6ccc63de04b75a98 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Mon, 21 Oct 2024 15:06:02 +0200 Subject: [PATCH 086/269] [3.12] gh-125682: Reject non-ASCII digits in the Python implementation of JSON decoder (GH-125687) (GH-125693) (cherry picked from commit d358425e6968858e52908794d15f37e62abc74ec) Co-authored-by: Nice Zombies --- Lib/json/scanner.py | 2 +- Lib/test/test_json/test_decode.py | 6 ++++++ .../Library/2024-10-18-09-51-29.gh-issue-125682.vsj4cU.rst | 2 ++ 3 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2024-10-18-09-51-29.gh-issue-125682.vsj4cU.rst diff --git a/Lib/json/scanner.py b/Lib/json/scanner.py index 7a61cfc2d24dce..090897515fe2f3 100644 --- a/Lib/json/scanner.py +++ b/Lib/json/scanner.py @@ -9,7 +9,7 @@ __all__ = ['make_scanner'] NUMBER_RE = re.compile( - r'(-?(?:0|[1-9]\d*))(\.\d+)?([eE][-+]?\d+)?', + r'(-?(?:0|[1-9][0-9]*))(\.[0-9]+)?([eE][-+]?[0-9]+)?', (re.VERBOSE | re.MULTILINE | re.DOTALL)) def py_make_scanner(context): diff --git a/Lib/test/test_json/test_decode.py b/Lib/test/test_json/test_decode.py index 79fb239b35d3f2..2250af964c022b 100644 --- a/Lib/test/test_json/test_decode.py +++ b/Lib/test/test_json/test_decode.py @@ -16,6 +16,12 @@ def test_float(self): self.assertIsInstance(rval, float) self.assertEqual(rval, 1.0) + def test_nonascii_digits_rejected(self): + # JSON specifies only ascii digits, see gh-125687 + for num in ["1\uff10", "0.\uff10", "0e\uff10"]: + with self.assertRaises(self.JSONDecodeError): + self.loads(num) + def test_bytes(self): self.assertEqual(self.loads(b"1"), 1) diff --git a/Misc/NEWS.d/next/Library/2024-10-18-09-51-29.gh-issue-125682.vsj4cU.rst b/Misc/NEWS.d/next/Library/2024-10-18-09-51-29.gh-issue-125682.vsj4cU.rst new file mode 100644 index 00000000000000..3eb2905ad8d810 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-10-18-09-51-29.gh-issue-125682.vsj4cU.rst @@ -0,0 +1,2 @@ +Reject non-ASCII digits in the Python implementation of :func:`json.loads` +conforming to the JSON specification. From 2746ec4ce2b55efcd160002012147ce20686e47e Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Mon, 21 Oct 2024 15:08:10 +0200 Subject: [PATCH 087/269] [3.12] gh-125660: Reject invalid unicode escapes for Python implementation of JSON decoder (GH-125683) (GH-125695) (cherry picked from commit df751363e386d1f77c5ba9515a5539902457d386) Co-authored-by: Nice Zombies --- Lib/json/decoder.py | 9 +++++---- Lib/test/test_json/test_scanstring.py | 10 ++++++++++ .../2024-10-18-08-58-10.gh-issue-125660.sDdDqO.rst | 1 + 3 files changed, 16 insertions(+), 4 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-10-18-08-58-10.gh-issue-125660.sDdDqO.rst diff --git a/Lib/json/decoder.py b/Lib/json/decoder.py index c5d9ae2d0d5d04..5e5effeac02551 100644 --- a/Lib/json/decoder.py +++ b/Lib/json/decoder.py @@ -50,17 +50,18 @@ def __reduce__(self): } +HEXDIGITS = re.compile(r'[0-9A-Fa-f]{4}', FLAGS) STRINGCHUNK = re.compile(r'(.*?)(["\\\x00-\x1f])', FLAGS) BACKSLASH = { '"': '"', '\\': '\\', '/': '/', 'b': '\b', 'f': '\f', 'n': '\n', 'r': '\r', 't': '\t', } -def _decode_uXXXX(s, pos): - esc = s[pos + 1:pos + 5] - if len(esc) == 4 and esc[1] not in 'xX': +def _decode_uXXXX(s, pos, _m=HEXDIGITS.match): + esc = _m(s, pos + 1) + if esc is not None: try: - return int(esc, 16) + return int(esc.group(), 16) except ValueError: pass msg = "Invalid \\uXXXX escape" diff --git a/Lib/test/test_json/test_scanstring.py b/Lib/test/test_json/test_scanstring.py index 2d3ee8a8bf0f92..cca556a3b95bab 100644 --- a/Lib/test/test_json/test_scanstring.py +++ b/Lib/test/test_json/test_scanstring.py @@ -116,6 +116,11 @@ def test_bad_escapes(self): '"\\u012z"', '"\\u0x12"', '"\\u0X12"', + '"\\u{0}"'.format("\uff10" * 4), + '"\\u 123"', + '"\\u-123"', + '"\\u+123"', + '"\\u1_23"', '"\\ud834\\"', '"\\ud834\\u"', '"\\ud834\\ud"', @@ -127,6 +132,11 @@ def test_bad_escapes(self): '"\\ud834\\udd2z"', '"\\ud834\\u0x20"', '"\\ud834\\u0X20"', + '"\\ud834\\u{0}"'.format("\uff10" * 4), + '"\\ud834\\u 123"', + '"\\ud834\\u-123"', + '"\\ud834\\u+123"', + '"\\ud834\\u1_23"', ] for s in bad_escapes: with self.assertRaises(self.JSONDecodeError, msg=s): diff --git a/Misc/NEWS.d/next/Library/2024-10-18-08-58-10.gh-issue-125660.sDdDqO.rst b/Misc/NEWS.d/next/Library/2024-10-18-08-58-10.gh-issue-125660.sDdDqO.rst new file mode 100644 index 00000000000000..74d76c7bddae7d --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-10-18-08-58-10.gh-issue-125660.sDdDqO.rst @@ -0,0 +1 @@ +Reject invalid unicode escapes for Python implementation of :func:`json.loads`. From c3e705fbe5258d8d02f2e405d04b72c427bb4a74 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Mon, 21 Oct 2024 18:55:11 +0200 Subject: [PATCH 088/269] [3.12] Doc: C API: Move `tp_dealloc` paragraph to `tp_dealloc` section (GH-125737) (#125799) Doc: C API: Move `tp_dealloc` paragraph to `tp_dealloc` section (GH-125737) It looks like commit 43cf44ddcce6b225f959ea2a53e4817244ca6054 (gh-31501) accidentally moved the paragraph to the `tp_finalize` section when the intent was to move it to the `tp_dealloc` section (according to the commit message). (cherry picked from commit d880c83ff7fb2e464bc4f469d74cc3fc3eca082c) Co-authored-by: Richard Hansen --- Doc/c-api/typeobj.rst | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/Doc/c-api/typeobj.rst b/Doc/c-api/typeobj.rst index 1b28a2a3060e6e..610cb96e8885b6 100644 --- a/Doc/c-api/typeobj.rst +++ b/Doc/c-api/typeobj.rst @@ -704,6 +704,19 @@ and :c:data:`PyType_Type` effectively act as defaults.) Py_DECREF(tp); } + .. warning:: + + In a garbage collected Python, :c:member:`!tp_dealloc` may be called from + any Python thread, not just the thread which created the object (if the + object becomes part of a refcount cycle, that cycle might be collected by + a garbage collection on any thread). This is not a problem for Python + API calls, since the thread on which :c:member:`!tp_dealloc` is called + will own the Global Interpreter Lock (GIL). However, if the object being + destroyed in turn destroys objects from some other C or C++ library, care + should be taken to ensure that destroying those objects on the thread + which called :c:member:`!tp_dealloc` will not violate any assumptions of + the library. + **Inheritance:** @@ -2101,17 +2114,6 @@ and :c:data:`PyType_Type` effectively act as defaults.) PyErr_Restore(error_type, error_value, error_traceback); } - Also, note that, in a garbage collected Python, - :c:member:`~PyTypeObject.tp_dealloc` may be called from - any Python thread, not just the thread which created the object (if the object - becomes part of a refcount cycle, that cycle might be collected by a garbage - collection on any thread). This is not a problem for Python API calls, since - the thread on which tp_dealloc is called will own the Global Interpreter Lock - (GIL). However, if the object being destroyed in turn destroys objects from some - other C or C++ library, care should be taken to ensure that destroying those - objects on the thread which called tp_dealloc will not violate any assumptions - of the library. - **Inheritance:** This field is inherited by subtypes. From cea93dbd99d4d19c1c8ec688d9411e59f1ba42d2 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Mon, 21 Oct 2024 18:56:29 +0200 Subject: [PATCH 089/269] [3.12] gh-125766: Docs: minor rewording of installation on Linux section (GH-125794) (cherry picked from commit d67bf2d89ab57f94608d7d2cf949dc4a8749485d) Co-authored-by: partev --- Doc/using/unix.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Doc/using/unix.rst b/Doc/using/unix.rst index 58838c28e6eb86..a2bcdab09a9282 100644 --- a/Doc/using/unix.rst +++ b/Doc/using/unix.rst @@ -17,12 +17,12 @@ On Linux Python comes preinstalled on most Linux distributions, and is available as a package on all others. However there are certain features you might want to use -that are not available on your distro's package. You can easily compile the +that are not available on your distro's package. You can compile the latest version of Python from source. -In the event that Python doesn't come preinstalled and isn't in the repositories as -well, you can easily make packages for your own distro. Have a look at the -following links: +In the event that the latest version of Python doesn't come preinstalled and isn't +in the repositories as well, you can make packages for your own distro. Have a +look at the following links: .. seealso:: From 0009651d8ea5bf084da4436dcf28a374d40b066b Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Mon, 21 Oct 2024 21:24:45 +0200 Subject: [PATCH 090/269] [3.12] gh-124969: Make locale.nl_langinfo(locale.ALT_DIGITS) returning a string again (GH-125774) (GH-125805) This is a follow up of GH-124974. Only Glibc needed a fix. Now the returned value is a string consisting of semicolon-separated symbols on all Posix platforms. (cherry picked from commit dcc4fb2c9068f60353f0c0978948b7681f7745e6) Co-authored-by: Serhiy Storchaka --- Doc/library/locale.rst | 7 ++-- Lib/test/test__locale.py | 30 ++++++++++----- ...-10-08-12-09-09.gh-issue-124969._VBQLq.rst | 7 ++-- Modules/_localemodule.c | 38 +++++++++++-------- 4 files changed, 52 insertions(+), 30 deletions(-) diff --git a/Doc/library/locale.rst b/Doc/library/locale.rst index f0553d51fedf14..a81879a2fe48dc 100644 --- a/Doc/library/locale.rst +++ b/Doc/library/locale.rst @@ -158,8 +158,7 @@ The :mod:`locale` module defines the following exception and functions: .. function:: nl_langinfo(option) - Return some locale-specific information as a string (or a tuple for - ``ALT_DIGITS``). This function is not + Return some locale-specific information as a string. This function is not available on all systems, and the set of possible options might also vary across platforms. The possible argument values are numbers, for which symbolic constants are available in the locale module. @@ -312,7 +311,9 @@ The :mod:`locale` module defines the following exception and functions: .. data:: ALT_DIGITS - Get a tuple of up to 100 strings used to represent the values 0 to 99. + Get a string consisting of up to 100 semicolon-separated symbols used + to represent the values 0 to 99 in a locale-specific way. + In most locales this is an empty string. .. function:: getdefaultlocale([envvars]) diff --git a/Lib/test/test__locale.py b/Lib/test/test__locale.py index 5041def7216197..a680e6edb63c0e 100644 --- a/Lib/test/test__locale.py +++ b/Lib/test/test__locale.py @@ -26,7 +26,10 @@ 'bs_BA', 'fr_LU', 'kl_GL', 'fa_IR', 'de_BE', 'sv_SE', 'it_CH', 'uk_UA', 'eu_ES', 'vi_VN', 'af_ZA', 'nb_NO', 'en_DK', 'tg_TJ', 'ps_AF', 'en_US', 'fr_FR.ISO8859-1', 'fr_FR.UTF-8', 'fr_FR.ISO8859-15@euro', - 'ru_RU.KOI8-R', 'ko_KR.eucKR'] + 'ru_RU.KOI8-R', 'ko_KR.eucKR', + 'ja_JP.UTF-8', 'lzh_TW.UTF-8', 'my_MM.UTF-8', 'or_IN.UTF-8', 'shn_MM.UTF-8', + 'ar_AE.UTF-8', 'bn_IN.UTF-8', 'mr_IN.UTF-8', 'th_TH.TIS620', +] def setUpModule(): global candidate_locales @@ -78,11 +81,13 @@ def accept(loc): 'C': (0, {}), 'en_US': (0, {}), 'fa_IR': (100, {0: '\u06f0\u06f0', 10: '\u06f1\u06f0', 99: '\u06f9\u06f9'}), - 'ja_JP': (100, {0: '\u3007', 10: '\u5341', 99: '\u4e5d\u5341\u4e5d'}), + 'ja_JP': (100, {1: '\u4e00', 10: '\u5341', 99: '\u4e5d\u5341\u4e5d'}), 'lzh_TW': (32, {0: '\u3007', 10: '\u5341', 31: '\u5345\u4e00'}), 'my_MM': (100, {0: '\u1040\u1040', 10: '\u1041\u1040', 99: '\u1049\u1049'}), 'or_IN': (100, {0: '\u0b66', 10: '\u0b67\u0b66', 99: '\u0b6f\u0b6f'}), 'shn_MM': (100, {0: '\u1090\u1090', 10: '\u1091\u1090', 99: '\u1099\u1099'}), + 'ar_AE': (100, {0: '\u0660', 10: '\u0661\u0660', 99: '\u0669\u0669'}), + 'bn_IN': (100, {0: '\u09e6', 10: '\u09e7\u09e6', 99: '\u09ef\u09ef'}), } if sys.platform == 'win32': @@ -196,7 +201,7 @@ def test_lc_numeric_basic(self): def test_alt_digits_nl_langinfo(self): # Test nl_langinfo(ALT_DIGITS) tested = False - for loc, (count, samples) in known_alt_digits.items(): + for loc in candidate_locales: with self.subTest(locale=loc): try: setlocale(LC_TIME, loc) @@ -204,14 +209,21 @@ def test_alt_digits_nl_langinfo(self): except Error: self.skipTest(f'no locale {loc!r}') continue + with self.subTest(locale=loc): alt_digits = nl_langinfo(locale.ALT_DIGITS) - self.assertIsInstance(alt_digits, tuple) - if count and not alt_digits and support.is_apple: - self.skipTest(f'ALT_DIGITS is not set for locale {loc!r} on Apple platforms') - self.assertEqual(len(alt_digits), count) - for i in samples: - self.assertEqual(alt_digits[i], samples[i]) + self.assertIsInstance(alt_digits, str) + alt_digits = alt_digits.split(';') if alt_digits else [] + if alt_digits: + self.assertGreaterEqual(len(alt_digits), 10, alt_digits) + loc1 = loc.split('.', 1)[0] + if loc1 in known_alt_digits: + count, samples = known_alt_digits[loc1] + if count and not alt_digits: + self.skipTest(f'ALT_DIGITS is not set for locale {loc!r} on this platform') + self.assertEqual(len(alt_digits), count, alt_digits) + for i in samples: + self.assertEqual(alt_digits[i], samples[i]) tested = True if not tested: self.skipTest('no suitable locales') diff --git a/Misc/NEWS.d/next/Library/2024-10-08-12-09-09.gh-issue-124969._VBQLq.rst b/Misc/NEWS.d/next/Library/2024-10-08-12-09-09.gh-issue-124969._VBQLq.rst index b5082b90721d42..7959ce2d1e9907 100644 --- a/Misc/NEWS.d/next/Library/2024-10-08-12-09-09.gh-issue-124969._VBQLq.rst +++ b/Misc/NEWS.d/next/Library/2024-10-08-12-09-09.gh-issue-124969._VBQLq.rst @@ -1,3 +1,4 @@ -Fix ``locale.nl_langinfo(locale.ALT_DIGITS)``. Now it returns a tuple of up -to 100 strings (an empty tuple on most locales). Previously it returned the -first item of that tuple or an empty string. +Fix ``locale.nl_langinfo(locale.ALT_DIGITS)`` on platforms with glibc. +Now it returns a string consisting of up to 100 semicolon-separated symbols +(an empty string in most locales) on all Posix platforms. +Previously it only returned the first symbol or an empty string. diff --git a/Modules/_localemodule.c b/Modules/_localemodule.c index 3dea764fdaad27..53ebb57d23ae07 100644 --- a/Modules/_localemodule.c +++ b/Modules/_localemodule.c @@ -619,28 +619,36 @@ _locale_nl_langinfo_impl(PyObject *module, int item) const char *result = nl_langinfo(item); result = result != NULL ? result : ""; PyObject *pyresult; +#ifdef __GLIBC__ #ifdef ALT_DIGITS - if (item == ALT_DIGITS) { - /* The result is a sequence of up to 100 NUL-separated strings. */ - const char *s = result; + if (item == ALT_DIGITS && *result) { + /* According to the POSIX specification the result must be + * a sequence of up to 100 semicolon-separated strings. + * But in Glibc they are NUL-separated. */ + Py_ssize_t i = 0; int count = 0; - for (; count < 100 && *s; count++) { - s += strlen(s) + 1; + for (; count < 100 && result[i]; count++) { + i += strlen(result + i) + 1; } - pyresult = PyTuple_New(count); - if (pyresult != NULL) { - for (int i = 0; i < count; i++) { - PyObject *unicode = PyUnicode_DecodeLocale(result, NULL); - if (unicode == NULL) { - Py_CLEAR(pyresult); - break; - } - PyTuple_SET_ITEM(pyresult, i, unicode); - result += strlen(result) + 1; + char *buf = PyMem_Malloc(i); + if (buf == NULL) { + PyErr_NoMemory(); + pyresult = NULL; + } + else { + memcpy(buf, result, i); + /* Replace all NULs with semicolons. */ + i = 0; + while (--count) { + i += strlen(buf + i); + buf[i++] = ';'; } + pyresult = PyUnicode_DecodeLocale(buf, NULL); + PyMem_Free(buf); } } else +#endif #endif { pyresult = PyUnicode_DecodeLocale(result, NULL); From e6746d9a5d215064ae5592b520d9fd3115a023aa Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Mon, 21 Oct 2024 21:37:34 +0200 Subject: [PATCH 091/269] [3.12] gh-53203: Fix test_strptime on Solaris (GH-125785) (GH-125807) Use fixed timezone. Skip roundtrip tests on locales with 2-digit year. (cherry picked from commit 9dde4638e44639d45bd7d72e70a8d410995a585a) Co-authored-by: Serhiy Storchaka --- Lib/test/test_strptime.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Lib/test/test_strptime.py b/Lib/test/test_strptime.py index 45b5c8ed2c3051..94b16635bb023c 100644 --- a/Lib/test/test_strptime.py +++ b/Lib/test/test_strptime.py @@ -513,12 +513,17 @@ def test_date_time_locale(self): # NB: Dates before 1969 do not roundtrip on some locales: # az_IR, bo_CN, bo_IN, dz_BT, eu_ES, eu_FR, fa_IR, or_IN. + @support.run_with_tz('STD-1DST,M4.1.0,M10.1.0') @run_with_locales('LC_TIME', 'C', 'en_US', 'fr_FR', 'de_DE', 'ja_JP', 'he_IL', 'ar_AE', 'mfe_MU', 'yo_NG', 'csb_PL', 'br_FR', 'gez_ET', 'brx_IN', 'my_MM', 'shn_MM') def test_date_time_locale2(self): # Test %c directive + loc = locale.getlocale(locale.LC_TIME)[0] + if sys.platform.startswith('sunos'): + if loc in ('ar_AE',): + self.skipTest(f'locale {loc!r} may not work on this platform') self.roundtrip('%c', slice(0, 6), (1900, 1, 1, 0, 0, 0, 0, 1, 0)) self.roundtrip('%c', slice(0, 6), (1800, 1, 1, 0, 0, 0, 0, 1, 0)) @@ -551,6 +556,10 @@ def test_date_locale(self): 'eu_ES', 'ar_AE', 'my_MM', 'shn_MM') def test_date_locale2(self): # Test %x directive + loc = locale.getlocale(locale.LC_TIME)[0] + if sys.platform.startswith('sunos'): + if loc in ('en_US', 'de_DE', 'ar_AE'): + self.skipTest(f'locale {loc!r} may not work on this platform') self.roundtrip('%x', slice(0, 3), (1900, 1, 1, 0, 0, 0, 0, 1, 0)) self.roundtrip('%x', slice(0, 3), (1800, 1, 1, 0, 0, 0, 0, 1, 0)) From 7a2bd3d2adba2c95f316243dc477910e63f4e6ae Mon Sep 17 00:00:00 2001 From: Zachary Ware Date: Mon, 21 Oct 2024 21:44:56 -0500 Subject: [PATCH 092/269] [3.12] gh-124448: Update Windows builds to use Tcl/Tk 8.6.15 (GH-125796) (cherry picked from commit 9d8f2d8e08336695fdade5846da4bbcc3eb5f152) --- ...024-09-24-19-04-56.gh-issue-124448.srVT3d.rst | 1 + Misc/externals.spdx.json | 16 ++++++++-------- PCbuild/get_externals.bat | 6 +++--- PCbuild/readme.txt | 2 +- PCbuild/tcltk.props | 2 +- 5 files changed, 14 insertions(+), 13 deletions(-) create mode 100644 Misc/NEWS.d/next/Windows/2024-09-24-19-04-56.gh-issue-124448.srVT3d.rst diff --git a/Misc/NEWS.d/next/Windows/2024-09-24-19-04-56.gh-issue-124448.srVT3d.rst b/Misc/NEWS.d/next/Windows/2024-09-24-19-04-56.gh-issue-124448.srVT3d.rst new file mode 100644 index 00000000000000..ca9845a8daea9d --- /dev/null +++ b/Misc/NEWS.d/next/Windows/2024-09-24-19-04-56.gh-issue-124448.srVT3d.rst @@ -0,0 +1 @@ +Updated bundled Tcl/Tk to 8.6.15. diff --git a/Misc/externals.spdx.json b/Misc/externals.spdx.json index 6c711e74f94c79..4219cd4fe4c953 100644 --- a/Misc/externals.spdx.json +++ b/Misc/externals.spdx.json @@ -90,42 +90,42 @@ "checksums": [ { "algorithm": "SHA256", - "checksumValue": "6e33a88f116822167734cd72b693b5d30ced130a3cae6dc2ff696042f993bb42" + "checksumValue": "4c23f0dd3efcbe6f3a22c503a68d147617bb30c4f5290f1eb3eaacf0b460440b" } ], - "downloadLocation": "https://github.com/python/cpython-source-deps/archive/refs/tags/tcl-core-8.6.13.0.tar.gz", + "downloadLocation": "https://github.com/python/cpython-source-deps/archive/refs/tags/tcl-core-8.6.15.0.tar.gz", "externalRefs": [ { "referenceCategory": "SECURITY", - "referenceLocator": "cpe:2.3:a:tcl_tk:tcl_tk:8.6.13.0:*:*:*:*:*:*:*", + "referenceLocator": "cpe:2.3:a:tcl_tk:tcl_tk:8.6.15.0:*:*:*:*:*:*:*", "referenceType": "cpe23Type" } ], "licenseConcluded": "NOASSERTION", "name": "tcl-core", "primaryPackagePurpose": "SOURCE", - "versionInfo": "8.6.13.0" + "versionInfo": "8.6.15.0" }, { "SPDXID": "SPDXRef-PACKAGE-tk", "checksums": [ { "algorithm": "SHA256", - "checksumValue": "896c1f488bdd0159091bd5cce124b756dfdffa4a5350b7fd4d7d8e48421089a4" + "checksumValue": "0ae56d39bca92865f338529557a1e56d110594184b6dc5a91339c5675751e264" } ], - "downloadLocation": "https://github.com/python/cpython-source-deps/archive/refs/tags/tk-8.6.13.0.tar.gz", + "downloadLocation": "https://github.com/python/cpython-source-deps/archive/refs/tags/tk-8.6.15.0.tar.gz", "externalRefs": [ { "referenceCategory": "SECURITY", - "referenceLocator": "cpe:2.3:a:tcl_tk:tcl_tk:8.6.13.0:*:*:*:*:*:*:*", + "referenceLocator": "cpe:2.3:a:tcl_tk:tcl_tk:8.6.15.0:*:*:*:*:*:*:*", "referenceType": "cpe23Type" } ], "licenseConcluded": "NOASSERTION", "name": "tk", "primaryPackagePurpose": "SOURCE", - "versionInfo": "8.6.13.0" + "versionInfo": "8.6.15.0" }, { "SPDXID": "SPDXRef-PACKAGE-tix", diff --git a/PCbuild/get_externals.bat b/PCbuild/get_externals.bat index e0144bcdf18c10..fa99d03e51ffe6 100644 --- a/PCbuild/get_externals.bat +++ b/PCbuild/get_externals.bat @@ -55,8 +55,8 @@ set libraries=%libraries% bzip2-1.0.8 if NOT "%IncludeLibffiSrc%"=="false" set libraries=%libraries% libffi-3.4.4 if NOT "%IncludeSSLSrc%"=="false" set libraries=%libraries% openssl-3.0.15 set libraries=%libraries% sqlite-3.45.3.0 -if NOT "%IncludeTkinterSrc%"=="false" set libraries=%libraries% tcl-core-8.6.13.0 -if NOT "%IncludeTkinterSrc%"=="false" set libraries=%libraries% tk-8.6.13.0 +if NOT "%IncludeTkinterSrc%"=="false" set libraries=%libraries% tcl-core-8.6.15.0 +if NOT "%IncludeTkinterSrc%"=="false" set libraries=%libraries% tk-8.6.15.0 if NOT "%IncludeTkinterSrc%"=="false" set libraries=%libraries% tix-8.4.3.6 set libraries=%libraries% xz-5.2.5 set libraries=%libraries% zlib-1.3.1 @@ -78,7 +78,7 @@ echo.Fetching external binaries... set binaries= if NOT "%IncludeLibffi%"=="false" set binaries=%binaries% libffi-3.4.4 if NOT "%IncludeSSL%"=="false" set binaries=%binaries% openssl-bin-3.0.15 -if NOT "%IncludeTkinter%"=="false" set binaries=%binaries% tcltk-8.6.13.0 +if NOT "%IncludeTkinter%"=="false" set binaries=%binaries% tcltk-8.6.15.2 if NOT "%IncludeSSLSrc%"=="false" set binaries=%binaries% nasm-2.11.06 for %%b in (%binaries%) do ( diff --git a/PCbuild/readme.txt b/PCbuild/readme.txt index 29edf9bf260ab1..e054a94cf578bf 100644 --- a/PCbuild/readme.txt +++ b/PCbuild/readme.txt @@ -193,7 +193,7 @@ _sqlite3 Homepage: https://www.sqlite.org/ _tkinter - Wraps version 8.6.6 of the Tk windowing system, which is downloaded + Wraps version 8.6.15 of the Tk windowing system, which is downloaded from our binaries repository at https://github.com/python/cpython-bin-deps. diff --git a/PCbuild/tcltk.props b/PCbuild/tcltk.props index 9d5189b3b8e93d..6b01d5b78eb199 100644 --- a/PCbuild/tcltk.props +++ b/PCbuild/tcltk.props @@ -2,7 +2,7 @@ - 8.6.13.0 + 8.6.15.2 $(TclVersion) 8.4.3.6 $([System.Version]::Parse($(TclVersion)).Major) From 98a0b362b5a9fea5662003474a690ad415195bc1 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Tue, 22 Oct 2024 15:16:03 +0200 Subject: [PATCH 093/269] [3.12] Doc: Show object descriptions in the table of contents (GH-125757) (#125841) (cherry picked from commit 91ddde4af0c3031c84a967bcf59f6fb4f8a48c0d) Co-authored-by: Adam Turner <9087854+AA-Turner@users.noreply.github.com> --- Doc/conf.py | 3 ++- Doc/tools/extensions/pyspecific.py | 1 + Doc/tools/static/sidebar-wrap.css | 6 ++++++ 3 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 Doc/tools/static/sidebar-wrap.css diff --git a/Doc/conf.py b/Doc/conf.py index f8e13cd109d7f4..f4cf4ee9547e19 100644 --- a/Doc/conf.py +++ b/Doc/conf.py @@ -86,7 +86,8 @@ # Create table of contents entries for domain objects (e.g. functions, classes, # attributes, etc.). Default is True. -toc_object_entries = False +toc_object_entries = True +toc_object_entries_show_parents = 'hide' # Ignore any .rst files in the includes/ directory; # they're embedded in pages but not rendered individually. diff --git a/Doc/tools/extensions/pyspecific.py b/Doc/tools/extensions/pyspecific.py index d3a4835edd58a4..8cb5eaffebfad0 100644 --- a/Doc/tools/extensions/pyspecific.py +++ b/Doc/tools/extensions/pyspecific.py @@ -412,5 +412,6 @@ def setup(app): app.add_directive_to_domain('py', 'awaitablemethod', PyAwaitableMethod) app.add_directive_to_domain('py', 'abstractmethod', PyAbstractMethod) app.add_directive('miscnews', MiscNews) + app.add_css_file('sidebar-wrap.css') app.connect('env-check-consistency', patch_pairindextypes) return {'version': '1.0', 'parallel_read_safe': True} diff --git a/Doc/tools/static/sidebar-wrap.css b/Doc/tools/static/sidebar-wrap.css new file mode 100644 index 00000000000000..0a80f516f28349 --- /dev/null +++ b/Doc/tools/static/sidebar-wrap.css @@ -0,0 +1,6 @@ +div.sphinxsidebarwrapper { + overflow-x: scroll; +} +div.sphinxsidebarwrapper li code { + overflow-wrap: normal; +} From da3d81d3ea94002bc81af2713f6f891ac8211289 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Tue, 22 Oct 2024 16:23:30 +0300 Subject: [PATCH 094/269] [3.12] gh-125355: Rewrite parse_intermixed_args() in argparse (GH-125356) (GH-125839) * The parser no longer changes temporarily during parsing. * Default values are not processed twice. * Required mutually exclusive groups containing positional arguments are now supported. * The missing arguments report now includes the names of all required optional and positional arguments. * Unknown options can be intermixed with positional arguments in parse_known_intermixed_args(). (cherry picked from commit 759a54d28ffe7eac8c23917f5d3dfad8309856be) --- Lib/argparse.py | 99 +++++++------------ Lib/test/test_argparse.py | 52 +++++++--- ...-10-22-13-28-00.gh-issue-125355.zssHm_.rst | 7 ++ 3 files changed, 81 insertions(+), 77 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-10-22-13-28-00.gh-issue-125355.zssHm_.rst diff --git a/Lib/argparse.py b/Lib/argparse.py index 22c9b07db18a4f..2a8b501a44e855 100644 --- a/Lib/argparse.py +++ b/Lib/argparse.py @@ -1911,6 +1911,9 @@ def parse_args(self, args=None, namespace=None): return args def parse_known_args(self, args=None, namespace=None): + return self._parse_known_args2(args, namespace, intermixed=False) + + def _parse_known_args2(self, args, namespace, intermixed): if args is None: # args default to the system args args = _sys.argv[1:] @@ -1937,18 +1940,18 @@ def parse_known_args(self, args=None, namespace=None): # parse the arguments and exit if there are any errors if self.exit_on_error: try: - namespace, args = self._parse_known_args(args, namespace) + namespace, args = self._parse_known_args(args, namespace, intermixed) except ArgumentError as err: self.error(str(err)) else: - namespace, args = self._parse_known_args(args, namespace) + namespace, args = self._parse_known_args(args, namespace, intermixed) if hasattr(namespace, _UNRECOGNIZED_ARGS_ATTR): args.extend(getattr(namespace, _UNRECOGNIZED_ARGS_ATTR)) delattr(namespace, _UNRECOGNIZED_ARGS_ATTR) return namespace, args - def _parse_known_args(self, arg_strings, namespace): + def _parse_known_args(self, arg_strings, namespace, intermixed): # replace arg strings that are file references if self.fromfile_prefix_chars is not None: arg_strings = self._read_args_from_files(arg_strings) @@ -2038,6 +2041,7 @@ def consume_optional(start_index): # if we found no optional action, skip it if action is None: extras.append(arg_strings[start_index]) + extras_pattern.append('O') return start_index + 1 # if there is an explicit argument, try to match the @@ -2073,6 +2077,7 @@ def consume_optional(start_index): sep = '' else: extras.append(char + explicit_arg) + extras_pattern.append('O') stop = start_index + 1 break # if the action expect exactly one argument, we've @@ -2143,6 +2148,7 @@ def consume_positionals(start_index): # consume Positionals and Optionals alternately, until we have # passed the last option string extras = [] + extras_pattern = [] start_index = 0 if option_string_indices: max_option_string_index = max(option_string_indices) @@ -2155,7 +2161,7 @@ def consume_positionals(start_index): index for index in option_string_indices if index >= start_index]) - if start_index != next_option_string_index: + if not intermixed and start_index != next_option_string_index: positionals_end_index = consume_positionals(start_index) # only try to parse the next optional if we didn't consume @@ -2171,16 +2177,35 @@ def consume_positionals(start_index): if start_index not in option_string_indices: strings = arg_strings[start_index:next_option_string_index] extras.extend(strings) + extras_pattern.extend(arg_strings_pattern[start_index:next_option_string_index]) start_index = next_option_string_index # consume the next optional and any arguments for it start_index = consume_optional(start_index) - # consume any positionals following the last Optional - stop_index = consume_positionals(start_index) + if not intermixed: + # consume any positionals following the last Optional + stop_index = consume_positionals(start_index) - # if we didn't consume all the argument strings, there were extras - extras.extend(arg_strings[stop_index:]) + # if we didn't consume all the argument strings, there were extras + extras.extend(arg_strings[stop_index:]) + else: + extras.extend(arg_strings[start_index:]) + extras_pattern.extend(arg_strings_pattern[start_index:]) + extras_pattern = ''.join(extras_pattern) + assert len(extras_pattern) == len(extras) + # consume all positionals + arg_strings = [s for s, c in zip(extras, extras_pattern) if c != 'O'] + arg_strings_pattern = extras_pattern.replace('O', '') + stop_index = consume_positionals(0) + # leave unknown optionals and non-consumed positionals in extras + for i, c in enumerate(extras_pattern): + if not stop_index: + break + if c != 'O': + stop_index -= 1 + extras[i] = None + extras = [s for s in extras if s is not None] # make sure all required actions were present and also convert # action defaults which were not given as arguments @@ -2446,10 +2471,6 @@ def parse_known_intermixed_args(self, args=None, namespace=None): # are then parsed. If the parser definition is incompatible with the # intermixed assumptions (e.g. use of REMAINDER, subparsers) a # TypeError is raised. - # - # positionals are 'deactivated' by setting nargs and default to - # SUPPRESS. This blocks the addition of that positional to the - # namespace positionals = self._get_positional_actions() a = [action for action in positionals @@ -2458,59 +2479,7 @@ def parse_known_intermixed_args(self, args=None, namespace=None): raise TypeError('parse_intermixed_args: positional arg' ' with nargs=%s'%a[0].nargs) - if [action.dest for group in self._mutually_exclusive_groups - for action in group._group_actions if action in positionals]: - raise TypeError('parse_intermixed_args: positional in' - ' mutuallyExclusiveGroup') - - try: - save_usage = self.usage - try: - if self.usage is None: - # capture the full usage for use in error messages - self.usage = self.format_usage()[7:] - for action in positionals: - # deactivate positionals - action.save_nargs = action.nargs - # action.nargs = 0 - action.nargs = SUPPRESS - action.save_default = action.default - action.default = SUPPRESS - namespace, remaining_args = self.parse_known_args(args, - namespace) - for action in positionals: - # remove the empty positional values from namespace - if (hasattr(namespace, action.dest) - and getattr(namespace, action.dest)==[]): - from warnings import warn - warn('Do not expect %s in %s' % (action.dest, namespace)) - delattr(namespace, action.dest) - finally: - # restore nargs and usage before exiting - for action in positionals: - action.nargs = action.save_nargs - action.default = action.save_default - optionals = self._get_optional_actions() - try: - # parse positionals. optionals aren't normally required, but - # they could be, so make sure they aren't. - for action in optionals: - action.save_required = action.required - action.required = False - for group in self._mutually_exclusive_groups: - group.save_required = group.required - group.required = False - namespace, extras = self.parse_known_args(remaining_args, - namespace) - finally: - # restore parser values before exiting - for action in optionals: - action.required = action.save_required - for group in self._mutually_exclusive_groups: - group.required = group.save_required - finally: - self.usage = save_usage - return namespace, extras + return self._parse_known_args2(args, namespace, intermixed=True) # ======================== # Value conversion methods diff --git a/Lib/test/test_argparse.py b/Lib/test/test_argparse.py index d8a4a00292d230..7f3c74be0ce6dd 100644 --- a/Lib/test/test_argparse.py +++ b/Lib/test/test_argparse.py @@ -5897,12 +5897,23 @@ def test_basic(self): # cannot parse the '1,2,3' self.assertEqual(NS(bar='y', cmd='cmd', foo='x', rest=[1]), args) self.assertEqual(["2", "3"], extras) + args, extras = parser.parse_known_intermixed_args(argv) + self.assertEqual(NS(bar='y', cmd='cmd', foo='x', rest=[1, 2, 3]), args) + self.assertEqual([], extras) + # unknown optionals go into extras + argv = 'cmd --foo x --error 1 2 --bar y 3'.split() + args, extras = parser.parse_known_intermixed_args(argv) + self.assertEqual(NS(bar='y', cmd='cmd', foo='x', rest=[1, 2, 3]), args) + self.assertEqual(['--error'], extras) argv = 'cmd --foo x 1 --error 2 --bar y 3'.split() args, extras = parser.parse_known_intermixed_args(argv) - # unknown optionals go into extras - self.assertEqual(NS(bar='y', cmd='cmd', foo='x', rest=[1]), args) - self.assertEqual(['--error', '2', '3'], extras) + self.assertEqual(NS(bar='y', cmd='cmd', foo='x', rest=[1, 2, 3]), args) + self.assertEqual(['--error'], extras) + argv = 'cmd --foo x 1 2 --error --bar y 3'.split() + args, extras = parser.parse_known_intermixed_args(argv) + self.assertEqual(NS(bar='y', cmd='cmd', foo='x', rest=[1, 2, 3]), args) + self.assertEqual(['--error'], extras) # restores attributes that were temporarily changed self.assertIsNone(parser.usage) @@ -5921,28 +5932,45 @@ def test_remainder(self): parser.parse_intermixed_args(argv) self.assertRegex(str(cm.exception), r'\.\.\.') - def test_exclusive(self): - # mutually exclusive group; intermixed works fine - parser = ErrorRaisingArgumentParser(prog='PROG') + def test_required_exclusive(self): + # required mutually exclusive group; intermixed works fine + parser = argparse.ArgumentParser(prog='PROG', exit_on_error=False) group = parser.add_mutually_exclusive_group(required=True) group.add_argument('--foo', action='store_true', help='FOO') group.add_argument('--spam', help='SPAM') parser.add_argument('badger', nargs='*', default='X', help='BADGER') + args = parser.parse_intermixed_args('--foo 1 2'.split()) + self.assertEqual(NS(badger=['1', '2'], foo=True, spam=None), args) args = parser.parse_intermixed_args('1 --foo 2'.split()) self.assertEqual(NS(badger=['1', '2'], foo=True, spam=None), args) - self.assertRaises(ArgumentParserError, parser.parse_intermixed_args, '1 2'.split()) + self.assertRaisesRegex(argparse.ArgumentError, + 'one of the arguments --foo --spam is required', + parser.parse_intermixed_args, '1 2'.split()) self.assertEqual(group.required, True) - def test_exclusive_incompatible(self): - # mutually exclusive group including positional - fail - parser = ErrorRaisingArgumentParser(prog='PROG') + def test_required_exclusive_with_positional(self): + # required mutually exclusive group with positional argument + parser = argparse.ArgumentParser(prog='PROG', exit_on_error=False) group = parser.add_mutually_exclusive_group(required=True) group.add_argument('--foo', action='store_true', help='FOO') group.add_argument('--spam', help='SPAM') group.add_argument('badger', nargs='*', default='X', help='BADGER') - self.assertRaises(TypeError, parser.parse_intermixed_args, []) + args = parser.parse_intermixed_args(['--foo']) + self.assertEqual(NS(foo=True, spam=None, badger='X'), args) + args = parser.parse_intermixed_args(['a', 'b']) + self.assertEqual(NS(foo=False, spam=None, badger=['a', 'b']), args) + self.assertRaisesRegex(argparse.ArgumentError, + 'one of the arguments --foo --spam badger is required', + parser.parse_intermixed_args, []) + self.assertRaisesRegex(argparse.ArgumentError, + 'argument badger: not allowed with argument --foo', + parser.parse_intermixed_args, ['--foo', 'a', 'b']) + self.assertRaisesRegex(argparse.ArgumentError, + 'argument badger: not allowed with argument --foo', + parser.parse_intermixed_args, ['a', '--foo', 'b']) self.assertEqual(group.required, True) + class TestIntermixedMessageContentError(TestCase): # case where Intermixed gives different error message # error is raised by 1st parsing step @@ -5960,7 +5988,7 @@ def test_missing_argument_name_in_message(self): with self.assertRaises(ArgumentParserError) as cm: parser.parse_intermixed_args([]) msg = str(cm.exception) - self.assertNotRegex(msg, 'req_pos') + self.assertRegex(msg, 'req_pos') self.assertRegex(msg, 'req_opt') # ========================== diff --git a/Misc/NEWS.d/next/Library/2024-10-22-13-28-00.gh-issue-125355.zssHm_.rst b/Misc/NEWS.d/next/Library/2024-10-22-13-28-00.gh-issue-125355.zssHm_.rst new file mode 100644 index 00000000000000..fd67f697641d92 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-10-22-13-28-00.gh-issue-125355.zssHm_.rst @@ -0,0 +1,7 @@ +Fix several bugs in :meth:`argparse.ArgumentParser.parse_intermixed_args`. + +* The parser no longer changes temporarily during parsing. +* Default values are not processed twice. +* Required mutually exclusive groups containing positional arguments are now supported. +* The missing arguments report now includes the names of all required optional and positional arguments. +* Unknown options can be intermixed with positional arguments in parse_known_intermixed_args(). From 440ed18e08887b958ad50db1b823e692a747b671 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Wed, 23 Oct 2024 21:34:11 +0200 Subject: [PATCH 095/269] [3.12] gh-124858: fix happy eyeballs refcyles (GH-124859) (#124913) gh-124858: fix happy eyeballs refcyles (GH-124859) (cherry picked from commit c066bf553577d1000e208eb078d9e758c3e41186) Co-authored-by: Thomas Grainger --- Lib/asyncio/base_events.py | 18 ++++++++++++------ Lib/asyncio/staggered.py | 1 + Lib/test/test_asyncio/test_streams.py | 18 ++++++++++++++++++ ...4-10-01-17-12-20.gh-issue-124858.Zy0tvT.rst | 1 + 4 files changed, 32 insertions(+), 6 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-10-01-17-12-20.gh-issue-124858.Zy0tvT.rst diff --git a/Lib/asyncio/base_events.py b/Lib/asyncio/base_events.py index cb037fd472c5aa..3146f7f3f65503 100644 --- a/Lib/asyncio/base_events.py +++ b/Lib/asyncio/base_events.py @@ -17,7 +17,6 @@ import collections.abc import concurrent.futures import errno -import functools import heapq import itertools import os @@ -1106,11 +1105,18 @@ async def create_connection( except OSError: continue else: # using happy eyeballs - sock, _, _ = await staggered.staggered_race( - (functools.partial(self._connect_sock, - exceptions, addrinfo, laddr_infos) - for addrinfo in infos), - happy_eyeballs_delay, loop=self) + sock = (await staggered.staggered_race( + ( + # can't use functools.partial as it keeps a reference + # to exceptions + lambda addrinfo=addrinfo: self._connect_sock( + exceptions, addrinfo, laddr_infos + ) + for addrinfo in infos + ), + happy_eyeballs_delay, + loop=self, + ))[0] # can't use sock, _, _ as it keeks a reference to exceptions if sock is None: exceptions = [exc for sub in exceptions for exc in sub] diff --git a/Lib/asyncio/staggered.py b/Lib/asyncio/staggered.py index 7aafcea4d885eb..0f4df8855a80b9 100644 --- a/Lib/asyncio/staggered.py +++ b/Lib/asyncio/staggered.py @@ -144,6 +144,7 @@ async def run_one_coro(ok_to_start, previous_failed) -> None: raise d.exception() return winner_result, winner_index, exceptions finally: + del exceptions # Make sure no tasks are left running if we leave this function for t in running_tasks: t.cancel() diff --git a/Lib/test/test_asyncio/test_streams.py b/Lib/test/test_asyncio/test_streams.py index 3c8cc5f3649180..686fef8377f4e5 100644 --- a/Lib/test/test_asyncio/test_streams.py +++ b/Lib/test/test_asyncio/test_streams.py @@ -1166,6 +1166,24 @@ async def handle_echo(reader, writer): messages = self._basetest_unhandled_exceptions(handle_echo) self.assertEqual(messages, []) + def test_open_connection_happy_eyeball_refcycles(self): + port = socket_helper.find_unused_port() + async def main(): + exc = None + try: + await asyncio.open_connection( + host="localhost", + port=port, + happy_eyeballs_delay=0.25, + ) + except* OSError as excs: + # can't use assertRaises because that clears frames + exc = excs.exceptions[0] + self.assertIsNotNone(exc) + self.assertListEqual(gc.get_referrers(exc), []) + + asyncio.run(main()) + if __name__ == '__main__': unittest.main() diff --git a/Misc/NEWS.d/next/Library/2024-10-01-17-12-20.gh-issue-124858.Zy0tvT.rst b/Misc/NEWS.d/next/Library/2024-10-01-17-12-20.gh-issue-124858.Zy0tvT.rst new file mode 100644 index 00000000000000..c05d24a7c5aacb --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-10-01-17-12-20.gh-issue-124858.Zy0tvT.rst @@ -0,0 +1 @@ +Fix reference cycles left in tracebacks in :func:`asyncio.open_connection` when used with ``happy_eyeballs_delay`` From 05b11ba059caced565facb95422138968c36c4d2 Mon Sep 17 00:00:00 2001 From: Tian Gao Date: Wed, 23 Oct 2024 15:36:40 -0700 Subject: [PATCH 096/269] =?UTF-8?q?[3.12]=20gh-125884:=20Support=20breakpo?= =?UTF-8?q?int=20on=20functions=20with=20annotations=20(G=E2=80=A6=20(#125?= =?UTF-8?q?903)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [3.12] gh-125884: Support breakpoint on functions with annotations (GH-125892) (cherry picked from commit 8f2c0f7a03b71485b5635cb47c000e4e8ace8800) Co-authored-by: Tian Gao --- Lib/pdb.py | 2 +- Lib/test/test_pdb.py | 36 +++++++++++++++++++ ...-10-23-17-45-40.gh-issue-125884.41E_PD.rst | 1 + 3 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2024-10-23-17-45-40.gh-issue-125884.41E_PD.rst diff --git a/Lib/pdb.py b/Lib/pdb.py index 559517813cf600..1e1b5ea4f0a184 100755 --- a/Lib/pdb.py +++ b/Lib/pdb.py @@ -96,7 +96,7 @@ class Restart(Exception): "post_mortem", "help"] def find_function(funcname, filename): - cre = re.compile(r'def\s+%s\s*[(]' % re.escape(funcname)) + cre = re.compile(r'def\s+%s(\s*\[.+\])?\s*[(]' % re.escape(funcname)) try: fp = tokenize.open(filename) except OSError: diff --git a/Lib/test/test_pdb.py b/Lib/test/test_pdb.py index 1542bb3bee1aea..bd61de0ad3494c 100644 --- a/Lib/test/test_pdb.py +++ b/Lib/test/test_pdb.py @@ -352,6 +352,42 @@ def test_pdb_breakpoint_commands(): 4 """ +def test_pdb_breakpoint_on_annotated_function_def(): + """Test breakpoints on function definitions with annotation. + + >>> def foo[T](): + ... return 0 + + >>> def bar() -> int: + ... return 0 + + >>> def foobar[T]() -> int: + ... return 0 + + >>> reset_Breakpoint() + + >>> def test_function(): + ... import pdb; pdb.Pdb(nosigint=True, readrc=False).set_trace() + ... pass + + >>> with PdbTestInput([ # doctest: +NORMALIZE_WHITESPACE + ... 'break foo', + ... 'break bar', + ... 'break foobar', + ... 'continue', + ... ]): + ... test_function() + > (3)test_function() + -> pass + (Pdb) break foo + Breakpoint 1 at :1 + (Pdb) break bar + Breakpoint 2 at :1 + (Pdb) break foobar + Breakpoint 3 at :1 + (Pdb) continue + """ + def test_pdb_breakpoints_preserved_across_interactive_sessions(): """Breakpoints are remembered between interactive sessions diff --git a/Misc/NEWS.d/next/Library/2024-10-23-17-45-40.gh-issue-125884.41E_PD.rst b/Misc/NEWS.d/next/Library/2024-10-23-17-45-40.gh-issue-125884.41E_PD.rst new file mode 100644 index 00000000000000..684b1f282b143e --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-10-23-17-45-40.gh-issue-125884.41E_PD.rst @@ -0,0 +1 @@ +Fixed the bug for :mod:`pdb` where it can't set breakpoints on functions with certain annotations. From 869119e36d1b1367eff7003b04dfbe2ce7129a20 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Thu, 24 Oct 2024 18:50:16 +0200 Subject: [PATCH 097/269] [3.12] gh-125909: Avoid a redirect when linking to the devguide (GH-125826) (#125930) (cherry picked from commit 5003ad5c5ea508f0dde1b374cd8bc6a481ad5c5d) Co-authored-by: partev --- Doc/tools/templates/indexcontent.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/tools/templates/indexcontent.html b/Doc/tools/templates/indexcontent.html index f2e9fbb0106452..2686f48dad2a95 100644 --- a/Doc/tools/templates/indexcontent.html +++ b/Doc/tools/templates/indexcontent.html @@ -59,7 +59,7 @@

    {{ docstitle|e }}

    - + From 31ff9e5ac685dff58456f7fa93d1872e8cda604f Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Thu, 24 Oct 2024 23:00:45 +0200 Subject: [PATCH 098/269] [3.12] gh-125933: Add ARIA labels to select elements in the version switcher (GH-125934) (#125939) gh-125933: Add ARIA labels to select elements in the version switcher (GH-125934) (cherry picked from commit 1306f33c84b2745aa8af5e3e8f680aa80b836c0e) Co-authored-by: Kerim Kabirov Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> --- Doc/tools/static/rtd_switcher.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Doc/tools/static/rtd_switcher.js b/Doc/tools/static/rtd_switcher.js index f5dc7045a0dbc4..2bf01a002db90c 100644 --- a/Doc/tools/static/rtd_switcher.js +++ b/Doc/tools/static/rtd_switcher.js @@ -7,7 +7,7 @@ document.addEventListener("readthedocs-addons-data-ready", function(event) { const config = event.detail.data() const versionSelect = ` - ${ config.versions.active.map( (version) => ` luDpkzHG=18JfLcqv%AtD1kE7InN3yi9 z>+8qNz2a7ZN3g8X!2Vt*kgp&n#7B?!@z>xIOpQKplSJ>dVAd;j&>0*b4o^}csWgAw_hfXO5y5->(F zGq^75nmZ6gPh3?#EA7J(yDo*8Jq!WR{Z!YB_!-`>d(KxcxQZV%#k-5-6C_r^pB(X) z#JY9w(FGVuITrc$RT?msvtF-tBZ7s1%2n7iXG|Id?{v#dLBr)aQbg&U6nb{N zr)yTTXYN2K#AH&rB&{12lx4nS(j2f5uWFY}&(u_vN4Mbg{ z&&E0@l&@GLex7;PiKYQA2uD@52KLlqUq&&f9+?jQ>_G}I8LFTX2}BU()eIm}yY=BT z1??usuAO8xTVKh%kkH>x1iU}R!&OWJ>ZEJ4E`!a$OgKUP5@4hQOAYE+o6yk{w>l?_ z2rnV8-)%4NX*|q2QbvHsTqOzq%RRRrFmg4B?KM`(C2&grMlMgvh@CSeHQYnv&Nq=2 z(*VKGU;PTNuEJ5w_RWuKJ$`-ojkRYtpmIo2&ZwURZUxNJ?ehYl>i&9A(%lH`f`PR* zw<$Z5LXvQPwLPDvz^=jzyZ=s>kUCeW(?foS&k+);t3A8RqQZa^CmB({XvQlUR)WIe zXLADsn^=LGiTtj;OY-nBVFG=_f!C?wYej9Ltvd{R^HsvIwlZF#0YB|&^2ymzs0)+{ zzhFrZC@dK&0`%}&b3qywm1&7heEP8Kw^yFo-w8a3TyD*zEa>@X@=^=4gTyMEZ;3Lr zZbT+n(8p1U>>~gH{60VEApu?xROg+5J_ID?pEZE(mcZ?`U<-r;RrpU$kL2xr z&O1e4O^*-%Cyh``ZZu=s`3lFI8nCk7)5X)WF23|EGs5BNV1e69uhaFtVXl=fDKCm^ zVwq6+jZD#j_tnLlzda}qwh#8OtTaJd5cG}A7h!|&Kxs!u;k+P6w3a`xrL%2Q2o1N4pnag{K2 zRzBEjG-Ko?;pW2B;8EFtpg2-eL5U4V7^@3!-Y*0O{bPu7r4cUi(*JH^ER&{l+8Yg< zXkFgSxx?Jdjl}=0M7Fb%zD>(WxSllhRHP^m+5q729OC3*pJFd{`bxlSfjF;2ESTCH zo;J*GNx>4-+gDnF{zrc12F5Y+r@<&(aFlCNCMYSuLWxv3Fvkxl|Dk-yi-SCjj1YTz zy7Z$p{ueCpG=ZfxGe+T0lAH@E(5&{bD^~Xi*z->g-xv6cLrl2(y0!oeTwSkk#W zPN<>^{aP=Roz6lszEOLguqJO(g97$)pQFrx_sf=>ZS3z*Mecc5)fL*ktXf{(Dy$rha|j04)WFLB16wi`Qb-#>`cESus#%XHtly#bGUiJ zX;k%9FvZ=rXq~vLnfDc45>Y*T9A2~rU1vL;9iF}p=RMBWRd}1{22R=Y4yivVG&i*q z+D%3p%5Au#p&9yGpW-G9%!H%2f_@*~{t8HBW0 z=oh>1@R^;m0Aki+&F58Q6D3^&b)a80w6TmK;XKGc-=W(dvOgQcW}yy-Rlw&yZW_y zKHkyaI=>13U-`GCl^>e|=Kml%BUCbUME{sP0mMK+`2SOaA*8Tb|HJa|DPy<%$J~1T zgT3(|mS@EV`{F+`)vzmeCei=6JfwSec%1-2 zd&2Rbcq9WqV$0^NEHzTy8%n~b0M>LeDJs->bsgT$<-7n>75jbXPtBthcz;g2+gm!c zK?5mXt#q&TnYFudN?oq^k49dTFI1DqylWs;j8MfoZj;q&_Tn&w(1^+N&1Wy3vg;yR z!!7 zusBizG;w|bc<`U|VY{P|tDq+^0YTWHnc{Ha=8QW)vALeBcYUdf?+?|ZZoP7dk2XUq zmxk~6ms=MakEYyzU0jgA-p?J`y}dnHVUsPWA@?MhZ9)F$w~;}? z=*f_Vx?#<73aPd1rQ*?Tm$xi9Vb(h0KXzwyd%E8NE}q0CZNR4*8o;#AAqbPOT*R{% zYJPqegr}7Hq3xqs`kUZAl%*>{+UoZ(2xpkcE~C4LOP@cmHi$^*tI3jce*=HjlwhW; zEIepDT6(%NcXfyPczU~j+bQ%FZpG%L7OpICr~%T>EvkLylm)m>TosO(9XRfFF1_4I zvwiCTKi4k@6YTrU!#~im-JWkOo=(p2FP-~>+qgcePtT7@Jof?pjl#5_$drXLdqS<(9!e;2q6n-*n@i;xU7_*coDBk3hM z2IyT250f79mr0)=ufB|l7o)E3hFsGFznC#qEH8;DGyV~$U*j$44Wefvo=hJ+x-?&`u@@(+7E1n z^?g>}d2zWb(=ol^UsN+@oGg@i(eIWu)zrpW!jPc(Zn$Wl?CI#>ODMhQ9D$RUI}AF& zsC}AU4k7#?ObrYK$0Vh6?fXzgLE@>? znp)Q&y!h62*W@lSFYz+5>srnOrSxEc!&Wx%3RTA!k{jTm(&<8! z#QeW0eTVxD(fmR9y1;B3%-+8Mi{jCFQDQZfQVfZI=o|ZA{X{U&si5a_;|dYurRS-1 z)jI7Z8)I~Er1pJFhijHSFt=LnKxZLS>%qcYCReOh+XQ;8VhkBq%Ys14!%BhX(Vo3< z>sQ9u%g97IecU|!QNR|(e>?Drg33I2nK8c0x(P*xtyQdDR>j#D{_CUxe0})rFTxuz z3lfMwxrPK7Z)aZUI3RC_PW7^bNdN@}R7`Iq*P1|18lfB-k?t}u-8OKA#K~xhpg}#5 zeS`S(HV2l_q`y+%v>U~1<7BcaOo~!Ioj*lE?jw*e`r90`t1DNUPxDeyd?Y-G!PA(2 zMdkrl=27MDHWMt^IChi)BELQS8>E4L#7ocxQmn9~HIaiUh0XmP5Z0Ty`<!)*v-(IFRJ<5ElC~CwJw# z>sRsPAObC!SYZNtP)>9njRa?dHq~aKjwostR5LK%&xFz}MJ`hTX2yfk4Dn9VQ~sZR z=eYl%HDn=j%l5lRu8^A0uD@168o?&XQ5(qCM6~b&3`yX)h@djT$dsaRFkFQH90VZE z9$Lcc*59v`c>5&i6>f7OX!Ay*7x*DDa5l#Tn7 zDmzOH;;8x?49)@;fT!pJ9o%-3oHyfwlKt((NkVbvIJV9;h{OXse1d?w!K1dCb><$Slu`D;(zB1jH+$o{si$`!VeS(8ULJc8(WE}D5*JrL^(_6AD z7<751&pwrjU%QG{2^Uc$hFQ0+#D%-YQIH(dTE8f)!yLjdz=FTp-)Yt(_T*%BTCPl( z-V*m&dw&~*!`dqh-D7H-BMf!$un;CfMZ$}@#>-9!p~}6=-dHbR5h6awJ}i^*Hw3qF zvw4NJ7BFOrwLW z76eM?U(}6}lawM1Gc!&rT|JOm(yz-{<`0(vC2s`ul?{djmSYqnA;vV4F?fUg(TK-rNUF?X^oYm^#RAAYd zv>_7ofT9$}6c^xxAd*Wm2>-|4xmq^lzTbVo92^{W8^3cIO!p+=B*=X7;g5KxvZXN4 zdBwKGfX-ec3zJTx$7kk%DVU0YxlS%UxjVjNPA`wFJ@L0Dr%i8c^B*cA&Zh9(2qTLS zy2u(+w2jU($z_y5*U^5fPb?+<8T75Mj6Mo=1dK4H?=@H_=}#hK@wjFZ^<@lKKv*I1 zY2dNikTR;-F#r_juc+Z zEMR~C{fPe7k=0W$Q}$~2qKY}1MTQNIX_=d#cHK$uxy8qV9FjEYi<91!wxcabD z0Q72nK0k&Kf7m|mi#SpaP^Ze_i2mJZcRUY8uiD~nf;3Gk@P_=vn(VoevR2$ z;a?J@qulVUcmnfO$!j3vkoI=uK0h1vM#~dJ6Eo!_S6C5;i676(jGV+ppAmyKF|X6@ zwCs%6t^Lrn&UuOV;=jonNkC09d=bKy2iyX+Li7&y=be?!gr~POP7JkI{TAn#y^43> zzm=D_6jDN`(KZkGTS^w$Sm~m+MN;3B`yh-V zGJEPrnD(s!1X)XJ7hRB?8AIT9Xk-sI=}GGxs+|Fb>FU>tra`pFwVcXwk2#CS39#SB z8@19HZK6wxEVMdG!kctN&K*drhS*wv(0b)cJortx`+hWnX1(U@c;4K;`a#ERVV^YR zKo7S#%_~<+mB4)=R7u?)^XJ7Ha@ZYJAbFZ{eh+A`L^kl&Iiv6~Q2gzXU75;5cz)3) zmd1g^BzuF3#1u6x&>q@ww#44Q7O?K%aUP`rAN4V6b1O#in0v=Z2)eZi^{$zUl~?6Z zuWQg6Z3LafT0e%ljoZ?3@*~IVy-VK#-Zk72P3&pc$zl8QFG6sE-G`VLh;YVMPFVEH z^3e}Z{{n**OyGutc)kG&!uZizaol;}Eq4SVk}vMgTMz^6^_uYI4>%C^DnOE~*0swb zD9S)Sn$|I~%YZN^Z=^y0@9Duoi`3FNQ^|$|jYkB}ma9Q#YQi%6I*YJo+a~^j1_k{k zH?P+##g*&^^SJoz-?M1Nq@>{o54dOV;*}U899=wk2z~f5=Y9VE`%BFI@{B7EHPUMY!=7 z(d)a_oL2Uz0BR3xs3xnvAXw&MT}3+59+9dez2-X=VS{{ z?iGnt!nu&z*@V0xeU_4pn4KZ>yraz}wNz|+ZVzRC! z9tE3pI+=GC3xyN`r-2ySgs`Jn;=&v*6a4(cR~_?YMMr2s+;pxsH&;rTPdi5XVZ(k1 z^T}pbV5Wg5mTNOM3PsN7S+y_v2mSlG%BJ4biphV;6b|@y5I|Bi&qPAZIkYyHQY+(v zvw*Olz5IR;1Ww4Bb;0zAjXCOK6bDmhpaFtdc+Zc4t(kO>N04Y&~J6rdl*;g5Ql|XluJVa_ub9{6Jw)nc{V{Jgky(Uk-8PL*2OOUhz~xL zE9tXJJWJ_U1&D>LK4jW~V5fAgd{RJlogWV;6)|qIA-UnSXexH`EH)yD%eXsUrO?=Q zSNWrFc`9#}mPn-8Ygh*dY@x4dN7wEDCDEpFy-$7%z1*90a5D1`4V8KeBip+F{8qeP zJQMMB|FM8sU{+M=<%ygfywiS9QusS8_w;84kgV532*Cfj-NSW%E%8}7e2uf9%Eg7@ z>BzZWMqA3qm0p)51E<($SB44<3PObzy|B9?dWz@ctg4STEj*2MdZ9X@n;yyMGbznteDJ?(_xEtTr0qKiVg)XTOZbt$0Zlgl&|>=Qe>9*lWf+5Ytxy& z91n48@?MppS9%BH;|$`1y7U z0O+sB!RMHlpFP#XZm@B*O6)@4Ih|;^ zD1)Ji;~xfv8J2{GHg)4NY3c~Y!1Ml=N$??DcjZQzG{V8U-Y_p0(zUT5Y9>${gUQdE z<(SYmB%X%*_JcBH%itEgT=Dl=(R1%3fP%3tOMbkid<5{CR%W&s8G6^Scor71h5p;P zP0$&JnbPxjcro;7rUYC{@YMnnxu$ss6HJa8!LR*kgCS4hq?4#};c|9a8r5|oJNr`* z=9FcO^JFKG2t3V~5I$50O(h95a4eZIoA{x!-dTY~{@!HTX@V&xwg<6Al6pmSfUpcQ z1;p>tY7h!WDsqZ^GJm4uL#TrOl3V{Bkdp}*$4Aci@+@O~b%?0H%hyMjejyFcCl+?K zt%8OOx*elAcCYtFQe!CC!Sxp;Q$DbERzv6b%XO8n(Pp&$#x~Dde>~|HJjwg9CH<>` z{4a#U)YoOoV^=|2w)e^~@5RMc0k`7N7|Bo?U>NYSK_;Xhl|0~&vJ!1fiNQYDFw8J9 zb3@^pYE?>EXCVJXev+ojzNbXK>Nh}AA^}I zPkC>1fN`G!O}$Ko#62{c-`@%n4v+4RR!MuPSNdPc1Sz^hS;|ADH8k)py{+T)l|gH{ z3fTt<8Y1>@zYQpg=H8o=*}dDS>F+JoOt#ua`>MXU@r-iXP$c0|7sCCiP}tR*11T}w zG1|kQ;5AL;>ywi%+zmI30j46F?3_ggDs1)P#!}eZvn2T)P;E&Ml_~#TOW1TTWP6Pv zRhi;|7%SW>AzT6-bVb*|QGT9wnmTfJH;Cdf-9z5Vu69DcE2s%njx9lM=QzVvubuS4 zi_sl?=?#~oi***4fNB@zLffpuQ5!P83!s+%I#3{3$L}BVVvX zBRThrh)M{IV0t~f4)&s+LA6M_W?%jPcZgw|j#3X3?|jg;_bkq7gDySMrxJ&=*{(OF zDpx5MbEU9SF3YcH)K&CPkD0NEaWKQr?}9>g&RrcoqZDa{-%qrd6H_bP!c=QKa{T*C88_mjNMi({ ze?T!!8lmtxIo1)le%RYG^&(&a+21T(-MlfyD=TU%fGK>-4euY*hF`sLxsWYOv8vCu z)^;!HcH8nZjC$o_A^B*%%_)@Ini)Y@F*l{u$4-_X-FK7vFQ&4&cq+AgjYP2!)(P;C zRYCGJE7c{#{vE}?Gx4YjFRJr+6#3m+9jDjJo9Ek-%eho7mZ?~r9%ym4X7ALz1Y$+2 zo%ra901wL)zrvkEltK0hKOpi{@fiS2AzzlscUgp$}=Je^#NufG*4|5HkFDO2S z!N30)qP@whRmiOU1SV0OuV4y58afGJTb(^9-ds>_?E1ZI+mX*90Oo^d%qnCL6O8V> z{>W1AL@?$+LTn3W(Wq|@CIZ8Uk(T+)Aoz(B0YvYllSMrwF~{fznOUOLCMXh;IkOqr z#mw*5-&HYqAW|qC)=JF=W&}JtXQruK!#sNPmJE_MP!oR*pF*kCC4jc6ipRuZ4o>H~ z)tAybL?ZMj&VpfmuF1jCFshJaI?=X)#3qmFWRB;k0e!~Y$oCWas#ag|G`y!2>B4#b zV8i~uG6N@5%<=WVm;yG@6heC(N`Ur-(?M(8PjwGST!n2Rom>nd&)D0vTT#+4D^=d+ znfuLUvZMkclz1fNLgC-ZNmIvmJ=enlKw=)I7x#PG)y#x~1#_q0PoTS#5ne<2=G|rN zL7^lRY8RcQSUE`^?Pa2c*F5-NUSks`G-l7PLq<+nDaYlTYZLU0ENS%P(*V4Yw6VyP zxo@k-ikr1q3TtgY-q20mD@@(koUDI zeH36T`;WMiqSHvqxq+0AkU4xdS3-I5fti?sl99fx!@cjjv2L7A{!KB&m=}eYfsYMn z-fv|$4b_W;h1qGD%86bc!j7)U+nw|*4Q}h zuv!tF9KrD72}{uX4CqVEtAEBLRR@+0)qJ?JvjU*MAY0xv?EYh1^uAZ)6<8ggO(@6*p_njY==G}|Z$pbSb7qswN=3ity zY9Q(l^(v$-zX5FG(ArzKD0v400Q52>bJgYfUrH6Z7FFILaGjZ11Mwa<2o8p3kH=K$6i^m`T{|qi3_Qc#i>6C1&|!6O(#n=!AHsK5Rlx{f1{PmrA)en^jE0bw1ma&!SuZwYMGP z^m`^o3;-o+AwvbeHX37oC6y@hkjV|6yX7KMijrhfjfj_$4>NygZN>_7=uissZVI;7z86? z7A0S+I3yTfo;XfMEPb8OHmXN$jkdU5?p_)DP(Z=)t08?j*KL@;KNgLU5(de3-S*Se zsWDq1;KNTQl%NjpRo~t3J}azzK2dQ{gRYZ+ccZS)-0}0^7WFNEFMi-=?XTEiC;m4R zuOJ)`FkkVXe>!)8Efp%Wj31jCB_0z}ml%c5kpL)BtuUo|S@_fSDGBIr~q0R!`B^3tiWNo1l31p>-|@82_7-?*b!U}wIGIrpN1yP$|0Uwm<)%C~d!Bf9(|qGy0113-?( zHI;3YF+~N{c@jw$B$lI{=bk&&x9Ll!x%7*j;>pPT3KY2!!l{trlVkqthFP z-Fvx-JN*QkTJIkm3eM&p;p{upsfwa5DfgkX;-osw&2aINe{l!}rNpZwhQ#sMq?Aqj zJjI{Ru=FbQwP@!sg)GRl8-OZDcF1VqXYw7vn@F4G2!Bvs#W-SAE%jOqN1Q&Um}Noe zs|j9jWED54wUJ~=p8n8jy2Jy$$(f8Hs)vP_>ukTx{Kg_wJ5G4yEg3|f=a4I$P5?7Ky>1iRlD>Y% z`3I;UUgd+r<65eGApDtff@v20=>$`6C_eqL(56^@MOzuwHstcmVE066UsAHrY=TRP zl`w{t&RtqhrHy|-eFB2dSeC*Yr9x~O%{tp3BtoE1wf`7*>9fk*rH=*$K9OZ)K%>VA zKPsIPp(O{9=&(wlLvTG-Z}QZpy1?vNBa%!3t-lPgK~7vzlP?e9>X3KjYbMf4saB$et(l)x?ddbt%d38V=Dk)moDe!hJ%ZYl2ha@6DCzye~_d%>1B=l<|!!eagATL#%jxjoR<>ft?4jY2M{|t|wKr)RpJca_$Kx*oi zaYOngUHqk@dBZw4(=*>uHV+_+*s$##timr(Zyu{E{*q+}syd-=MYuTYo{AtaT0m?h zWE1Bh=N<|i-~i-<`xXq~zX)0SIT_zARYN=yMzyk8v%bb<#JF9;SkcTPnBWAp;GO5+ zUNH3lH^p}ZQopr4dyOkXUF7~u;nP3=dsTz_Bx}h z$$6m7I^*sZk$4kkp|IvegPwW#gOjFOFU31frITp8Tmm9LIUBQ7+QHH z`o{@|lYm-fR;7qs=k3WtT}X5Uk;y}b&<-RYt{g-CDkysU0_`jgcq`%hL^kf-dUE7l zxKGb+ZrDm8UWP50hN<=P0G$2xL*gUZG#--n8`&>PI{=kzS!opIi z3@jC(^sJNbT>~~Ei5U<_z3zBe`sG}HkV|OoN2{0-x@<(jwY*I9RR#AON%c`x1+3^= zlyebvDR5G>L|Kf~EqoPoC&*g+jAn|t<4=LgydVaK#iOu|7*M94^--8PF5jO^Cs>uAv2MQx%Sa^s8=(-qcY%^A4w5ZJ10y0!!s){6NVUZhk_l3EK{5sjOZg6x6K}7XM<2q2245R6`ltKNJ(rjQs z`XEHhI2r2q2-* zwbul*7@*+HJkL_4wM*4cjRE2m-rj)yD+?kj4?oC1Wm`l+D6y)oW{E*5K&xX*IU=05 zvWFE!NC~Daw1Z8Z3dtf%fV$8b3VzNE6PM-Wi+dcy9|T`jAE!htUurM=+oa*QW#&v= z53JM$7(41id0mz3`!>8k{?JJ52%xL${srfP+C56sjZMxuzpuYchR6Fuw_&y_Hfs0hJ-vD{xn-1oKzU4@b4CH6=@bWFuGuw#p@xemHT*BUMLv&|;gn;O-f zGynNVJQ-r|S=&|%`%SV>fE}YV5vvOQjdi##H=}VQ(EuI9!E7vI$tz(#0}yD05^Fck zw-4xXY_Y^W$TVUH(|i-w^6_QzEv=Z3O!E8oA>DHdsdY6bU;51Mks3i0ScH-ibj zh}>MF^Wy`4B{YP@MTMc4<39(P9HN<_OwfGgS(<8bZyc&V6>V+RZ%yQp0s5 zj=vdKoMG;#`SlS)yBy&1uG7o#Zn?8^2sC96j{>6oI?CV`Xte-;URCMyksLNxk-Y5- zaKDs2!#sxZ#N@l@c=43)4|sTF!5pxgD&Em>I%QT<6YlnO1)NRdMuw{c5n!1fZA@zI z10_<+b3m`}Y*sy*$0CVAT{8rX<;FgTDraEPK)5c?D@08r6H@PQ47<`!Y4m&Ondn47}tA}L3 zRcj^w>-cjE1~BJuT@oS}VyX>ow6GdNny8C@vqZG8c2KH!L;ov2(zFC@y&**%UT+3c z@FnH8J=}X^;{gaX0YKQwx(WECyw85#5 zxd;>xGht`-PH*(CpK}}$RJ}`joxEO=5a1D)nO}*u1SkOE>pu`6`5Pyet>kwbgo#pS z9)BbF8-V5A*7b9D@ap==z{DU&m`B!Qelf>q<=^vDjo5rgw<24)yd+PER$C7NZw&;* z1ZgeNIwJ}-`;xEn^ykLq#H19vc>eFoLG8tod}nn^Rq`5^`cRAuWshwBfQu@b1sa93 z3s|)(7$BEpRRw(zqKrN!b3V+om}4$eqAH{JY6(lMzpQQXitXb=L@bOhv!vG}kX+){ zT}sQyiC2)XdLHHTHyB`1-g(vnZSzw)hW{-eLDr)IMw`Ngy68B@5lAc6HaYCp3)P=@ zR}wnT@@Y;hzesx=&lbYtYAm8X-wqF1Jt_@%`6u>E>K3(@NfHqV559O%yLK4# zFW`?+Gwa=cM?5|vr1Q0L_B}W^)V?adn1idpdAZmX@cPWyW0yx8yOP4CPt}3m!+382 zSK0vu`jun9cbvDUC-gSP^1vyo@`tR-D3g|dG7gLCMhF+F$U%g35@$C9c*@!OqE)g$ zMOVZ70&15`o*X!ct8t#qM1PIYusk(b7og6kEhU(+&l{YX9UPYx>sN7&ydf&a9a~Xe z6d|dUka@5fX#<`YskjG1Q`Ghtdx<&`^*@di9FEK=^1>j+T{D6ROHdw69Z13?hHky2 z27u%otUCMHkEQ~2PoBPuA@ic=DZ7NvKS(MyAojYdMbuCgpLmUUJDFux6J_*1{WgHW`#NE(S%o@iXb16ppC2kyb*lW9?VYcy z*m>*d1K0JDA7(;Z*n^86C`NgtKH$#3*zVpymtQ$P-&f8}6jYB97D0HZH98zSUtLNR zsT}5vUe<3v7_1b*bt-msJcwQ4F^-PZD~z6W;*Z_W;{Ns+_YnzEil-1%2!fAGP}_jT z7^RLxpG)pxQLK%1ciz%;-NYAYO4GBa!)px*iaVagF`tYTymKA*_<{>Y8UP4Ih!geRtP)UiVL1YRIPsNc65^)hY@SU% ziq5pKVT80cm-F`tIs1Y|azY|ywUxQEe9qjbMim%M0WRL$fUVrHi7}XKBape$!(@u^ zEOlnqewE^&Nx0h~wvlIab!uT@U~t*Eu9gWk^+iM*w|1?Vg}c}D9Ked>8iMy8Lfmmc zH%D^CqX`n8`y$W@S{Ax~XtL4{g2q0!#!^ee_WGAw{a{%PG0ktypgC(C8R-pfAqRP*(4m<} zDX+hH#RdMh#%mF|UZ9lx80Vt}+AoS_zovgc{@=!V%X=^m^MC2{jIlUE|4X(0!boL3{+B-AN|~Y{heOzsm5THJUwCsy zA&w&I{~YZ%EjVwG0Pue?)DW53tqSQ(fZj4hm;=`Czc z?HGkrRU{RDD>FE|JKLno+0l-<1iyUIdUENW?oV?DGj}NNpooQ$I#wTV6G4SvqI3+J zV(M(kZBx|OW?`)jLV*~g5=Cemvx_tN9)txDJx_|W3MQxL0AfGmdF@=3-7<}d?>b8| zRF_mw4){vmMiQzjPTnRE6Z&$t-o;p$WRw{yHVXs9n6#A10yM6r8>A>r4TM=Zc>!N% zce^g!-T1PQ2hNTT-mclH8czj?0`IR9WBRr;)*2oC6akZ!^97r&jw@pewUwP$gR0b}|7&1b7M0c&&zNITsh+aRzhaDN#1T$XtC zENY~cnSb;9y*_kjKrt@6p1Kn5Mubt4n^~1HTss#R}>?3i!kstn0EIx zAFX8=0vHwUlc8;5HXdU|wBK!-oM+OD)l-YIVLo3gX!x3kx@ZqpZ9H)*j~43@;3G^8 ze#OYl^Z*` z%m+SfX%!0>)86C9#C#8Fag1J$-VbMUT+6W;OXK&4eVMq}rnN<9-)i*FsM>39@pko{ z&{#^nF7sM?hTQ8#iuL;|!(h+cix>~Ea$^`%>5u6;E>6lT*0M=Peyje=^o>hIrY7Z8 z0FAc6MNMU$I#`CTvB`dmR`~823`Dt6_y`

    B)!DlVs#jyfesv&)w-I0m&Ih%s<2A zA*MPV<)%G4P0iq}`00u)H)(rJrb@b!qBhJ56-v#f+wr>vT2>#gbnaGS1T?@W5tZ`3f-GCI?o(tQ|Dq{PMD=gX$V z=D`J-s5clRjSd4gC00p~y%JckpdGo1Q$W-+f6ifRUN~wuc_is_VsCH{Fone;X4h`` zW9@I`byS}$F|$_Qe|ZUtD|SnJJ1o*Oy6T(JM1A5xDhuWHa4hE-dbD?F+a0OE2{bQ+ z(B1^LHC4*%q#==N0I$Y4uXRy=?fK`xYfdH5m~Mn$MYLc*{{jcMx4W?d|2}Dnj5~r9 zBL#5~2OV%^=q!5QZ&L|d9MaP49Mf~&N5)lo9;;eGPP<3zE^MI2J6{PMIbe8H@JbHJ zWH{kyR&Lz<#BU^}!SZuja0s=KX0c%9a^_`-{0ErE(C55ru6oV3t1y zAd%geg|~HB$|R?qay5v916b-Z{M}p_Xq+9A?3FZ0cr5U}7XwIT(o)}#rYF|)jXhqF zQ6Pzo`Hi(d{#j~npG%t4bVrEX8io8d+VF6O4;)+o0p*fLX$Z$s$gwmDN!(+KKDcPf zxYPU>Ym~UT)2(@w+Zl6{#O$lDRgBxXGre#i>jOV^S5R6iOSohm7C;s-0=Vli)@9`X z3@x!@qP&l**)ZOYClxc0lpPT@s#md79tqbR8ANE#{g4!}(7rXkCy;k+5Tmd~hX{#f zW5$(0a#jsV&o?Jud&K*@c(;4U8|&;WO|cWPydLo^-;AeB?N+hX`(WZ zT2dRo@l2}eSpFrlMsq9EXkwdoxNkq zMQ`|@UTf81MEdUW8O)SroF*A=+w5wSylzvkD@vcHf?Zx6#O09DZ5uyX4XtB(d?B!R zqtIu7%lEeWr`eVT8n*&&qY{cART!;d6@CoT*5&fD-wJaw66or$?6j-jF6P_|BMNxH zfM$Sq=wgNUa?}*gef@K)_|p%~LWaHFxI3~$4qz9Q#dS;Km_e4JcyT~&&W97wGk^DC z*R!nQ=~$jovR-k%il|x+X#JF&jIT@9QM* zMD^sr*#yxq7tdpR*nB1?C0D;*prue$t9Z?~E!|M;A?R+uSd- zKQqp@8)M}GUi$5Bi=4(6o?k$Bm~51)>Il+B%oCDzGYCCNh{~^FAP4^q_dyDs9lI#z zOg$Z`Kt#8?FSUzy99Z9Oj#3AjFQHrq2Ur+rU^vz2ZSJY2^VecZ}%6N^4(NG z@oik3ux>ohfEJvofmym{=cuaYobG(sN^}3@+?w=^r%-2JK8R5r1Eci6d5#@gIPO$~ zSQ-KJ@-ouc43u|yAIut-3QdKUvw=4B)#Tf|*AiF4ys1VB6s#1}{BAt{W<#uy$e_4< zgJGG1Bg7N4n;vFdIvER*pzHb|{<~CIcYfNG01Ba&a!8tK= zOWIDES>c=<_Og!yrbMJ(y@Q;l9t!i9Zj`y)+N4bieN#hTGv zQ$1t}Le1q7v!MJIF!y<8q7_J9{PvOr5|(!0aOfbvD?ZZgil&7qAH|eVKkpzSx*D*7 ziI<}{<;i{jK7)uX9s>&aJHT4e$?yO_)3yH+h`6m}g)yG@dcV6&Gou}^=4=e3qQJ|x zxRI$2?nbBdeEg4l3X19~b<^|8iM2|AK zprszKeH`x{&z{QX)Rv?v;;~q*IMlT2)Zu&-jowp-_f-^81s(w)V#d-Z;?AJlXcj3T zy<=0Y6@H6H_95{4g(JKV?px`T$X&z$Ok3Bhx%Nb`c37SQ?UpXSZ2kf2l5TP`eW zQ^P{#unjFk%R!E;+n^4&53-`~R}~vGE&T9&YuPsYCEr z#QCeFbzXQv8F31bbil>t!9Zyd+oi&o&ywz~u%;KobXixBA5bkb?C(A=5IX~aPS~Hp64Fs8C3U%q^0(et z{8K!f(XJ(2KxiIc<64_M^(~KTJYJ;!x_^la&!a|us|~D7G32b*ma~m!$cz7{Zk4sd z^R24tVvBV!&;~VvQh23_=62h_fa6mJ6Wyk6?qEwBzM4LI8kJ3`^S4Gk#>p6x)!45$ zTUUxRbD%kZS26qm_YkLm|7EsPcqaITM=fOV#z1Q@Q_mnN?6>lU-5_6$bQ2A?KutM9 zu%Rm`dXb$*M06<>Z8o}Ez}?ie~q*WxXx8X1+(?=&&g6wGNHVgTb{ zqYyJCv)etkcSVAzXj7aRZ$IH zGB<+{q2r^6D0pR7HDcnxJDCdLIbgi8xhw%4xh2oG|ICz|wXU*Wq(k^#>vIY|rnH-C zc^yz3kkc!rVts{9Nxzs93~B|@Wvf0K{tP8Zzi=_P?d#sEQlj_=rTt>(i<`&mgDJ6U zcb@ypDX(?^&fusZ4n_vusF>Io6}>WeYOC9j@K1jFSDa5N^->u`J(rNrAmF>N9edeVci%chzY} zoBP(*rATT~jTanJi~|;4=X(xknfMD{q4BE30xrA_33^Yr&gQ|-M--A0|H^QsI17>q zR@PC#=QU;_N$UxRnr0yMIUowM+8XR4X7G0Opk6b-xA2;~L`bPc)72+&H7MRDu9HiD z=q@Zf*Q8qG7e|G=S)f{Qxq#ojR$HVZ*uf94CxVbmbTZ^lTgfLfcVLsjL$yxTvE)v1 zTQP=V)o>kZ_PFG?y&V2qlbWJY7gn=OwFFEuOqtBMOtXY70(~)i3m{;uWNeBjGVUew z#j6Q}o%bIzJ9kBGAC9NJ(enIqPP{T<>v|8(Z2Xf4k}}A1%WU>D_Vukx@S_-{9KvU# z6xwlT_mG`b#qDqjGG()}G*H(F(qv!iqo0>$t>5>f;865+S6gO2!&0_>qvuZC*7c0l zEEkQ9?ZF$@%iHs=2+;7ywl*7xjdGW4DSUmGv^H+Hj9>XV->+ojrlH`~WsGmMfJT;4 zuXw@nH?K$$Hj8^-+4HHX><0nCQAYS^noIqUa3~TF`i3p{aa1g9baQ3v#r?qLa;aaT zV?TxV+Eq+}p7@n!tK<0w%teOfMB6-TL)vJpu4Um3?jPLxzJNLv0k*|*p_ppzrx#m* z1uD~9R-^+3&38&z`)sZHrt9x_o-jt%@YL{|LdI!g$k1In zwJ21R7Fl{iKFVu(JEihxYImzV)Nph7pxzzY5yW2vTwT&S4=)n`G`w9_pxA8J2+W-M z*@!DLcXeiE7+}|k6+VS}qGOm8u_j;p{3GwlrAX0ZJ`nOnHPZezCNdDi&Rm%z{i*$3 zaGA}pB(0Jf3ts8w6JEQbW3af1LZhMb582ETq|=IvP?%KnatGElDO6dpa*H(liNmTw zcmi->Iw1YW&WP5(SqYy0$fu zbt&|@+m@i03Zam|@ibV3m&S%<-94M)ZM=JcI7skhZ|<%>aR#|@SDR;W5zL0xY2A8w ztjwX}Tcwwj!^QE#OTN7^1{%QrM@8&cr77M04@mb4G_?9|nQpNJ_ltU&8cfKyBk|jrspiKSe z(o^E{HzEP`br|1*dy&p;N4zmgMnZ~jQKe>?M4%G6j*rxoqMH^+UZN!R7Z1_&+w)e+ zIRGwcM@d>(CYKhqBNvGy%BLFosLyb}%NHCZ*VZPj%Tc6asA6>heNZ*K(X94@kembf#Iv+Z@D~Jp0pDzDe&0l3wpieb?sMQ_4-|zTb_H zajsUESc{L|A_1B^M#%GMU+N`{I`$Dt6rlKYpNh_Ad4!_PklDtYl)GL?-KFRIp`cin zSd)RhBd;$*d3W-IA`RqmBm1$P)h10|TYNb2M6q-aD%zQXz>Gqhx1bEiu8ko=MiYaBr{lf1YrqA88tp&U8_lz6gug51h zKCic}g_<1Sg#5SX5`f6vLdg!V58&yMP)@HQw5m+6`-3B`u@yFAwj-=rgX!RP=yZRo zWT0;g#6&_ok3xgah%`UDj*~=_sin=ngT4mH)>v$LdgVfFnlm1vlaGJujOe7Q0K zColXtPF!$dlYK1lWmNxUt|D!vW?qTV6o;QgIaWSFt0nv;>FWY0Km9aNj<6Ld*`o}0 z3d#ECZlsch>6MYkpeFg{KBpw$UcPt)juO z#R_R}63+n`dz5U%n6VyN*;LmRVX8 z$}>qe*+z%Y7lC>Yi249xa0b|k2VRCO_!d1!*%a(3Pudj6t&n6I0+>gUUQ~PhNZ7lm zrGNhMD|j#3=?=NI>>}y%%FA~zknQZ}6EWOm=e@~Od-rUw!Cy##yBXg;m_z3-_*=|p zReUCM=;>8SgqQyA^=nav;MF)n;_kM{Rxw+qCfy{T(EWEVOL+0+y3T;Wdc95j%+G>^ zF~o!!>EH0OIRF4q13=iq<@>fgk&)2iQfvIO5->-!4C5%}5WI4ZjEuM+qt0KO^SGL( z^O4fIO!MuW*=aj3)dl5V<-1WN>)vxyQ_!sFCQhHia)@g+=(}@Bx87eDKQJ9PtU8tv zwRKh^eS4^WOTP0Zty7G@R;K+A+De$y?fb@)cU19sc7xROCV5>0^d~1?{s5jm?|fEO ze-13v^t_+B0c2R-ut85N4gvp%!SbK+e1IyA|3B=D2>Aa$o=@4U#l>iOUBQX?LGZto z*DGX=VE-U6Itc$Vdj4yWpOU+dg9+FaMf$qdV^ri*wyfJCvsqV{(e&j169zsgP~rQR z$7_1K7RcFg5s+|yz}0YZV2vPHO^vc|`<#|$ww>~PDomUi`YmWwWXt59))~qhio2Ekk*}>s;sawP{r0(hBB*Y@L@>3Xdf=_{gJs0gR$;sU5P3=R!kOK;S2z)8nqJ+ zV+#XvaWXJ23zp}GzzO(EDYuh5-C7Y>U1Tb=HP?T1J;9*ZjJwT+P1e;7UVT22a5BG< z9@BMm^NsDQudK%V^OR;<*(4VivRbHBi5JR_tb%2^4X#3qRr63Y49_^5-TNOawwGs6 z9=eUYW8lF`Q_m$H%D!^oS_2eeX0#2~cdKLy!cdmSgM@`P1@Te|CPNL(h$nU_L;ZiF z=}adsq@@sy$>3r-*6yIAWZ-FB?(&wBS`&(hW=c>P{hWO4 z-+~Ds&3FD1qf`-Ks2Xk#b+HiEar%`hugp1)d?2K8334P?<2vMpi3@02SD||~F$-ho zlInahaErt2i0ItZF<#-2yHMTp5xW~ZT56 zItV*{bGsWVFFM=3oi1{#tFz@{+$SwN-8bg^^TEI+y}31z@i`H3u<(%96Xs4d^cniF zIoxtj=I0137Y1!=%0XzPgjJE1_uuR5+qXY9^wUj1 zfiO`@huM<$A!P?y;QmAiP>Mkte|48x2~K-mZ~WxkxU!6T7P_*6j=XxPgfX~i5hGGQ z-5hpONNk-N%B9i{g2O%s{#K+<_*!Q6st6R4ye!xp7IO7_JOqe3@r4r{nsONmjptDn zLgYiQe6nfX{9##9^Sy!SL46T(i;einiQAm9V0HRQ5*Re+GTW?T46z+2IER)C-FuNJ z>+U9M1_N>Cy#G_cZ~pCPAlSN`JY3d;F|`jD%jCHyuLhbA{3)YbDp`}Eq%H~g+FRf=ThxXW?t0K$N zZ1I!eq^n3YA&cMGN7yB{;Ap_S-49*Fr7!$M$KrXE_o@;`r33Qtsw!7R_&Q#S{mU*W zKx~Mhv5V_*g8SvEFF<@wYoOj+SAi&Mx#_CF$VSYcEfIj$51|P;e@rG#4YkEmx@=H@ zCo@9{mTtwSoPGGJSW$FBO;v#QtNT@{{lK<+m_h3z2RruS?>0`sB7*7_$5iG51PM<& z9LdGo>Jshj(C9nlbp5Q5ofn(UC|}20;?&i}_5<|Y&n^(0p%2-x{X01{e8;HM;ErM9 zp(RFg>{ftd!lTT0)e~{5QZto}W5UlIQ3?W&eXs4fI;+JT_my~*Z>%CZ`S6Th_O(kW z0{PBv$q)1Pb<2}J@jEqR;iE{i-inigYnNL84$gnI&Htl+s)>t;NJI27!VECLLCoru z%&x*QNl5;B`B9|xi#Sc^r4f`ALW98Zy}RYpkD8|QsyMBLWgZNM-XOb;_zl^#>F&r^ zRLThl(laB7JEOC`;5uh=55FqcXx59{-f|JBA-%bOM-xKq=A6o{#VWad7+|+ zkes4$W7kM>#dve=T8$ZU%k95VcK&`C=s@IIgIxi4`n$)V|%1kB>V2{q}L zraHky7bUk}CC0h~H1#kf<*ev2tgw!PZ$LeQWiufNtCXOsvU zdWl#D@P3(jH#$6GI_g(?sM3pG4H0avKwl2Q&)s}*_41rS&ww`Sj%HVhCQ!edE$V0$?5 z^dP15$qk-$9ylnGHJGmxq|tO*c_=G#rqA~w7PQ9^Kt zG^cuwFHEfJ*8LpDyi!Ixj-+l9%~J_QnROgQ#@Td-lI?m(=Nwxodmia5(`$7VIEh3s zz2cy!^cYZD2D}zWv$=2YO-^TCpLo@3mp3VsRZN@Wcbq~K)jOa< zqq)yWR>-o~hJOu}O1Aw^PNvXhj*8<049B(DD64n&ZtNEyJ)gbJ=C-w5Ul-eZX|Dt= zW^*)PY>Md){HSkil>C?jBF)zPDXAc#k>DgmUwV>M1SMy66v&*$H z`A%1p4@YIGzb!Mkph!yXP~?wugmY-XDQs?@L2l=vZiuf{bLxfv)HI%PUk)b&$Tt!1 zwNM;!G5q=X{~6o*i!CQ!WSXN*qN#YUZ{$$-FyaRt9_mBp%=5$js|XnLY`DP@*TOKJSTs5CtiCR6i#mbI$|-kv#;KXVd;&SD6%r3|)Vcj~BFf1!{Tk+klK*o3eKAF5bJZ2Q z`yB8D(J#RHJiH%LPQ_qzebPevC={bc#}Cv?ZTzx zw~s1Ij9(|<(E`J{LiH176u>zQ5@ra&T5S z@nS&a2Gt^;(Ux;2yBSFefCj`jPKg)#aKq`=&gre4k$O6_yUEs9XUP(`)CkCP$*>IV z&@&E=e+E(7rE!iOBT($R?c54KJ3JfA0Cgr!lFH!R0|-s)A22z&*{6TyZxMTpa+bD| zxZ;wTt#K2>8RZz;m_L++_+;5pNRsuB_zV`|;j;RR4}>Nb-(i&iirDK)w++d3YqWC- z*wlXdXzmz1j##V~;|pf0zb-TSc^RZ|ERQRwJ_hR+*iX+5ye4iJ!M~5NFOAQb zel@@(t0avO_X)}2Pg%}6^|_?#1vAhVG>Aob0xQVc<{`rp_4{ws1P~%4n7H~Xf&o48 zL3kzA<9RD+d1SBuk+o)J`@naBQ4j^!M5*_t`;8t5NWt`Yl4|s+DGZvZ1X&;+)&To0 zXKh?smLNCHFrhh}PeZ--Hibam?G^KF9|E+>EV}O!!p1!4ryd1Ul8g_7aRhK|Y&mz4 z39S~qOM&6f1mj#Iop$8-636&zbjLo>5!@G?2EViw6>}@GOt%@cz~3}QSZ#wr=n$LM z;a1}TTp60q#^g?!2zAXi5BJM=qN%SvkdebU=lxy%c#`}0aIu}boW~Db6IKMHnpuOx z5FLPo*GTU^FVtvbO$@9T*G1^qkf35I*6UGjYiy-ohh|ma4EZO-{bM!@Q(^;r=UcI? z6R@g>Wu$DZhkr9zIK#8mRlhz**(f!h7|+N6bw^jhnblzKyq8dL9|~8+Z|@|iSFeJD zP13s-x;)pKwcA1joQN?6KEl8T8REq*MR8k#g(*5EKA6p#SYQyYR7&M@`>}Jo-dTPq z88Fcbrf(hD(~tWk&qRoFDRTVb3_&c^CzY z@*+;x2Up@yVCu`@}jX5qjgu(%Ge2HLtl_c)NL@F#{1qU}?v=X!;f zh_cpAWN6A(fh_kzLr#Uz=- z=430zA09=EHziKEtB&PrB--+aCN*_-`Bj_SG@a(PM0LaPK4!6Y4dV}9#!4#S4yDZO zU$C*slYTX|j|=#dgT>26_$?(T+j$}nIw&Dur)9=H2$tCyJi>nbqRz-ntWoMYcpBi`hQj@e*El*z1xj7r zNkcxV^J(6QV1XB_gzKs15E8Ic{+s@BkMSegwtKaY70137x0SP-<)4+2h4Fw(*nTut z5x)zY97e>Dd5WM}-A)PkJ|X z2J^eZhdN(__=+*5QBZsx_S+g=!O{=U(@oS=8IzaFG#_T)Z@5^1n$1Ft34yofQ-;N{ zf>TWp12Vk8?HLW?oqaSp@&Ve>rHrDx)h?I8A33fkx`Uiy_u#v)iY#~s00m@{aVqYW zY$hEmY@%L*nB_VpM4KL5^YFQCAGYM0qv*L^J|@^>Zb>H_>sOGj82-T zQFv&1FzHerY%{k%e~(DeQZi36;S^jw=H%$bb=7+1*t)dLiPA4t1f&k2`Uxzw8{)ZF z&PY}Lz)n>!p>^vJ`fI}nuwQ2-u|qmSO?xS0^B&g0SYQR93}mS23n99enZq9S?h^Uk zB_hb@SJ1h`9mM`rQ*C1?3W*U2#W<}@Wi`(V7G=$=zgYyCcrj$q5D{kUpSnxn$|35F z`mu9bct!*GP{BOUQ2Ss)&@7oq+|77BtH!7Jsqetid~f*=G1S0vZL8o%d6PDPAlkFk z@V&cJ@HPi1i!pXOaavrFTPcI2$=+O`o@;n98uymdC}j4~>#YS~h;(~Q14?@Y6ZM)M zl-5gXJLz7B9B}~YQ*h)%V6g#pTIM}d9~qx-GWgAvU7ksZOZ3zN zim|EOIW_GQQDZQ3G;#bY-n)?c}KR8GT^8@6@x8yw6zRTtU+Jf>xHF-GGk#|H$oWJGo%`nWPwlwBp5#-s*@8PsH}q|fDHtS*Qm)Nav&Ah$@IZ0;5Q&6NvTT5M_O<|-fA_??-o2OoujMZuI{$^NGuWHx=B8`-55M)@18&*Xbu6&3^lW!BHQ2fvePOx z7ub7Oj9tCGpv6lk-aSbb>@;)Vc=iSV_R*Ty74@A7Z{h0Nf20l$Ct&D>!*Bt9RXMwO zkS$P7Z+1o8!M=8Jdj!tCI+9gc-Mar)KwU@^R8(UlSt(H18SLdwcGONU!?HUU5V*;B z7W`6WXka*ZaIbwk|C9F4t(*(8+0yCaidD42b2sf!QXSmXROp5l){}9J`DnVQ*#j`y zNv~)u9a_8-DlJ-;sElR;9a09k-ff6v(^m?@7`$SYuX%GO*liy|d|fT$g{-fVu3FKU%W`Yy~6{#)kTry~5!{ir_~ zA$7>SP~eT-;u)VjB{DH!cC7f5=@Q4C<%}e9eXJ0jVpIA3IdV77@iZ_pA#NOf5_`O^ ze3VpQ9s_BMWDKDUw+OlVS=>DDkykp(r#f}~TK0YLvrDU~&rd)4&-^n_1bvL}&Lg(Q zWFG96dnKwB&{0PXo#7vTbEP@D65kRKDCm% zp%MTw5<@H^)k6M`k@d|m+wv3-+R0(E1n+n4g)npu`8aAwe*MROd`_sKZsche3L7DE z4*|3%i=_wdfpYS_kSR&VIEH63CzV;-!CUE)75tKCI~#gLDP|7fjyQ!aIERJhx+5t{ z82r+qy~7v1ju*Tja0E6NM5vijCUk^#e>X`DmOj1u$GG4LrtkFErZw$HRWs0_(hanh z(V>XIG~5OQsX-Pesow*6U%@?XG$7rpJRmY)bUNqk58DV&gN|9E$#(?8#lGrOFWb`S@G0tGx;PTonkeVu(%a zmTT(8x|s+LvFaLWPX6Thjo z^Stl>tor-SC;Kr2(&NhvaEK4xJw|Z~Bbw&eW3NJ=yLO}iCFS6}!^p?ttafbB*#PUb zmY{#RLV7ZZ<$EMN=lL#@yJs5Ru)SOZANgf_N{|i+kjcUyIoF-VqY`amo@KgFA)49c z2W1MfT^nsd@SidIP}+5G`k8trsN_{%VJq+7&e{64-S#d$&UR;WXZT`yry?>lxrNYz z2LZlQb$617@@?Uw3(xp36xmK!{&aQg^kDFHY)amDD5eo-7mXH2K07W$XB&Zh9WAJt!zyjKKBEaAYH96Te)CGir}6 zNBI^aAn-`)+l-(~!Ye1_wrKbQU3_7Qe3?I+qweW_ebwh@V`tFoAzJEewC}Kur19At zUzi9jzw-b5eO}!VkznV7`fd*EsoMcu%!>3)F(z=WUpaQ6m-O{S={l*nL}C@a@~=M! zi_GLF)K4F8bi2THD?Sj>v(ukO`Mz=9JTwh_bj<4dPjysWjEXr)-n=U-p3UZa$9Hbh zERxfj61j0mdnbpLuwp&XH?_kA;~D3P(SqClu^j@{b%#mbXF28NK#p2qV9p14j`^u> zbPgb{x=%0;JOfky&MM9roA0pai;49|i|Cnq%W0R#vRnI7#B)L~aCh0{AqskP(SHYg zl}KLlc(HW5>Oa*wV#8X=H86`jWI1g!Q%KddkmExx1pdsNEN3wwHz{O4`d0n?<`K*+ z-qzAVJ2YIfkrvCIgRSW!^`!)0Oo|v7LYvQ6$5v=9h~ywBwLl1*wv4|L+Tyb*==Ov= zufPL+Cox4j*T#u4X}9=xFhHHkJI#EsLkC%VLA7wAd*0f4rN&GX-^4Z!^=uN9OvMfq z`=feil~RIwHSwX9Rvh}}Ym$jnTT+(;d0nGU)~;F{`|KW>UGWJ9u+#uGCxk6&KRFwr zCq5+6Eu}cO5nG)ov{YL$jS zaDqOHpUJvnndhx0mG>Qr7FV~CcDuBF9v5do`_Ph1nL1o?mnIdnI?EE%6G~O(rwva& z2vA%MJLHOq>vA~@u%`juRcF88eSla#X=PInBc$RFWcFj#Pe$ZnTp=`>upp6_Vt>Q* zy%Iyul9PsPpnYe%rrL}2hWzz8()=l|F}9x7S7|`_=vYj9w`gZ~3)&$SE%+qvzKjdy z9A1`@zcRFr1}12+MG(R)iVb=Q6)18HMk z;Xi%Wzg*heLh&HbepM|%OjS>|9o)v_ZX!Gp8i4HZGoy`L>IG$`@yfySO4)r|vwE}g zVPkT`yeDHfWfWR*^Z3!SK##rkF3Vokjkji!;SyxIOvJ@L#l8Otbd+BQAV73#mX9x41Q zSr;(FTk?xcR(MsjQ9hSG9Cq}ltCw;TO#4~1U^lDi_pvPC$qBOOTJK{O^G!bYDakoO zyC+NaS9)_`9XbIoy*l$g=C;CK$j7arI1{n9R7 zDKo;w)QD^Zb%x{>jK7kd{3YZF`^hKA>(PoAK4&)4-Y&~$(bTg$$I7V8q7J37uws%r z8gi}n-oND&pH;h8+P5nFUG~?m-^jUC-ZTWrl$)0>-@QUdf>c+YCS>BIdzXJ5piorv z^oh?X5EeUpQU_+P@SNFeY+?gcciBR*cZD26bv!}SMV;w;ExlHf;ja;!8QoUd*&hRc zex1M9DB8z9F)n(y@yP5-h-yg(^g4xZgBjaxw?PB~6gpA}sIcract*New|LSlzHsu0 zjBpjjqJU(|jn(QgS;rMO7k9q!G-sO^nO;B09WjmuHwDOqJ39_2>%!md&z-(qL~tM6 zr&XcZ9Zu;gn(Ygv#byp>ofewudXwu5tqfs95Q}KiuLR@Ce(|L)ddE#Ms--} zg(XfDHCV`FAuBj41oO3HvaueY27r5p(IyLEX+!Y5m0-N6r>(+^M_f}xwJ4TEw-$kg--qm{Ah zH8=&r$%;|*3nT(;IHLtx<&d6P->lY8jVZZ`-V@n_k_gXH@*Cf~as9S?-+|-vQIAhR z`0)U3n}`=Dy0mi$>Pt`U!1+?B-LzLA;uZCjm((QMo2{Gi^wLk}LpuCD53TKlb2OHc zk!C1x!4EhfY0qamKgHz1Wm%jX$H>wZj~>W@7x`kOaE87NBv?$2UQbYUlSd zEJd!=R4;A0?%-rQ504i{%r3EkaydqV4n(eXvl&SE#%loV^A$7{?Kl?EgPjg@+$WVX zHFQMV`w7H(=%!b61ySeqH`jYjLqbR}9HU*!r@p1Un%$YQ1~Ve|WRh|+ma72z_Z;yP zJvwvVlK=KuT}A}hVZ~gJ@}KI?81k&vTO@wqmizSg=xKg@#~%CkBit=49_h&*fz$pS zs$b-nR$uDpq;Br(ONdfw32@zD9?H6s9^<~zYFRZvivE!P?%rPO4H1^K>@>v!Nsl#Z zCu#ia5L*P^R=Rn;?bWaPVwBJ!HR1<-@qfCu zU{WJBm(-?p?YzSF(A#~YYb2Js6p%WOquBqo%=POqcUOhlo|+`QZ2ocAKtomiWueGJ z+Qo(LTF6`c!>qb^Fv|pBGqLEc)eFH}AQ^h%#eCE^6{oP6-0%C7U2i(X;L}jQ3(L}a z_$mO47;6d==E1#0DeO|#9eOK%+FU^ZubJvBBM;G zi~FReb-hAb7aRt#XgQ)#S-_7~oLBOZX2~wG@oJt%o3^idk4=&g6*I}8gE}DWOVT@$ z_$cBcIExRjt~~NRlP>i~ZkN8)J=kI$P>MNG3sB`D&Q-rmd(8V*w(0SI-?!o!9S-@_ zw)Z|gHMZ0p`;A?l;2nELx1g;_;`m;PcY}Fy{82xBYIX^DCh%jWk0TC3G<9sO;-4IH@>;EP;JLJm_(OcEUqD3n{q%fUDLL!Me-8{ zJYwX7nWGobH$%fsa{E2_{E{PyG^(ZE$CfdWJ=`|wq*^_=m_PcmPcmxA&Z+;r_1j{( zwg76MmgEI8yufcqtwukS#hlQe7EbsELrG4;X@Z|M@&fob^;FPJGzBDl1LC}X3WH*F z2DUR!udM8b!x+e)kc)&jP-?*va%WE_GNl6>9I^qBdIWhG8GM|fFhddMoq0|Z?^aDk zOUi-7?^3`H;ZpX;iYfl zZa4X-mu%zwSuV7-bInsIZ-7ESd>P7gaz+f`7jo^%S!*l!mCZF=9@IH*lO69LLJnkG zyj;pM&K&|u%<-T}E!!>Dc6Zr;tmppy`rWJ1QjOMvFW#P*lFchA#i=E_R>`zmWQ6tP>fa zpfk?Yf;Gb%Am3>Q{hYHMT(kI{%{NoUXRFq6Av4-eCkkOn1G8U6Ci?+c&GC-t{p9@Iufi)9ONan4>pOM#drK-ICqT2nKIZ0`!XtXs=J<_l1(7w{oK*gwtHX=M^3b2^bIwk6`otuE342& zdzH_gFOEGu18KzCc^rS}JwgTc&}1xd-k#p-?Z z?&Gh*73g(24vJlmtQe3iw+{coZW5`1Tj%YxfbgFem4FTyh1w;>8hfhD<{WxG0?i{F zt+~$`rG;YJ`f_I$Yf&Zr!JYh>tWyQ?3;wdOMNy$OHCmMPwNp#VN0 zsYcPEY0p`PPA%L({eU|R@|5!#x;T2$op@`G*!|{~TOfG59Qzwj&3oDdlY>J=O|_u9 zX?|2(-rd0?Fj#~Bp`Re8Dx)rQRQ}DN!S_XOw^e%Ev!m09N=M;@j1R+%s)ZxTaJMX$ ze#^0ED`buoBDO<4UAr1^XbQVc;*WWcLCiPanXMb(*&#qH@1o`iMG*lX$q6YI+U3`X zbkZeu2&##bB!aL#syJ;+2nBNKZ|OantqhP@3qlH-CnUe5;PY|-YkE=&vqL8=PSlCWx4g7rQwr+q{dFHZ@HvsJecV&c z$P;!GxReH24V44HgLrK=h9*O>!p8zPPLdwbpCRuH%Q5{VrimL`D8xhom-G8FKTNp0 z)!Z9g2s4a+Ubr+A7ZakP4XJMpBfLLm`1dP*gFW-j065K+$QltTDagenyx;D-`-T2o z?Ak%~(1oP`mdY+_LTcnQ$j&8~H;DLnascTO574n7-dg}1lIa}#nm|WNUbI`-_LKG? zY;TwKLnt9)yP|^kuKk7*Av#37ZRv)s?(1S8`~^qUNT&P)szod=cS>7r-aM}>lE+Kg zqVFo)wi=ok(BG|Bz%dtRMCT%rv(Zo3HIe$-AoE7FC%w#b6tms==w=A}4xCA08_h}H z_adpon-u`GNFsz1fnsuCq=!5qkz#dVd7P7DQ$vxYD5?*Cw%fvY+3a0J&Bd_)eX|BY zPb=l3l(Llpc}S!MZRFL_cTqUnYqJ8)@_%c&EeK+X?2sfkKaQL{b(*P7kBHU7;qcv; zz(Bkn9Y%|flP<&o2QD&uArUbmB|*H>dG9eDAqM~qVV}64TGl>BWRCDqMO{V?NUnHc zo9{sByl}#Xt1!&UKp6IS2&oZTzrJ38|GUc~`|Abq&{h+^WVrl?C&Bek>wORkxDtn( zANk8$0|9HyBPe5;KWtplp!UA|Z67eJhdJWTLhL6E8PG7d!Gwx=8WFj26Rj1cpCZun z45tA5O0A@2Zr@MiRUfq;=zZkaA^NFYovwWsuZ9j-CQ=d* zs;4q0jgvb#>#bg6Qx@C-+6rYv%sV4B#sLZ{^EWmX9>q;|CgZ@kFQ$T=#vl#>nQOfg z$2&_`2E0f6(VJ<9pjzO$J}PG!%g7NwL5h~VWZbCSu3dOMqT%pn%&+LBk@;*Gf+hfr ziDH5h8uG6eoNBi_RbkSC%{Agx#Le39=Gg;QL(cGJ%{e6muu?D$Ad!lErC3}`Ka&u3Sj6`co*1O2m zvITbOlEM-AjYtBuT-W?g} z>)m`%0M@RM=W5uBICLt9i6GQ@dSq zyZX(0;x_WlQQ>#&$Oz`psmpSgqYaG5dyvv)7xXe@MZS=zIc1bw>4Xn}x1yYFLd+(l z;1k)|6eO3Gut(@vqzEa60%7o zGgPv(=k20pmvTt9q7c>9A{9v~Wh9cB$_P;kS^wvrbFOb!zyEoCz4Sew_w#u^`}sW2 zxz42(G3ebVX?w5K{1U$Zi2!q>PTA~{f&909(O%ZaZ?U#ehLt-D)1e3BLT?X^;0K237*`8k`WqYyuX|d<{NZAlU!(D3agShao`7oHy*?k|#(4gS z3_O+NW$jm!Ls8Q+UE0xvXOL8?hkboZsI&t2)+#N zsk6&sJZo8rY4Pc$o7A(ZKB%0&sd7QRI6Jm&uRX=?RJ}W=c04J(t8YKTvWqbyEk!%U zYSDKogi1J6kIt{7B}3@_d=kzv0fh(9jRLrQ{1W|nws|eWe5$)iclB$GUG$09_m(Iw|wC1M*G7#=Z~s>ubT0X-jq~J znzBC+r=2I3e!W+Ec$JIa-i+1t*B;AZ2%kfHQY*J@^g-9!qNs=auWe?3a1`~Y&NhbP zi$otqU1bigQSeeps;EtPDxI=7f>-EBCT^gvVOFSO@)aiH*F%~X{o?uZNKFOaSkWu2 z{t3T!ggn%UW4P#H&*GXal6XYf$5~)86C3?$zRI(`?382m+#7sC?)_xz+tV*f?RWjK z&)nz4&s%M4I%3pXaCb?F{ z|Ee`RZ(&>OLCY6whiNY>Ege3s4h*cB;T-%HPRDRb~Tv)aGv!JRU&&`%uc*!Vf))VQO}OWhIp*z zt3_tH&=_al6Ta8_?mFkSD4GeT0!Do3{X8Z{&T+@lv#)K-m!v+P5ujjO8@26SE&K7u zWKF2Q&s_7y#S^qbfq|}C<#$ifU&T@3J&#Xl?vU0l%+eZk54;z1b8UqOyZlR;!sUu- zx!(QI2U>drtKuW{Ezi94`E%NDX+~pfMDLlyM>P+Pp3B$oaG~fl{MeiLWu?%#^D|Rg z@-`M>`qKiu&kqDt>p!5$!GATCo#pmQ+T%+1=jr`bfuUNrCK2J??*%E(GbFsxc#g}; zHIzIOTf)!wtTMdd#{p4>;OTY&eEd6>oRy;|zbhme<%#~f(H^CmXO!Cg@_}Z2bk2>W zg1M_j&4)7EbJN8Jk_F?|JXB+wScJICR5tg;A27Yi_=DB>%C{2QN1p<_*u~z&A1xpJ zPOo<~qWQy9;XgK?f1X)$i)lIX^);1giVQB6NA_7Lzp6=&?N*IrNeqpjle0#ZD??61 z95^|+h|3e4zkN+w`^{t5k&4KZ*WNDdcbAT8;Ows#%q%%@zIf{J7sq_DKWB|!cTPOQ zK2lr07gRWeZw~tY&gneM14=0e_53dsn>!xe&OE9WQkxVov4zzs-{0vGHc>6ox#kP^ z%VFHrr5j)5q!L4RcijURqi~dcK2#rYHT{LYg)wfk*?~E6`MKR zvjuHdj<+U@Lo7)@YP7#Rpa}VWF0kfV^!CrEzIA3Wha>8Bx;l&H#2Mu^{hVR}z<&a-J9*aBp@~cMQt*Z~0r>URl*yKMt zR<*gn;drOwkXh612CXc^TqpZ$&#gF@HF9DgTSwMkcN6@^qAD zn7M{JuAe*YEsSsx$2w_@XWw_TEq&mMf5b_@N`cel>)UN|PorDo^frNMGY($c#1xA* zmCxak8iAZA7<7@rNK#<@q=|lJSAA=*3gn#xIn2NDdh90p~wc1To2pEqq5b8aF8R3XR0%^_1tYw5uSakPga5o zw4$x(eBbUny=P*ZwSD*X_q&eVzb_f`=&4BXmkRl_k?B5m&!V+&F2rA0iD(<=T$W-y zuUb{-oI1J(L$BwDYxxnu@pfi&Wb29NKepF1Y#xbtz_i@1xI8l!EuS51+Gsz7ZKoVM zew)^vdFu87&8e$NVw5Z{LyE@s|+@LUS#q3WILxG(Hl+j~5t zd9>==lN30UiM18!ct$E|QH=!OlN3_hw%7P?ofX)@K{q1zHQYw(_p=)&zC1aFx&!xS zJ~l0Mdr5W@n)dlZZp=23)^c&epI%iMuwQ=|K}#o>CQc`knbY)(domD8MH=a_Tba_87f8@^lo)!2*mgn?z8gQ@rW%%ZV-)d)+Hw)fT zw`D6glQPjiAZ?n;8LqLJo92aHsr~t{5tC~-2iGEmK0y!G6r4)eaA0{D{#^PzY88^H z?2NC=kq_t<;$moMmfk*<$kB1?OVAfai6`R86L*a!KWk1F+)IqeKLftj;q0pw2|)_2 zAz9i<*4b+^F&C6ix7oF|_m?*R6s^e;59f7daU}6X!||w%)vHAc@zJa#Y#&>N_w0+pJrL<@BJD&7OVbWEOkp^rZD^ z$YiqBC^(1pYT}SysOacouIWsa?0npSYUx~Kw}u@Phnprd?N8>~BbRVqF_`8mbaWB)xbndk}f7>Ct?^x`MmZVOLZ@&3dZY}Fb|2W`8>lQIC>wYX$X4kt-SzVj?W+g&>_O$$}{>VRc)DQPlT==%; z6|KD~4){APjoqPob`NZxB&k%K9Uhp%mhI5cZCV;tdn=rO@m}7JOX1#{d=dTfb-b+o z+wSXWK4Mrj>M$DC9F#tK$lLXShbx6eX4}!*i^iP}kCU6LEE*H6GFEmzc;rU7Kop@wb+qU|5Q5AI_eS-BqzQuRZu!xi6p7mr#9uYUONi{wpJ! z9ca=*`}BPoW5V(trwi4;Y)*)JVWM@c%fC@hP4rK}nE-PckqgB!)8i!*W#NTxI2o~N z;lw!YbK_jLF^iKJlRVF&$K5Z4FQ2dJb2{ziF2ZhyO*VL9%_@HL%Cmdra$L`~&E^&@ zo6Ey*21c+j)>-Ymr~T`dt?%wKp4O}#7@0j8=Qb-$TaBa@Z=D{fIuf&}Qsw&2r1WU^&lRo)oymLd{`m)&ACTo4nq%a2z8CK%%$CkJ(zM{w_o%`{_l#!cbD3& zD=`AM+~pezU5qk9+gt2iuijV4e=r%fu$nWlRMpaQ=}+7Ko~N7A4(N&g_=PWEDLj&a z8|nFQQs#N3+))h0Z{LOGAGIvM|CEnt=bxk+3VoQ@Bya5S$fuE^s8^1AhPE@bDwyN@7MOO?OG zvg_}a*3XZhFGoecirHRTo_)NtY-NATy@9PbliNdLC-*TQ=%l+KES!B|I)Gu>H9t<_ z-uqWl)d@ehnT$r>nJx7?J*{#6Q+xYqmKR0uLfNkC)$V)#vzBMbLPG4AGs~6XyqXePwebIo9;gmC~UJOxAo6y!M*RoV`*3x-45NgoO(5Xh6D^@m9@}gkiO}A7%0<`xHvlelPwsx#67F(qbyJRXVHccu-$H#lz5N ziS-9MeB7+EH6-7kkp2`sYcW<8_DAPJ>4XWFbk+_j+?jm_2Jz7?B2r3mj&Bi=(F+HQxQfvip9eJ74k5J!>ZCqi#$-o5*Z%{%6_< zoU>SemJ6;mX3Xt=Aj`@yJj-FurgJ)%isx(fS0{|` z^Wb0T-`Zp0*x>m5U68lOFfVo4Bhlf6;SR}Ymjj+xCvisU%ZmL{^Y<)%ec4vzGGe15 zmtE2ErNYxZN~0#)P@tqaUFuUr-iM)N)yN=xyTC8!)d*#r!J{g@51BWtR);!&6%?cu z?=GBeDlVEk(JoWQ@!GA%Er3Of|G>wmJyDqpNj>u}--QlWCcYTvdVTx?cbu|X-_qyv z{U^E->FFKb;&R{6@IN}uHKZBEUC)l|Hy=1Lv-B-VWv?-B-0d)mnkP3ocb>IuDa|`K zb;ql0I%;UGa!R9@-ym~pbtUt#;DGEc{VjXL@3hm>4JUB!9qV=}8=scGK31Rez(g)s z*_d5I^i#r6yq!$4Y#T*)$JLbLKgJnz4&3HWX6LkT&532=lB3!UKaCw?J@s{OQE=qh zT`c;8-KHJSn`<6&_o-j^$}CB$vvBE52*`g^X=Kc+^@8{AwbRzsMIWy`Wud25oD^o; zq#n8Jn_~1ey#H9+y;Sky*6wB##V%T{(o?S5W_}_nl3o(QjdfIZ9J!1sox>BDoA}NY z-trl##)ifmIm_fZV75EkteuL6Gsimbh3!xMgUefg-AYM$NE4(L z%^9ud%tP7#^S9A#94~#Ct3K^oMCNLZGjTbqGs1T$9u<=?*CBt&rX&36o#uBju^{s>UbUYHhFw; zoXu^FDo)b*o54k10<-e56D15u38gHb?Uo9;`}|*9Qc>+;JbCPx{lhC_+Y2W+t|}g3 z7j`_H5c%eI%6v1`7E5HHtTQ zMrLV@GQqL$r(6;t85h4aWBo`hvwwByLYSdilcKTl;#1n6*?T67Y_F(L47_yZ?MSQB zTEvSCJ)iZ7dxbT2aAT-l^5l;Hl{@BieM?%a$Fy{M$l|S}Pd$UT<_Vgwdx9KZ-rXVQ z#V%I0hif8Rjdz$w!YJe#`>*j#DUW2jX)VXd0nwuZ>Nf<2 zD%-uSIeUilR`lXFJ>%{z+~4~GpknpH;ii4>oP>)9XyYugIRn&PCg-=xItu5UE!23!5}@R2Zc@gJPD+Fz35 zYq|xJI1ktA9C&7<*({6W3&|unnkMhLr@dl$=RLPSMXW~x{UM5uyR23jM)rN3JSk6R zlMh?z_uk1K$;5wrNxPS=?W^gU7sb*q1MXJI3(Yk%%a|Ygr^I&2ZQ7}Ri8q&F37ehd zFhXe&k@c?eyU*a<@0)Mq6733EquL}a=)vP=L&;xm61^|d2lfR$IMq`p z*7sudse-Mz+w9@{n9EdGOmJoLea3NA0zxyq$0}YNX0ga}jCi-B{yx^(bnna!>gPL$ zeqnBTXz822K9BtpE!2`13}*?bC7H(x09^UzX(Zga47nd-s9r4qCkY{4Qz}TqE7}Ll>_7 z?3bo#H~C)ZX-b#9`As>J$it`B+t)e>$%?o47b|wV-!abSjB5GP|(7#QHd7 zv+e!0)7Q3cGt^-gEsJWipW^-D-p+N%dGsxzENJb$4}FW0>wUqSUE#&vrnN_pUjC&5 z$w+>Pxoa$~c!%#YhoaB{Tf$X^!57iDaOS)7iaW%!z6fzjr#4)?e}sMe*2A)lYE9E2 z-(M^D-S)~iJFa%~>k@zCf&~A>XJduEHR^*k!kU|k_(UFxHW;KhZvT`1oX|Lh2LqElG1*&UntvX1*K)Y5VOF0)7p5lVm5-aHJRQK5mDGQ#?~b=!HQV;?$0{LQ@oDYB ze4eOLw~9ZSt&QRjkG>KQiZg#M=F9ZFK0?6dJa?00|92NT3FF(*4TCfXnLK)~?iBnf z>iz6_fSgR9SWfc8!1I!M&3}I9cE_(oKb?;6?yzq9)K(r-$HPw-#!KO-UtP7o z&F?BZ=UH6i9o0WCL?Zuex{!Z*z@K(uHNC(6Q}mqr2`gLhDYUSX+_KP(ko66&<7RqQ|JcTaZD-BhQq`_4lGM~W%K_KlU@cpDg;p|~a3$Xu#r z$zv0n`myvAgt7R0-piZkO*H>_Uzjw-ChZS7+f?D#yBlZjMSGd!Yi{h6Q69gnjBlk9 zr!M8H_`) zm%S=1Tg{ub>x;Fp5pD`D^wKIV`Z987ac+u3tL(VBnEL1b5k~o1FG$vq55I~Hwt2ij zp~Qc5t|TE2*B~gglh3@*Y9}ST$h5+9b}N%bH@T0!RI_C<1;T%RPyb|(RngmgvHZr~ z)S~N-PAd~^>iXLnZM8TLoP};WP+I3ujZx(pMFu(yHfZUbuR0iATgMYp{Os1tMa#rC zvlh9e`8RPM=?5m*q*YFoKKY2W~=j40U&heD2 z>PNlKFvjQFJMhG4^{nt7(CyYWpDj`!T-=ZQbTjGDrmmcMLG_@CiAP06wB?7HKKiA7 zwW>|8^G-cL|AC=PEKEIl|5fMspm#fdr^yyo-F-Apx6km{lC*gEi$ZxQ>w-rdkK0Ej zmMpXMY1hl_PxWwfw5g9j{9b!9kmEV%e0e^z56_h-K*1DukRt4(qD{CC-kkTrI>lU8D z{b>|*?TfrmvQEYnCCO3@#*KUz-M>;9U3;Zv%0WFr)P?^?c1`0|zZ17YT@!DxpH<3b z>}y_<^`Uih&@^QnsL#=v%~`B9H+j(Xa?g>WH zfgA_5^SBI|ng&mttjFW3rn?@Ru6;{eIC@1{>eD@+>}}D%jW%t1`BEFdr+)0ZCR3fG zJgvsAT|6v5ette$TzO$R%(-8OaeI?qdsFw%kdRj{je3vXY*I75YSYv<%KF**76t2t z>3Ox^H}j3YiU=oe9&)NJ)lsDGzx(ukbhp`PnnKELpF>{E5MJf~*!1qQ3Rn$0C zu@5*ulc4e2Tw}2?|KejOpM~V_w(ScBoVqmq?V)9R)#^62J=Nmtmsd$DYTWzE-k9pC zj%jrJm&R9I-_DujxpS+7v(qx1Q+mVp(%1{7yUNaSwd(xd@-6qA4QI>&)m6cT+UzHB zH@X^QJccjidC@lfyvSaIwa8T(ID7Q&(NFqR)f3Mbnm!$0+pD?yWc7&5B(quEjPc%@ zr%#>wDmilY>K@qCProhs1B;aXx4@ME^CYbl*S6ns3lyupc2uL6tbV;@Z`88E-8Q;y z*f~)4v3~RLtKHNL4GP%>8g$hc3X;@V4&zQbYu&7B*Y@Li-TcxusO)E?P>d?So7PF@ zO=iqgRDphJK{wb;lRA=g4QAKk95=;W%!mk0>l{c zzBepdSe9AtxWq;?Y2H$`cwxV@Q|zr1|4b~;*AEuHr#13|66(H~Z|k_ks{U)+YuDqr zk6a7Xd2!$kN_Jf|Z@UX(r-#_je+aqRrMds~PtU|lACmV8hb)%2Wj`Xg9IVyx|R~9P>gN%b_YrDX!A8&w{I&zYjfZqf>GZZ6>6@x*o%}=&J0XGE!H^#p}C3r6A zi7XZif87%_D2wHxCg3r&(2^`x5+3;B{?y20Fv4CMdG8r%ECKHa>B(WGk%4}4@IX(_ z3|&qh$jb@KC`^?9KJ!$>J5~(lE_g|qAZaA{O~99kWZ*=Ep=mi3DJN(fwGBW5k0LK0 zBY_Ecf^tThvH-+ef+8g`#ie6_ydP*nt}eF~ zL=iQ>o(ntP?Gwge@mm{3qxMkFhg&ZP?auEJHGh{%xVn2T#WeYInqVI`#yrFW>Lm+!c+Ux z%Q2S&b&7$j$c4nWz({*#RLGcN$33S2&t3)Oji~?e^mzj#%apNvFwg6+55CL;Z^v6@ zN8X6~A5!5XEbF%3&Ct$NEF&xFYxSKV<}d=gr6;-V7C}kj7La zw7^k7L zUj)3;B7DC?l_bq9avx6(xv67$A&Ko+4rGA#c6h*jCWH{Q9m|anQW3%+J3<)TLO~C` z-cHh>520%FAXIH`3R;L~2P*Hm#*3O0AkciMH>QOX7Qs91z_P=}hHfdjwgJ_mwrq^v zcmV`@eFsU@r#oO#(?tVUD7Jv%RlEN1uUk#i;th7;} z3R1jK+8M$mzYCby z0R+D!nV2j7(3(1SJB)g|<%?nqSPSMsIFnBs6G%h}LML{j5@td=;o*ScKEP0ljN#c} z1ezQ~!3e$GNiz80PT1gkLlG*k2FXoI8ZhB?3_@s&rl6DCnEktTOx_5CS%_vM*Hr5y z1%$^@a6&tA6q}(Z8l>sk09P6U{xU)qftYuZD1y6SO29>Qpye*CfCw=es+2mSCxJQd zi){4I&ig2~co&un3HL{U3rHNdCs3OvmJjlM3}$EDi^h*z|HaJ{pi;+xZ#)zyGfsR5 zf=_FwVEVVlxoN^ot3M(jNE6Eo`{jC`fi)h4!!n2-a`ijD5TSC2OcYwMX6X|Y(0N`; z9%zRaDLJhWipdO0asEz02c>F}Op=F?1Lsh3rxunSHi?YJjc<*hOv{7in>=h%nJJM; zgtW2z$n-ke@bsB{FhvehIE-b4xg1upEsFr+PlBo|LuL*=EQ}R}T=*#Ip-9qzj{8Tw zgMhA9Kv_`J*DnH|N*EoWtb^r%nQP3oSqTFyEyyY$<@!ay*Gr?=L>*GWEYyKT6jz~y zgq0~ppg|py3BDtgJcPmpRVh<4Lh`yKvbHWfP;5IqkP8ab#qy$c524I!q7*t!N;ar0 z166$xy58CV(qt1*pZL0L(nY{$=~6=JdcaHEde|)pD>*%Ql1ut9MfRU8(3DaiZ|4Cf zvBqFPW3qk`@cf48>~ec4X`v^2B(d8Nj*_k@nMxnaiOj{X56@NUhEgo_Nu}Qpp;#V6 zDTVr2K{yHrSi~Plf!bRImKE}-zTyoH>SI-4f0QRrYkdZ&TDFZE(*~j(jdzoB-)%P> z@DgE=l>wFqy0n`Vu+<0wLJ6ODqh-gUtNnHa;O+tnBY6#dn}YCA*n^fl%bzxzhe07L z07(ckdi^5c6EYBJ)E>06x@FsSunWY{bKoNKx>s@;fwt^HQ}Jj0ROmy%al7%xdjGHl zfld}tGW^pY_6#yTcnjoS_($&Bb;Mz4=Z6PY09EBt1H$V26Oa}41|)C3L8vd9pkYJs zdft^r@a>$$nuB;$crQGb@i{`Z+eEn-7?GB?G%&152>Nx>boQp8PKa@AZ?C^&bnw&frX(!P=4vtU#XA zw9CX{>WCHsM)1^?tHgWK(9AMOLq8*uj ztOO)vjAem?w!5^fSsP4y4os^^ChZnlB2*h0o^A|Jn@3N)CkYMGQqeA-)@5g=`)-=D$bfF;Z>$C$Z~A&UMOw7#;Nf zr-rfqHyACmtNUP46_p`A6RZq8`EOmlW3!fn86(3A?*R_Lq=>b?^;sjAyS zv!V+En7krU?t|7$Ko-6>L6a?`$G_&W7zXn?X=ALPa3MnF5!)%5!UFjxAj3UPv0Gr% zv#DAH)`Pu@oYTf;d-@Clts;(u`%pBrCySsVGZ5MW1*mNis(=}yaxp^PZ-xp;v@@_d ze336Kz9mHHR_Ho-%RVg3C6*W;ejp6B^TsvkJ`CN7&SVbHB=8uSY3Lo0!^IrsafM=- zg%$WWADEhaI-kGsb>^seAE-kE--0X=F~?v)C%JwR@J-MD;?HS;)|S&>m2gVJ7|bOc zSaH|U#LOD$UdMAnu@G5;HXuw7QOAwt`uB@cfO@;iRUA zn9NDjXcxm87)U`P2e4cavpDr;cu|IZmf#i9-VN-{#k8?3C!lyUacUN5(FzUiec?NG z-h;(<9`vN-`@k#m2>)HysB5TB=r=$hbnu{%k*_1=iU>5pnp6$*tYH~rRS-g(HQLWV zsQmWh4j6wDEXm}Z$5C|zs<@q+6H>GxDXVJ(Gjh{Gs1v(@vN7w_6ok4%7ok43!SW$7 z*^dxj86X6f{a6v$JfCIW=$-+p=7W`syes@;j6jd(qRX#Pd)VSedV2*}2si8WAXqbV#P=fVlXoEPzVh>gY_}M5p}?#zlGTY0DB3<6kZ1smkg0&1lr(;Muqzf zg;5yT-gW|ugV0>R2zZ`r2$a8U2=t&c zx=s>j0MfzN-{O_vl{yhkI&H;h#ft{lz- zxV3+nfQ41}X*B8$h@`|6pQk3vuM+&zaY(_9v@vmUgA>KqgN`^CKa}N$Zg9aA z>%REAij?4Qc)?gssMihMs!!4GT_QfO2n5a}U$DLc?C>3+!%bVd&Ho)7I2nIYPytq6 zD%{wsp7lltSlH41b#U;cfj=_^wD~>F8)x{Pi4Lecj9Q6R+=BZT2n#K%jrH$IGmI7Z zr%!)7s(rmw8H2I@hyC|Hl)a7ndf9e|P1eamkEm7vD6o6jr<~duL0riGThfG4?36o} z3&sv=O*628z}*9FROo^virwjfx-$1s*U4(I@azHGD`BEv(2IZ{GKB&>u<|g+Q?DK# zp#{~f8ssW@L49LS4;32F3qb=Oq&05Z1ItIn769&hV!2dQl;u6cJvW}sU7?GlFxLid z1t*7pd$k0wD#t(^$OHL!qUq@9NyVG-8pw5xf^5Ag!9Vtf3OupWu!uLz9Iv?n@gk|o zQ3QOFIwNAtHBa<}>cJ!PnycXav&wO!I^tk?hhxg<2x`nr7yVo%2w?Ujcnz^5#u|N9sl%Pz*B>%l}W*h;;TAW&)qQ zD&H7rg1cFeX$KToXrZX1Xdk=e8qg;S4B7>n7IGI&`$9U$u$+*k1Iu4Ka)JGIUYbB8 zUGPJWH}gmERfhn+HJH>VYxsK0gNnrUbfZ8`Fn$}-Uicov@=?m=A{^4rvC#dq%AN{; zSjG#=5kur(QG}Xhx0+k&nWyR&oz%ZXs+URjoleC8L$4()G5b)u z`D##JzUc(kwiO^ax#xh&T8nE zKgixHAJkAkdFM8d0Kq!IPLdoNe+z-GlAv|pou0^prtk!)UF0k3)w>AP%oknF$9q(+ zJq27(Gi-!%JVT&)BxuKh!@}FaZf99?qo=rDBGB)?=sK-f{NsWvPzeG{x)_-u&vhWs z>3UXX$ixpd!}sLqWFb(Lvq4cN7g**Uf=_?K$^=~jcqBV2{NQLx`iM}6{LqO9_lW$t z0ysFuZZy*2aRe&wk2;$u7O9Wgw8+W=Z@=N_sN9hf$GjDT(a71T&^I=ex#cR*NIC#r$?wr8o>T?BVY1D}#a2TV z9gr`~M*B|@sNnW%_y)qT^q=MIx+zo;KLiB0<|;99CEc&`@CHzayQ1r`JHBk43lqyHAw*edsFj zj6ecxQUA6e0k5Zp47)kEg&tZ9LH8kUC4PI(f$ZQ++!(^{dk}n8EZDc~3&pY^syT(i zW|2IOP}4)vuyLl_S`ZE7b^k|RTOR~E5Q?4x?OqwkH3I=b{9KGM(dFnxz%%(H&~3ot zaO|vh#!{XHP!0ekH&|8>5h@3x2Ia6`t#n|^03hT_+lCWSJP_mIEt~(1nqfeq22UjM z2#7PB^w3K-942I*MZ|FlN0;8=k=v9{Ofi_T-y0{gPDh|Oz(`o85SEV%0ic{KNN!x{ zt@;t@;`(%~5%6?N69@q~b`xYAffoDJXB5}p0FTmx^Z||i`bEGmFhIkR;KcU990)#Q zsv{nHjBsPwf>7I*0JR>676K-grwl}cE&zj)pTLE4BlrW6q~^gt5*AB`A0}jOV*j_D zsd<$BJxl_Fu}#|ewDam#;wa=H`5R7dsUEO-pLf`}QFSv$Cbf?ut-5|u@T4)W2tj)v z`=)>Dr=nl!Lp#9lxXH#R=@~~6S));h5uVeX@&m@Z0k+yw>!u?%DvGhl1P1^Vv728s zJoD!SgisNUZnOP zTn=Rri#aIfmz)iNXdx=inIWykncw(!%(bg zJi2kbvigMLATW{+FcSH5u!49P%MC#Qi}>$mJS@EGDVV|o1sz7YydU_~^9-b89@wsu zmvpwXFjn}V?cftFR}DPqT&lri^sQvp-f{&MGBN>e@#D_i-+metxG&%+lU$+BLKNGQ zKoW8|0Twdj8cN|fMJfier(nwVGL+(S3d;!_EKBGB%>!Tr5io}^nJmt4QEd4sG?j0( zdl!U=05!ulVr|(Gf zsB8Tq;0OL1Knvx9QU5G9Zt>Tfz$b_?Enve-9#Cl((5-W%t@7YGxL&=qBi`eLSQF7^ z=hWSWOUFSG8U_g_ygoCr2vKL}6Qq=cqKx0&`v`VC7<#~uywfcD z0^yTLyOCQ-u!0d^5vgU{IO(B@B(xw?;pSt5K|BtER+8LEJI4_GWIN}-pYh--|BAf_ z1kC=Yy&L_B47=RV`9B`~oKJ>j`8bb&QinNdp%=-hEH&2!q>6zq-k@TT>!Y@a;3qzD z{`>rcyUIsoKJwlp+hI^_#SU(afJPn^OP_}2hq0zyF<0s+F_=@d8-4g$7R9FUa?wJr zX;>C`K*^?STK!V3 z=WeJb9V>y9pFxEG(mqJ{Jjh6v^XS%<@%Ee0b3i)_u*?(ByGR!}R<$5P5m6RBwl{GC zhgX2+j$9vO2k6v!tRyl?^?6u{ORkVk29{IV$N$9tXyCl|FqvNl+TEsoV1>JkiB?>KMpc%Z=uqv+}Nc9?&2J&ihWC9Yqh}{MYG{i(}umBKE z;CPGN?9P*r-$kq>9Hd)~L^`K{ZQ?+nlIKy%3>PHV%Z2@Sof*Ce>u`G(TDeHlOYRbU zFKUi>j~jBmgl79NcHeJ(Q27Eu29UQi^9x)M^odLOpA}`utnTzSV98RDnB+kjN)5H= zfco$gaEEnNe`^0C34{Ux6i9L(zo$o_J2OdV3tPH{ra?v#KtD>Z=w)UEYP!Hh4<%)y zMt(Nmoj?Fh(qpxaQas^D@T?G)8hVpS+W8M>!WOt8vra`nZ6OcA1WrYS-~^!(4OrhX zv;f&d1p=8|B2R4udJQ7&H6J59CXEonG>OOfnZeB?z*fhBt;h{<-W-9dWs#Zyt1MWK z%KZr8WEQ$us$;ZWYJm~=0PB$RuseW2o7Z^^BRotFAq3WJlt&B)?x{HNFJ1CRU7|b@ zsGJ2iJ>;5ADp>*9urNId2sJ+&?FmI}KbkUti2V$h39siYF~NQAtV8Lb*=#fd&MbH< zjsYV|$!?sOP>0~vE|ZLCc^RJbT{A+!T_)`@;Ny407UcHJ%cQC>jNnhA1lAlh)jM`_ z2yg+TTYy6(@&J{4h42_8aMMCAIV5HMbKr?1+7N0Uh+#NQ0i2QEb>8SW>3$-V1L}JYj2?4^BuB~>SdR0v2=&nw zR3S=-uNn(L4kMrtc_eO~hw!;zPTE}3o3b1yM5%`l1~z{0-iTJ%NBSIA_BQY=sm2SSTHMjM|&#Ra65y%mfH z4hVY*jTd00VcW|{V-|?33Q@<^xKY=(=FUl2=WOK;@@+SpNGfx}JwxwYmUD{2^z3l@q}qDnVJ>1)Zw5 z0G*kG#m07ND;E(e{J#=-sBZ|HB?CV4kh`&Ot(GGWLO$W*{MxwY8!$Yt@J7~22N5ay zw0W8Sle$^TL{=CS>Ufcj0bS#c;Ipry*?*?|ad9m0>si2_TtQZ_(IeXK^HoymUb_kh z@T)+Cz;DOP4C!1WWtYV@n0f;oL=uNB`SLPANq`Cmx~1@a4R;{@t+0*LWS>Xyt=CXH z0!yzyc#$Oj4|;)T%HZ6v1e_@ZQ1Wep#1E6ALaP*>_~hTzUWk_iN-ibIb_IbX5D*56 z4ZJi^e>U&GJ1ejMw`Ev* zNN0?f7TR?k9Yn-YqF{COH}Mw%e{wT2$iEZ}n!Ju>g(hZrk&hoB>%y(;urNIvaLA_R z=C;zhgTNMtK_|D9j4!{yM!tt{07mc;N-XhT1pI4xB18y!a09iL<3-*I2@sQBXEtUZ zhXMj+DMxEEaW0ZV;4?ex7kFZ7NrdXcwi>wae#Z#Fh#&nTN3~i)g(MWypiF2IaOxm% zl*T$IqH#QJAc_jC5hAr&1uV6Z1ELtNg7l|R`3T{#BSLsw!bbz$*W+XQr<-PY>qtxd zD+vQI8BlZmBH(pB{u;Zj5-nWjRKN3=0qO@RmgM;UBY)u|03H$Xd?hR*&#}L#er9}s zCE5lI4yY|DQO0SyiD@u+W{+L~=lGH)Mv=%2l@)3zfYedsn; PfT8F#cr;~5+)Di)oD8oq delta 235207 zcmZ6yQ*bUovjzIawrwXnwr$&Xc5HpIZSNR6ws&mXwr$=2oc~tcI(J@Xrn`C``e9Xf zuQkWn4naruKqXl)aC86w01dEc_|$6ljO%&+&&?DB06_ZB?r81EXlQ6{Z|!Pm_{-6Y zU#ZV#fC;|)gHG(1Zb4%KdJ6={syP$(24ZI_>ZX`9c;s(#a|)nf83hJt!N|hGK{LMO zEVuG$z%aV@Ub0y0ZElLmZ7{R?Q#id>1+8bGNh`3mvmblG2(GBi@uofbeprzH1WH+e zkoAKOatsHTwG-v@Q{Y5%ny8b*phPQqp zsZV8!T%|xFTWf!IYad-q-UsUFw3i-5e)S2Wb)Q6z=`N>0PRd`90+l8?-$Ri? z2KKM`b0;G^1M;zd)Luz-YWt2V%Q)P9`}%6cf)RL@<|wkWji445{#&=SbHOey@IC(h zRNIsYT)186f5R1SzlAMYKYLHG2|Tlpt+UsCo&PCzHpssQPz?>^W*jUvi_@4Q6mE-M zywn0~1gd5hUWS{+kw4RGkU!s2&01~6a-G(6Nn|HjaKdI3f4>z---|rX2Ay!$87(jG zqqF!TI3#x#j#Gq;Y!7Wa_wBL{;E%oqFGqlZ@75dc>%{&m=^Fx_0%z2uJT+1JAc%dF zRw;o3DAsAw_hP~0CwavF`ITuWKI;BRPPRjk{BE#ve*;V1W>&TnV~~hGLJ!gu062koFy$@RQ0k()wwrX(Js%QW=y2 z=7Y@*gP{JcIJ@F$glQ3(HQaZj}Mn&wNsG6Mx~ zt8CQ`f}D1T)^(>F6rcevUQr3Wg#?L>#HF0A5X4%4bE;2@Q{*A3!)Y@$8z3SMTogca_-VHj#RBsaulJ995U9Es(KL z5iX&Ee+;SRuP+dz=6jm`^wIoV@dYM0vryXXy2dCk+Cp#x8>6 z0A5K6u&7lYJmn>z;A+^gTu>NMS%1k`4`ssQ8^Bs~Qz8 zXEaD(f@`brZ=eK=tUGD>OT{Ct$RGun;`*wLRBrv@qCw%cC_UFfo7|~vA3Gg-mLa_r zA(M7>ounrAHStvr)>KO`$ovRn7lxU=}AHiSXeRF$6Bw=x0u-_w(=m~{C)q|FU5zBz13!i&VZY_k2`B!D@8D0|ar3w@voRe8=|gm>PU{PRAk$X~o9@;vJ=f15~O?d&c^rv~kRF^tMq&w{Wn zcS4;Sl^?>Eq%*nZ&7vyyKHlXV-4NgGd4zuax-=5);gIxn6mOK+ux%VcA=B^cM#hs2 z@?Mxw{#S&u*;~1%tPmU6zmJzCa*F5kUG|H12U=2;M0A`OI_J#F3SUz6BQJ`k+>&s= zRgSp_dpy#$qhXAKy(z_T_GJSlljgVO*ysv5KP(9sM>XGzZ2q%8@8C$&CGwv@j7HT! zlkubW+R{u|{K-kChM_u)I*SZr*(0n=H>!+1^S`~cN5PjfQ^OUlD(@vLX4HXO^D!ysJ&m^)! z^3c+kRO3e}6<8;KJ@5+Fy4GXRw1`n?MufZp$>|DefR?sDa=Q|EkLDCYe_{L>#<1TR zOo`YkxijSmV0H!S?IzHG_{$O03x0*Mn*A-^t#KB43hIV{?=v9Bax2=xrr{K^DAo&E zpYUX9#DsWUwT|>xa1DnQ9Mj4^au-pA2!O7zxm!k}WOJABYhr#1_0*ZRQE;Q1a`}`v zp>5N$t*+WxfOx`fQEQx-)C|y`S5{Yg4v;xigvXrJvK|MzOiyR8rJj{^f0~_NvPG1z zLLiFTmH##?h6%5Lm9U0Civ+L&J^=`3sp0Tlqr??w9O$L@IE&p%-0-^F0@2E-3;g^u zWiI$?nN41p%1W)=g9zsW?gtJMyNl5-D7b?@^yW%J^u@K8;R!fSn)VEiSkRXm0SCix zzKr>-THe4z&qsiOx2u+Mramd5D^yT!NzL}?NqdLy*Znb6)%c3w)~BPJm)FzX!24Ht zboLXI1UguB83+O;kc(n|n$#_m3NdlcsA!ph8f7|b)vc}uT}>20oIbn3N?w%I3r!2- zZ^2d>3^(VK0~E`ImVR>C0r@xA5U6yQ*}&hwZOB09ew_Py;q=LmVP09!IG@@ntJpH9 zmtv|0oxNed{&&X$-x0y6d2~C2I>3Gy5KV z14`_O8g*S^a0N=ys~a*}FdWi6Q8N@Shj$$QUEZCWDyM(XCL70Q_loPc#LSRDqoHRV z&>c9@$ony>5XTo7jxk%41*U7G4K-0tMz^UPk5t?5|EHT^F!-|Az3Ni8D$v;zjah2? z1C{6f4rFZp(#o%!cQ0Y!Ms-xcOQ^pdGd&HHZu33YJXqB(@O{T(xy`x`0(Q5N1e{w#mfjX3pxNNr!*^GOsjt-1pbV z?=Ha!y$!@NE1>|^Id~c-A{y9El~rJ>BOj4UIErPrEen|T=AQ_htWnrwlf$c_o}YGu zh81=KDwwUrhWMMEuiw6GHVhcJj7ZvgwI6zfMB`ZcCIG8kS%k{TPVEKhPq0KDd?}U8 z@?Z(Kp1Gdyg||2H|8(X=P5z<)Drj~ws5X{p0dCjtFu=L zZQsRF0SDtxIR~Q6XUR2wd*8<3{$_Fa-C6G=QHQ6lSGJG6_Zm&NpzO@3?N zym+Pp4{u%*)BSt@;axNAR2lkxZi@?MZRFs1&)r$M;dXt}oBEl4(#uAZwGi4kWEoLn zx>?5=IJu-nq2>P5I2z-NL#@xEQ@0d}O%k!cWI>RO23Ng%^#7eyJd*AditEbc$CO9p_Z!uOvoi530$<&q)760XKc%vuoUtFi6V+Uirjlt2Y3XIM z7%;)kbxLgM8;*umG9upc?cP$*m9X(gEfgu!KHQ;iUGV1CAt^jVnTj&P%rirTZ{;{0 z<`0CUiXq8j4N})u^PmQ%gMj-|a6NbJqs)toorDgW+OFZeZ7(-8zqS-6?+${83l8Wc zOGv>^-q~JUlC#N<@;vzkd2QH;=COm!db#8U{x!Ti280oDD6!~X@xP$%{3BcdmE*Ru)_C`k*B?;&=#8@^*U10tKsK0&;2{72@Jy2@Stx;{PCrlRrgkc_dD42f zFB)zi1ybvc`aBSzfg~_+;8yU&4DmyM)$;l(x+Jzq9QLWCfs^o%M8)L~>LvI~Uechf z{Qp^B{H8Mxlz&LuX_NU>3sqP!j|xXC-$-j^>S2pM9DjWk66Z&9#T)dF#Az(V|DhYV{KAsvz>0_QkLpx!xDO_$GfxaXBVZF8*KB~6J%bB^ z`59BB!%5|G{#B|H3|GdK1oGyUQEHM_tb)^aBtlqW^A{zA;|@Ac-G&_9C~uf_LKbBr zbwHo_SKi!WM;ToD@5VPu-POBROkHF_46peu06i(AxEVFhtJf!^-_}{rS~O zc1IgIp2)?R=<3Cc>VT%c-}6dL5Q)?j@OotmX&8r1I`<7Cm4M}+CLq$I2!V~X zS%-4ieNq1E0MYwyPx`ASa9dJk?K>9lglPk@ka%?+hWAxMKfpyVy6}*#G^Q!EN}8!5 z6{-JaWb{eIr$pggdhI_g==52fAPdawlS?!=BT<0Ad3DkLS7I4VJlbCZAG9|&Hy2%m zwNDFc*~k~0IADN3NP!$C)U?e_n#Z27= z?1UK+-2QfEI-c3H{MqfR!QNLGkiQd{2_S_Bs*h^l)AaH~Bu9cAfPjQ1MgZgO9or?i z&aWa2tRQ(>szen950Jtnr0&5WD~OP{q2uo$F{RK^n4*nr$h_GPJzgc{Tu9OR1C~;A zHF2@DV9b^QkT~UgiLxnqm@x-Zhe!<>C2t3j>649J3EN7a4Yl0_uE$>qzdc2CYs&=L z@!x^t$#wzI?>4EFM}SunAP~OPI%DgP9#$&;zKx`Hc}km7b7~=j$W`|+h^!-sTlpYT7D<{& z+WgzhDDpqbltGg!r05H@(U_!vNEoT+pnXPT4%vEtO*G>K?Fn~w(E_m{KwDE}0;@Fs zWrP624WL&Mp*zM=SDWhWSz!j_&5GpFElA}ssI%y0Dz#9XnBvgVyd>=60f^;csHuG? zvkYSH=_Y)b@o>nZr1Zso$gT+YeAXx;2o~i~3tIhJ000i?A;=?u(4cId0{#=K-!!yN z%NOhp8{BGIeTZKw9~g|OM%R0}fW4G}uUJVA@!jz-C4*DClw5`|Fk_Y8zbrRST<)bj z=E~8~BT(714Hiroovy>M=^29LSowmDiL<-E^)DpWlyJj-dQ>8Db2l%%g__|!EQ?Em zXZK&JpN}r*T=brhO{*L3fpZ^+F5xQv%EQ<(L9sE)^^1HAI1uf-ieeIuc}xxB6BTqM zzgs04dW9g5pS(WCZv&)QyGR@j|E@A9R=v<0PBulYEu_COw`)(THX00is2S%1%K%Jo z>ojDwuN5gyhpZ(f;`a*YZi5O%meHbr%oVU8bD=Ibo*vS4U-BU|S~*s(F?LOlDl9px zmi?yAj&V%I3fR?pfy8k6Vu3cHJUi&`5GWib6bnx@oei7j#q@Y(pY8S2P;e|PB*F>p zTU6gVVzQ9)@NDaFdaYG=0UHn)8_^wb7MFV^v2H!9HS0Hk(50cudo2wYZ-}m|wX9() zZ@Z=ObN;(xtVZ|Tu$zJ5%}w{Cv$a_tW^4UBY8+d#1E}UFUJOEZ*-~l(_4+in$ z%wZqDF>KEOrdRp08kkW?$lEuC>(jQ7>t_^7lS$pLE4%nIou^HOnvTYUm=OEC-kI6Z`kX5mpx)OBAT?c}Zg6wVLUtUNKb5F0&NihTG)Ta}#hF-xl1xB_U z6e@Ql3e3>{k=*!QF{&X6X4x{ z;V@Iqw*K?P_)}jraBG>F!>r<;Hum)-`14Tb{Jg_*(&HuEjkoFo z#ZW@^tXRcMla~;EvNL{_*Ln~NS6=U@t22co3Jfr?r)kQ?ev}E@%KzXYYO0Y>(6_nK zQ|`=?&1ZRqq6b4kh3Ux)t<=Q$(Lo5y^&*rj9yqZU{!a0duyxe7N`gM7QfOe6ZP3j- z4!R!5d`Yw0jfH4@a-H2{qos2!7oh!`K$mxQk`!3is_uK!-Hz>jVhFsP8bJs42VQ^HAca5oC)MNWzM8gvth&B<>+!oI z3E*Wy+Y!83|MDHN`Cavo`O{(>=eFpX5=iPv&4FKsXLUR9JNhGrB;uF4N3iE1AbXW= zo^Egm2)#gVuZmXG@{Ode$)*x>FkCWPy<5&Gk(KGXZZc*9?_D#LTf*f@ZQBFmvYI3O zJKuX*ABsHnZ}HRD6*`j!R22j~ioyd_U37%T*tkPtNsDPD({^ggzKJu5|HF{O5D=c6 zqd&$Rr$0ydyw)sbGAW{M`33(oY`TWH@T&{{l79FBTjS+@4IA@dqhP$ThwOSwnCXxc z-9eut&rALstk%JEdE*D_GxFj|L2}GP`gc0o05le~hduSC)VnN2mq+r&f@axD8R0s| z_K?R;+{{kT_ij5gd+H>skn}l4J#eqpir?gw3k0hMz?NLnuXIT2YXlc(78ZF~^oj!Z zoDx8QqVDAJf}8hmW@5!Fb%-sylXsq(B_^Ey5)F}$ELT(|@k}SdF(}cf{Q&lWk0M;s z*vXZ}7N>^U7gcjp0h4^hl{64vde*N?9T1!iYQBbID zlr;p~X@;c&p>WJd1_m<6R!N9Lx3o-6zw`Jd3C%tH5xO*^9HA;vYRbHW)5kxGS+o~q z#p!@S;s7$0pY_KjXW3x`-B<&0Y(v3`^ab*w%*&kRg$TJLcr2u*eV~+Q&T^lx5*N8l z_$Z;+PF+T2_Q4%i>%jGX;5KQ9ISmKAgX~3jg^$Px!)R1Hcpmj8xMFvbgwmC6Q5d}a zEr^98GR&&uBsFmcdVmruvQ=TZOKOe&RWTFB_&A(ga^t5Uc4RyAF*g;exO$*Y>+E_T zj&^+tWRDm}gWe7x9OyT*amWUdi(C7&%i=GBkVp+WAgxyElo@ZFy@-cMu5N`vXQK}( zZ-ffg6Df%erdWXUK3G==rKH|#fjmft^1>1$`{)5;TOLlWVTM!@3tmdit-A<}NkQvX zz))Tw*egsxk~W}epfjWx>J#l&OZnK{K3po1R@+bz^Xktuf*8*bv*-JDaiSLioOLSzY~Sj<5nRztFZcb7R#t)w~6_WFC6BFIO# zbC>Fh?zZ%=0qe>uQBK*I+8M!O?+xWHbvzN{Kf*X!ZHyLh$`2G*-(to1%dgpeOJ!7e zY&Fs94u-${Nz9|}QQ02_q$@TK{F5(9P2Z-(8R{eH!THqT%#>B6Yiy`O# zeAMwq7wV*nVj0QThOJmmVqHCG<1K2-Ao88Lt063~0JFGSMq4&q=Ki zsdtl#5o}88BqKDpf;C3Ftox~fh~;ngBa#jE1&@_Muc*;eHF6#N3Wc#)K%etEystq7}ij<7cvcFRw2B3D7Q=U&VL_HW_*g2V=c6!tT>*>B)|! z-T_4G9S0QmCvtf0d8EGfZv~=seZfNPN^FOd!%DphCLrTXYhNZYlVGmB<4(&Tw3U{y zNS@hZj2*2ma`Ea=xLU9HT~M*$PzhJX7y7{=8il;OdPjS9#N1iMk>dA%c z_FhuoGHa>IfX9AiEntNM-CiXqLdcrZGB0D0Qa+pygqy zShQ09T_3;@r56We|B3A?1hp+MoSm+c0WP>XcoH3{1yMkZ^5RWMegqd%I&NsS>XS^D zQp5w@tP2a!;~_b#jQw3Y{PS-WBxO-)oOc(YH`$Y^0EGLGf#AU{mxJ@*z|N4ibC7mT<-11Amo{o zNZ@eIIsWMT=-~X~$*8MJhmIAxC{r5OpjNxwpF#CC{JGIB}(+gK-9dr7OKI5gKL{ zb!tX^&;uujANb0Q#zbg`pSZlCm2LZ?2%=RVdt?eS;wy4hnS?z-SmjpoYb+bdg9t$V zcP2AcLoBhDO%jgdu}#zAfuXA)VtRhCfG(MehIEQaBGI6HTJuOz9VKE^rpZ6mN$?aQ zc`+v8?tK+YS~B#1DDMhg-w!?->EMFntpY)%ZrNuk({_Is;NK6?EPw8R0g76^UQji| z5JONO?6gIwa3iGtiL#sV$l22n*bMG)ndUNTm+a|GOsdCCYl>6|0F~lwN1$7v4ymUg zP#4k!iKw`s+j%nJF1&d!^V5x-CZ#D-mf`E6cJckpRcTo>&`obT31Phrna#GyAAw*i=r$Ur66nJ#T%4_P*tLY_S_N6 zB+a#>t_<#@o6D0MpdLa{#qGruvF0h(Fld~fyu_9q^{+I}% z&*7gQqdZoMP{eGmQcL(8*KnMO9T6+n1SM>xsm+trCo1R{4ffaT7<;kTRrq2Q9EUto z6K6T*Yg>%|#iRd)qT=AKJMWJLNdN%hFH|Xb`pS!ZP1zv(QkccLGSFi63zav+d--LM z*Qo32(Lsp=fVs`1IQWSDPipPX_w`MI#y8)|o$a=rm}nUQg-W_*snR$xb`1q>W3$9^ znlo!(!U1+Ho8Az44uzRGu}&G8+ex9?ivd>!4mGN50nD=<1o@sBk8IR=UoRe1Ts62(2y+INQx$`Q>SDoYhwgz>qJnp}P0D67-62?Hmt#9A z!(ntE@C}}KgDYA=C*HiwBUM#6VF@z4@fNE)}0KslAW6l4{5cha||#of|0o)pFj z9B8FI8ue~r<&l?eL<_O%sfpZLHd#J*p_9BVbqZn!OqCmZlu3quzz}VOtb@V(`>5zC zQ*H2fn^{_hmkiXBrw6e?%^*V43tlWL3AD-zSa<)L;gOql#f@W-dg^rHHb2sD_{aZe z@BY&pwTrvkTlE6B5t3@k?r)Lt`iSQ=w|vOReS#ekVF)>dF{xl{a%zP9ZfwwMf@G$jS_v+UOF&2>H-mpgK(2_TU(<98tUf)~u zyCNnlhx_~zhqM2HIyruQ?_!0rW*fi^tT30+ZxVOTYeEoD8g&7u4Qs(q6|xrJT4L{6 zFR-2VI8W6mRJkM3R5_u4Bp@jdSBGStN2Q`VttfCjVY~Q+F!93ph#V?_H%Hnq!{s*YD)kN)Nh!|+VLKW z)W&f=NQ5xfly`e@VmGjxQ&t0|hc3XXHbu73s7XJEk)c=`Ujp7&*b0Di2P-2XS zPejBXA@ATS*$mzDIi+dzAX(4%_$RK^S_^x#-)VdNlk?(s)^9LLt$K4{;`1vz*=}l z)e6WoB=!QVgyH6dDM14Fv>a7vlq!UdjhyK9&kC}ZL)uRunejKZ4jrb3RKVhCZV+o$aCfO&!+l@UXo&@{#bK7yLI=hPTTZm7bN~6Uo2ZkMOOV~ zUn}LED4r!t$U^9p%Czj`rqv7*JPdK8b8yq5^HuJ`$fYPtPviBEu1|@76yY|r{NLI| zPahq)I0SCf5vS-ts&`Yptt~D{nf-U|rS9o^<`@piXNguCBvMnVAFhM1j-O-P+=#~5 z&5Ir1to`Jte_u-_1HfoC=Bb>Mi(dC<8s(%ts9izH|x}k`3zxB%1!jQYNx&*#n zpLsGq_q?6oz8|kP1R-w}c7jpfXfyN-{M(A_`ESC>n$n+Y9FfZFw(68^j~eX?oM+85 z`k2aj*3w7vVgIT$?PWa0Vc&ie@S=t3C{ zgz!*OmGH@`!n6qwh>EoSPfHFrp!cnVTl(F-rro_B9t?9qoGA=uuC>2;^q2^K9Pb)@ zd80!$?_+N6Zi`1i|E~fVwCpT`LpNm>91Q$aWt%iZ8L^WBGHLlAb;G#$|f}oUEKgO8<0yILVp=}pwc+$#! znvTkVy>>6u0?-)z_DD^?XvsEZD-(i`512t%A`z1dcYv<`D>Npn*)x3NF+@G88O70D zPAZMu>y5(Q$AJb90g9TN6YQ&LqbJAXs{6j2Tp!Q$o}BF+g{vSX!$U*il@Ed$owyk} zmY`_I%oz*xs;5fWm_6*%wn1&w%-$AJXB2cmu52^2xV}bk7eBKQ;`m`7%u<^8MH&rI zHWw{6`G#JJL6N$NTtRl5-nX<)odezC!Qa+CUaen&3}rDm*kfo160+XnODUo?>as`1 z{20ak0D;4Pse1aR2+NZ){%w{E|8dd}o;&QcQ$?R!okZ_;LVlj@ zvsmOAE&u%XWQ0^4TO^}yW~r{-9o^kr8)!zH%Ruo!xnbn@=oY`hL(pEQeIST)cXu+P}cKJ@TZ8778~4lf6e=;FYM z_s%aE+#Rvf^79Q(5Xm(QH)9e+l0xHv7JkeJ34Eg5WrIhnj zg@NM`ix3)@&mb*m0x5!7AOm}IKcN4YuB8GrQYmBnzhYD&6?EADYEZTGAmRR(tJN-k z%vbx51{Leqe@WZ`OZ)$_yZ@<+*sT5|@C^Z^*9n25CnLKC{x>w$)dKpzryt+G|M$7U z@L=Nq+GNYE|0`~V;F4uZg8~3RFw;qt!2c&6JUu`T54rKY;t&-0%tBdu^ocSnQT}s^ zzX_Mn9YIK}%(_C|AdwEJWrIXQY_)K0aJ|#-J0wIHZuz6UTNSGVC9__L1(?@y*+irHZIyPbGpQfr z{9-Ex*(CogS+(TwkfMF86K)B|pw&V#XW+F9s}WP~3OdMaB&KO`Ooa=~K#IMRGXa=t ztwJYvb#q!Zx%h6T5{A?c!afB_c#z$DAf8HVESq%0&W?e828c*61PJ6hU6N_EHus>8 zfx0!$OrOT!A_2?A0!<;t!->z`IM#cy>ON9OUG(wvDR5Yo-_NFQzOKPzvu-KU&60k- z&d%p*L+;DV$M^dt(RbBeg&IKR&S}?r!Q~M+bYPqN-3F2W3z$#ZTVtOw;-b=10U=vh zit|YBV+0T`IJP^BZO}={v2ce>hTQK6dql35)Hn`0l-j;167gV1cVjz$?T4cn{4D2$ zHxVQ0I8`aU!*c5v5>){fL;~A*G5b72i9&=oPFh-{KiuV1p@G@40!)WElSYJ2 zk!2yf1>U4cEG8uy25u#_#AbXjitks+SOpqJc_`3|4p4j&1k6A};VpX*(VA27b)|m;=p9_k6QRjBc#;vNl z!tf&5&6pB%i0^u_{nA$9aeuR_IO?;R;midRb?p1JI19D|cE%y*SEra77nncZcHhk2 zm0na)@H_CcMz(7z&aIfNcYDiTSVEXy#Tuzy}ZLz)ma%0UF zF-Q%=$aF)Q?H@t}A+v1&3Mb|)RDSw88w1SSxto|OIaRizTP#|?E`=F%S}tp%`#ofB zrO#U&JYe7ZH=9*uX1|;y`K_SFP+>%N?x*JMwQzGAMtOpDe2iZ2LP_lYM4WT`d87A{ z4;3mr@_1J*ycMDPdX~79q3E6UWoSp`ecSapeM@~mENYahI&0mKxL=$!v%@^b0UfBA zQF_*m(2aE48ok}!-*n@0YPF%$Ou0-}@KkfJxc~g|tf+z9Wug@r+DDkgt6y4qSc9hFWP}d}X z{SHf-VNI6KgSjNal*trpyZ+A`YYlLj8sUV=9N+eI7IF6BT|Uj5&0E8o>Ej_%VX-=rAh1?Iedj*3N46LdLZZ;NYA%{96;li}eebB#OYs}f(k8K3+86Sb0#KH8vQR*|zSCR%6 zrEbpohO&n`JT+sGO|K8h5F{(!Ajj%6h_|u}{Q3#{4?0&*ya!uW5vXj=yGCm&_dgA6 zx`Jy7F83!+^lhwK#B>r%RwZ)pqm)-T2dOi_BsQ8q=$!&;vZMS)k?^-@*@82?S6TuAW3cq2RYB|NX3z+I2(ysYq(pf1rfs=> zm)7aR#rs0*!(t}Rzxnzj>pc4NL3kF-_HBnyEO^o*kzwCip$JM>Vnn~zAaQ07i(0lwv?61MG`aWe~#%G`TsIo$N!YLW~Nz&y>PRs51 z_NR*E+F#3*aMF;{9+Tq*z#Wp%oA_E??#GyN{@|W`uQ?9M*8!=C8a%`wLngN=EnDn! zXmFi|BYv%M*h5AKvqjkbk_%FvH{ok0?ayU@=K24WLH!Lm`mP#{W4IKF6{2WH7QR_F ziNtwmyIJNgzd;^C!}ubr^1A*r4%Mg<*3T~4Rv^JW#h~}M!bPy~o^UWd0_HV_t`?`{WwiE~tl%)TI^0M(?DARQUpI!Ks+1~NL|8l_ zZQHnqUgFlo_}F7w6D21qe3Fg8cmvODdcUu(F*fE4lv%*{P>cjfZqftX3)CV2SHd=9 za!$tdkJ2aLaPIzFq9&n0Fa^Ym0XaQg5MpI8IEmF2#30C47*C&35Y>U<<0Li#kp&=# zdmH30qqTpQsX;06uxq-H@uzUINUUek60o|h$W^)#zg+X}`@E;r zo0N&alsk0eAGtBEjWM&oNLDTC zH?ckFg;nA=*s_25&K5WoGl*EcBf$beDhJl8GVCm8VU@?zXKs>*hL0-P4ht*SMz@=_ z_IP~vK}@<7#&|QtaNLxylDOL~PTNIQ(YynD2_e1-Y$fsPi=c{Vk5S2re2Ry02+5!2 zlwx!}BSFyTe|f!W-~zGuFK}0P=jrS$2j9V7DT8Dr5lt0XB8NOqTrn+|$25TkS>jl3 zL%TItqMVEnUSDnyuIG8EK-wpZhi>sOkXzp}D)0%E_#maGyEwzXu*>n6eyAuNCM=Am z8qvT5^%h;>y@l~YGy&2GqZWz8`40&CzQp3bjnAD_th~ z6l73%8liq{+=$DZVS^3GJ!XTT`_QRZn4%QYmbqfMhb8FK9MFriKml5Ej_)mhe&Ci#_c=VP|DIP)NJP zbrQ}d|C0m$HyuN4lJ?HMUG*Mj3x`uPJI;|e;6Bfxm5DCjHkk|9^`w!EtI-kefLX!1 zYd42ySHBH@&QZ|3eM(L9A_l=E3nF}Io!)Fo z(9uvDZ8Ni?dsI9MY*D2S!C6;?ke0R`%P-P~XJm+nqTCtZiDq+h_xFc1cNec$Kr+Qa zVksc4|563|EeAx>$&rzsQlFj4)3I<1Dvl~ro3xY%$=NvD4zh(%m1elPSqZh&sgru? zU&0bw&B*J7V+ChXb);2IZ6E|uYe~i2aRl-@Xp~)k_Us{ z{{<&bd5LmTf>3QN>1*3-o4whK-fCZu4zb(31wZTJ(939l<-OIeb16d%en)Qz_4W*% zqvQ_>=)1Lfr67}}Bx>C^;3)4M(Q)u>xPQDut3t&nazi0Pw+Uy5J1{~kKozY_DoJyl z?U(@q=B59jMDINjAABM!ibX2hPb>%4ECvdYwLJfX^1TT*^fWJ@Ld6^uz%vbR&NdL3 z(Qj>P!uPPXg%gpUcltqN)pj2;^Gy2r%29;39Sed%31oB;w+C2$p~SNYk?yMu=iRP| zWR^%H46PFn|plUuLD;fBU-;V zAsA&{UhT&&zfBZhZx(~lB%3r>VOW~e2{Cr7F61sFQs|Z4- zN)$6UTbjlA1qM3e={76!tnBg6ypcMec zDNgl^ay6Yegu({;iWjU4Z1rpwcO^ok=x!_S*v4*i9O-#vRy%UMHCU@K0n71_wilE! z0rO}q14fGC{mC#LLtu!>0vMbDW5JndVJ_8w6r{#L91mq4(;LSVCSt-a|vP;ky>+8k^xp{Q2(&v+JT4 z2QQ{Z-W##lYzdW*lAgL%$4?KPc$EI!8-N$rIU;TU>{2{3KO*DePTW2$rw-sB&}PGo z*wUsxWI=eG85-I-f(3d}hyYko{OjI=_l;DQx$xc{9;W)Z20m@1wu{&O zkA(|;ZsPfK+{RRb#@96CYe!bYg^}O5<{5Gx3ENQAiPweKk?k|$aSaK7C4$4nm{$N* zpj{46*55yX|J!&FQRZ^if&l=Tu>aqDrA8hP8Q82L8^6JU{O>{&9vwWNGb#acbpmvp zL>dpBdfmjsp@w93f7BwK5+W(;0`u*LAhOWFDL_2nlvs6OZzR*fAgydz3(2jL&LPG* z5+2mUN&5E`%x6jBJ0SQKOf%{R!{Qc(UFSM>YFAUS(qOgLdyObIUBV@`&vb)Y4pfUQ z8`$+3ql2BTt4;7QoE0p7LMk8|koU6b&-9o7tm@~}YFY>9*KvbkaX-`>7r1565>h$rWaeTx^V*J_UFrd(tm!xx&SWw}_%OI0ZlTr-GhP`P@%SW_i z>xTSXR1&!~__C6FB8{opIDab%tuvr47(fbqUunf?>GLW9ERaoLofLE`B8C_;a_^n%Hq(TI`a6MYBtnr_81y z(iMq2;b~oc{wm2gC1cc!Xh3U3S7`%jp>=Kv)Vy_14n;=);x)=G5sYI}Ew?c(;AwfU zsEnz1#S$@BW#m=?a17 z+5OG2LTt0tL58Pd$|XeNpIjviMa>~BGi_WU@>4do#%APj-`Wbc>;_fDFvi+XQ0=AO zi{9mwj(fP7ut|!AHQt8E3hYpZm-~OX`pT#{n&)d++}+&??hxD^g1ZFw;4X{1+XBJe z-QC>-!5snwcZWB>C;9S!zwD{g(_KAh&Q4GDt$VAP*beYQhb&wJ#Ac{NJgGy2f6p5t zatczV5MqQ%};5%Ks9jSG<l;Y`{o5E_lIo{<^l!+FZ>IrCh_b#{_Rz@I}Q2Q0v=+) z{k76A0e?#mG-Tgo{lh4Pp5zh!yV?>53C<4ppW}sO#+5^;KPw65e=7;7SBU_#hGAAGYoLuzs_6aalr}x`fRZw04E3hb(KH_4hQ|$%%%X3{MVO(7Tl5OzX`15;`D0& z;30dg|6$Dkn*a`IYvt_nr(nc^j-35E4|?~DCMrH;Dz=@`FKc0ETzhH)TNXI3bbSUy zF{75{jp)Vv)MQ(C=$oyUNah5u#w?zdiTl3uUj90fo zJIcEsc)p`tz2_4UudbqrMI{^%O#|AUU*ckix8}a zhuT|C2{qK`!MIr;5+%XM-kpBW*)i%oPJ`^eR+xH23zM~UTkP-8-%VJ(n8wM&W8*PG~%W$-acIDb{(mV;cbP_NdS;%Y6krCLLNZQpTc8K3dyspZbR&nPVx0(}?TaxpcUzS&k*rcx-b{SoEEVZFs;>LzoDSJso4o|uMlw=qOd9FMh{RsWGxrH0cL;xoZ=nG&5ge!7LhUI(9X6=&`?? zX>VXhq;?gYOOPmyb*H`gjr(o60lqv6p7QFsXlz8L5(Q0#4Ifh>E~eqB<1 zw*kj{)nzBisrcg6#i1$(&`+3@=QrH2|Nbm3&wIB72Ot?mj_dLt9qOKihDJ>(kd_<$ zc;R?~4*)xTG{lWWKDE3$6$La0}N_7`mvCWBqxbaizQgA8n`(n5ej1EWP| zH|B$|G|qXyei5+>G`MMWX_T?#yduhb(oVS1j=#G<5ehJ#p&0T*EYqOT>GouHrN zLx5wS?L{EA=|R2QXE9`4(ab{lJrbK6(2e-hg!SuKG9Tn58EbRM%zA*1yXec8!ys47!87aQ$@Umxjr9imS7$l{X%HE^fK3dmkB( zw9v2G4YNXm#TF%3)2H2IH}77{wsTMY6i4+8N%FMXs3nc!TG`OuAA=0ODU@^;Nu}er zTQ!to4ze%>>{zUJ;FnE_dq7hf(llY;(yRD0m;HtXKT`Xn!-AScvhs7hE!;~S-&Nc`zOE3I~{LdHC1H$8nr!0hZTEk*Gy`5w}P#>Y(Et=mhuim~`K_DK` z%`$~yL_OB}6i}t>6nVH})iuW?ZyI62Uumv`q*mAK$G0((8_Qn8@qonsqLv8C7{zI4 zJ%d-aUh~%G#P8$^2%h|VpQERWp~u<{xat}+G0Cs%wj&=cCdZE4hK?SBQCx^uxNnfV z0&iP_4AXFG)M@M+b^YNo-5dtJN(A&&kjLS5?X9kV-X8#P1A;@Z*}#am#cVcf>AnCQ z1&3w9en=~O|%Xb-_lVj=JQ648C&F zV0|xNpj`!kD`7aLDdP8ACGAMa1RYGIDfWgyh@i?!Rh2-db;K$&tLtY>Hs*odXlOTL z#{EqBQ96?0wFFo({j8=|e;ZzjNC31h7u074X{makEQ(c($pR~vc0+jB^bcP&uZ+3* zH&{YB#VT=U%G#ex7o!w7qH}R4=bQm- zx&}efXmUoCQFE7)(r~QBg1`0|aXUMuP_Ca?NC7`xt7m@IHtp%gBA17oDS$$3n)66a z@(00V-J*0X*$FeA>jvZ<0!bewB&?jVwz#Ji19NV z7%H;5gc?7@3MR&a=^m}-s|x`XIq@#~Qx!s~|4z2t#@`<^dV_6yh~H^mBOor6)R+sN zq)?cD5jaYDyQ*A?!su>m`(ZR( z#!c7YTtaJ9{JNO>%Bmy0Dq^c`C5Il@&0^EG%GwO63XL^)uB>bw@%WoPd{U7ifVB7$ ziV?Ah#XScXEf;dV18@UqZOe=wA&17)a#R!I1;!ZhjR4K~t)c~fx^a=gN(Nw~Vv79` zi`jV_Nb^+^onp##@DS)g6!Kfyh37EokrmnsFJ(S&Xt}1>ADw7}x9(jzkd=lh%#gs1UTx7QATdMfH7G*~apCB7<+20JE-_AcmsaxWFlXEWN7KUy14d(nWYF+T|Tm3yB>41CO8G`_;Mx&Vg} z>qqn^PJsKymdMQkhaDc=(}IE1n~C0U!jlvbOHq#y%7<*KHc0APLSrq&sb191&|D@2H*ELK*}GHSyJip>)9}FPZ+1+nTAR z4F6*5qhVlF7WwTmq>Tn9#3ru5*bFL_lhzs@N<6?D3*aFi4Gx%is*d z?v+Xt?0q|vYQIMkoGHaR{gHfuc=jZMsb7aD}3TWa*<@Q8)& z#=8ej-7htZ&}}^FH08qYy|(8{!kgbqHrz2dw^A4FKebN;)s}*?*NSTvqvBJ@ESRbpJ@;35eOi5@p7z5)NW4m4B~;sT3tq7uc!h* zvAVg{6HplX!<6BcGz!^}Q?zY_R97AmoT1`9Lf<8ye6^kqK@nK#Z8nOXBU|z0(Dag; z?7E$=VFG@Zu_2y6T1KRXtKpsrVhN2rTN>WCk*@~*1F)yozv=0r8rV9{ts~*!*nA@q zVs5l8Obbb^+KMcH7OPXhbNd$7Z`qG}|ea1IM>@6$G z7ZE2xVyxpZ0?a2Ej=M%Am2Hfy9)rwj z9918w3*mL~`pmxlXLiR!B7rHppm(%tt?V>;YSKLu^gH$ip`W)qZ{3Fjb*G-Vyy%|% zp|tl9X=qE)gTY#sn8M1W$srL|h7VeIMktO$so{iqC21 zWJJA=^!cdIp`U?$m$rD0WsGwr{Ytyyq;GTX$lH!cL*zW-B=lJHi1wmExI8QLO8wjM zRz%z(4eixeYPTE6Jc=Td_^#@J22-vpu*9-R(-=eSu#+%|;&#Srl|@m@#Sv@uJgfTh z2piz*0Iy5oc!4gBibxPnPFvs(?x9k{pbM>k0mySlL>{xsGWH5NdUC?695 zvg#N!fx2uo0jRDr9MOy&3H{$ccngsZxXfRmp~cz+oa!G|kYow&0`nINKyU!}`O7T1AQfc|{EILEA$BOb~)P0dkAK8@R^5@vFYzc7Fi|keolb z_Ft!^{^0W%e@(w+@F5DM|M4xtvpc;X|9H9tDYb$N0JXUrtseSVOeON|nCfZcU5E+p zx7^*6#g&V*3T60g)6gK%J>5GWg3#Aw=?38245FL_Q14yoo7dMh^tcgJP&8z}X;Vi9 zT1bwnG6s4qgOSHuHyf5nBp6`w8{31C8<&LAzxYvebbhFiH8qAWKULE` z_Rg1z1F>i{J12`GD>ypj6FNLB#OJd4@L3+SQZ=o}t{*<5@}WU7|6q)3=ObyGckpky zUkknI51XD>NhJ^35c1eftx&Vdwk*u9o?wp%>s+-DYijo ziJ_TdRB~B@ivlP26AvnpEJqDN_!Kqtm1Q(i%|_+eyG<1@Co!U@Cb_bet>qTlZ+aGt z0yq`5n5JORTy0aPP56owHsHQ~ia8j*;s9;zq`qz}PwF=~2)~2{tWIa}MBHX9mks%K zTE$VK3meJ^)3Gl^OSK5=vhJW9pe1u6zskD69yp>r!bJ~hg441;Fsh+buVzNzI20ge z=WtwdlQV<+d^fNnRtZUU<}5jUpH~0N9{4neH8U8=p?*`K&4fvrxqJWagBTwHx$bV# z%*7=&i0*NdY*i3W*4Sw#kx-^L9<{_*?HV}4Xb|iX-)&A~mG|WFJ{34+Zfp4%^MwBe zuURlT)1RQ5EW4;(07OEHdiJ^Z!<$8i0_`+#(^;B-1$t0TPYivL*0f?2a#tmd5=h1= zi8-R^-?GiC2!cgZRSWvA>hR#s4@y_L&Vpb>4W7dBwWI>VVB5K@O@h;=^`KEXjfQWG zeNfzHJI1Ajn#g6-D4ya`aJoTC;KFRL596@qifEf>E@+KeYsR}9N-K$e*-V8DfG#vC z%hOt{9Y$;weVD!XgwlreL7K**0wxb?{eZLHZxnJ{-$jvLx-Uh+NAr@;B`!SbU;ujv zCu%g$)WAxX6{Y>cLuxV(DzOB&iWS9a9J8JKMNB8%&8%z^QyiMueJ^KI#p5%0c{6(X9T=@Siwarc zU4sI9#4-9)`RA+OHm(i${j0{ow|iuQD~H7xfbm*F!ioH_9cv$Fu~i*YW}qsT zJ`+t+3hfQt;`WMrnk}`YBGA7MG+}0&&btMRM3mLpC;-ok+sc}>kkJZBpgi2TE=EML zfa4(gf{zZ^+7mNf6ftZ&=(`X?gJ_2 zu4gby!&|xcrBWOjPQebPb5B{WBDm$vNaOW#&{y4^PwhJ_=MSFQjV*yVp9{(^YHO6M z^VqK77~hB{L+BQ}FMv#sfNI^Q%F zt>~4A>ZV45G3x5QO0ahe+d(Mo<(*N z@*BuyARSooOT}iqldqZ?Uqg_FgAk<94Ne23(%_Nki)uR74UdYiL@na^QaCHKTU0k1 zPIwOuEz=)v9HxBA1(GwS9>#?5P`a`#iOU$RD*mJ+8?UPlGE*Nvdwe0NoTG8Im4Mk{a z$x9-mB?3D7C3Fbgob6^^a4egb83ToLsm6a*GyImOizP4xz6cE9$hakT z4WwISjZ*9qSIG8bsGJQ&M zc_hZ)^hary?NU5&>zjm4`buec*|IDPWAkqVwP+J1JE)W7!A{%sqL8k$rqMj}j*ELa z%cc6p!*U9559cd(`{-Q#YU`h4=L@w>`F54YU1*hwEF9X$))#S@O(RXS zmRwEoaN9ad*i@#;XlgUhkr`L=c-#1z$I`2O;%M-L5X zJTr%5R7-2PNmz3Egql(r!r0!mQQB;|KOVK_D>VxXM85z#jMf*E7#4(7vp6+mBxcia zGO_T(3V~nK%_EDqSEsV(EQ_PNvis3B3pukAO-jnq5w0~#=6Ne^*WPsxPIr+B$XAYnXL(-Pkk)aza7pR**paxo=W zfFFzLE8|`w!AR2G^2|W1Jpi76IZA820_$99KRx|%nky{J#=Dd?L%gf>{7w@tL(wLc z6CHdqAebT;NF@;C!7eDH&jJl}>IzjZfI{{Mz9-ZkPrFH#c*~F8jX5C>Bng=c@J=8R zmUb)1mzf;`DGLv|J`33|eug^ltXvxVGgeg<QvV3-R8)0Xw&8u5dp*_R#8(75gK~LA=yt-w(!z~(WuO#q*7422!KP``Gn>dI z>!0duQp)aL=Bh@;>h(}MNzyf-DkQE^Tz3u z&qfzQKAn0hzRmk+fk@>!6ZUGBd*yk+gcKee*O>fqwX>KAiDftq6&O3M-)cO=5C%>{Vg@SpOBU1*E;BXK?%!4;KzpiCHv^8GYZsY#WKb` zjjLbd>cp04Fc{LS69-+H___EX)#*ba*^y9$LvH?*JdLcj+|uh4G%JkJkU3lZWx8<4 znyO}#AVKn@0a|+YxZeIkpw7EG5cgW`6Wu=dtK%0IHMap?j`l{9J1hec7=B~ZZ1%z! ztRupkGNaK__RZ7B&CCS-=p_wterqkksndoM{y%7*K z9!L1vvTI7JF9`TbOOO!D(;izkHM6Akv)JC`QKYiezo_>GXNWhc8`Fx2fGZ|`gf*QP zbhR?iX^=CyM}$0JR|v$s*`DwL!EiNq49BKI;Y9tJf)JH3wev7;kLw;19vnfz;Re)l zQ#QJU`xB6LW0DSOXY}T#9#Rj?N1qGbADe8SWeKrj2Bp3EHE6mee0_>60V$@Mr1L|x>jg2AJns00?O+Sv$x<5~n-8`Lr?|`W4Ic8_0<6D2d zpSot!s`d@T`Xj6IPZWFq(D||?|2)xagz=;q4@qUUJCjVR+k{=LYKL_``QE*Hhy87y zhX!_N{~TIK&FTVH{oduy^$z{lcC#)V23~I@4Bxu)lK=?GaEJQ0I}j;Gr}MWchVlL- zh`!6<)d&q8b}PkWl-q4B^Ky7YzLF>Brxi&{QG*LU{%T5gDO~O$^6TJIb&+U%6Qt^k zUmqwTqq;iWm=mzv;|(hu=>(a194mNPtCCtfIjD&|-Nylv=ewyL4%$87XkqvUK2XPC%A zL(gv9=ytTB1=6&!7ztqOQ5Ml@3VO0jb;^{l*^#-A*GAcXgJVy@M4I)CuWc$)e5Cni zmL!6}_fAtyM%AN4>I-yHWmdedn}{isFs7r(kLtGg2n$k*Y8oGY=9r4LbM6H%BzMrc^5KK9jx0 zi{L7+6*c%%I9yyec%%}`wq+VHwKLeP-^@SgWMnW>qG0?-3;ljP8FjrP$*~C3k&{l; zz;(Z9A3I%ZxXclBSz`LM`lpHUYc*B1RxhW5NWN()0QY-TpKVU<6=rW$gLt<6nGOtrJr38Oon6B$FzeBM9Y7vQ zUpM~-vhQEtHIKV@<~z+`h(pOD%w|Y0UVZT4-u-O)11&8ma-hZY6P{u4n#l2%I7#Vs z+lx@?HTuJ$A^86dnOeHnz={9L^jfwLz}x?^GqQh!xBSCwv@gNQ|Fr^d!SV3_!Yi4` z0LZ@-6i^{PpyY4k7Xm={zpiMcfEGB|{{>=kbQU$B*-rpTAlDiVj%uMu*Aak#W|XN` z0#R&RdI8ZUrZh46Y$gAr>yW}VkIAi5S_$H0(Q@==qei1dhhQYKcs%oGk$%}F`KxBR zfRGH0(~kIqLKzB-I#GLEdyfi&`(VgN-2>h(&Y`;Q3vbKFpP;x!of-TYZ_zFQI8`&)iFkL2T{3 zW8;kE$9k!$Lc~Q3e7Rq4jgD&%GY{*z*f7NNJLb9)floI}(u=cYrxG8xaVIAa$9F

    ZSi5BrWw;OV;+p+)#vFL2ifoObO)`(HgM50Gsok>C1pk`@@!kPC-s-B@B;pHHNvmh@p}I_n02ACB%owxtthv;U})P{%Crr(3W4nEady9$ z2sHV4*Wb;YRKv*U! zBn>oCi{&G}C`BH*5=qp@%BBk}9t3SCw~hrTNT@b@^+>Kd#T@RS9Z6mcWoJtqbPf-7 zfzYYztPN$oEQ&I(uwHBHk)<~|891N^>-VM0ln@3Xlr^Nm?`N!QDf(q?BIvw3{W`RDG6biJU;)oQpoxOYLcD&bYgPVnpZvX6em}3)|Lo{)J~`7$W=vX_HY!V#2j7uURF5H~ z&xa)!F1i`@B^o>>#_`Oa{gD>dkZ5+f0{243oTROCnkf+Gnjww^5{wT3EGcu^>kaP19>inq18}-4!Bo;rO!R zAloi@FK|m<(>Jhz#%$@1$l32~Gi)x6jajQNycK;G!=Sz~v1NOo^Qmmm3lug$CBpu@ zuJVF-72Ea~p{Z-zK8_v!<^*t==DnXo-$Tuk)#n5KtFHi$${#W5xMa0*ey6URxCW69 zfeWkti&!^ChI^A{k(0$a9&NHnxfoYla>**v5IEad>amFA3>2on8ad!3B0&R$qQ-_C z7|qh|zA z7+r0aVmk^sB+3=G$WW+o6p+MglSyU3=Z$?6eHNV850Kk7r^?aWt%<9K`Z_9#V#)CH zWQw6r3<$A64`stF{s{;!|585w5dxh_oCxm1PW=pZ>>z5vZgJ6+9;%hWnOs{UVfNs6 zqsunw!|Di>{xpc6gw)VR^I4X;0xEb#Fuy}^IG7^knza7Lf2Rv+uSBAcJi@DL&gh`p zT=LSl=mNAZx{Ff1GvgpseHYY>5_}iPntcaaKHbc+D3=D1*#nEsw!a>ea^&E>YOfi` zz5`dd$e&(x+=K+v-m2JTjO29p+TW%>B}A_%-TM=fsy06i5T@ z)3oDtv{f`gk&BbThbe)`YJ@-1YwHWO@b&h4!sl)y?`V%&ev2GS)Hcw-;AX*HMY~c% zRq;yLs^n++S4m(t2a;GWTOR6wKdR&_dkE3EU4->VSMYribIBUE;4M)eR6NZ==?Ct8 zn3y2!KtN0OqU9b*VO_Vh$+rqp7j6TruknSZKfg5f)N>AE@sE*W8gg4AdtvNG8vZnvaP#ASZW>!JBEp(ag~$K)37H z`PE1$VncD<8Gam-e#rg8|8{-H)vgO7Pih?5tYT%GH;CqPG+nZ6m}Ab!uYh5JQa}5- z^`71nu}KfKA!vK~(PF>(%GybIpE~!GKizhYTT|>lR#Q6f+Y6)eV^D9R#t{uJD+8wJ z(lYka1;3r?dcm|e{v%DsJe9VUz|Y|CE7{nG=-$Y&9iHHz*H0`WvjR6=GRHH727w)* zvHp}G+9;Dva#aLFukcnTl?uMH_P6YF^&%Dqkh>g^;K-D{9Q&r2>1el&6mfPv?(t^! zTxkz~AshK_Hw4=8^CoNrGX_3S!Jle1-SDn^3;r=d=9gfry&=AAR7X)PqcJCc)I&~j zv|TN)So7`>7ybO}RSD16c;vxe4SAxm=5H3KJO*NY*$qPR3fLU)%mhZFcoC@14VIN2 zUFRFjy&a_UV}nQ*s$-U(pAX!*6LNd_5g>%Ho7U}pY3(#VApUP?24ZIixc&{SD%k;n zf74>S#At{~f09}Cc>l3+Ta-8fb^kJAPy&Fge`&O0VZhVh{(D@RuY!kXaN53gwc@yR}q2$lw&{u3mnq4iCUAyd^2s02)uOPbR-aj!#-!nSpA|k`2)GY>c{b_lz;oj6x5a&aSO*bG%wegA zGNeqye>&F`578Eb$9Vkzz2vko09^Migor%MNc9HB_kgHWjC)1+# zYn3u(QgDpw#l^|WW)H<_Wl^1KJ-J@m5~O^P%tSENa-e}t_mL&$69kKQX~{7TA2IrR zY)g3wzP&_5&hIDqAoYo)D0J{spbT4A^(EiE6Yw3^a%<=iBvg09nBMUGbozWgtZ3iE zNAl$4;2Y>wcq>Mn*Sk z0L|Y1p+8$>6`?J~A&`NY@$67X#PXNH!@7%DBCi8*Muuf>00|3;?>E=aX~S1b%~PO# zmC}5B!UL|uAn@v#&@Vz(zBmIBL*IbZh04QkDZ{} zUX~eT-KvPP?jF$CaNeW_{9bkms6~hFw36%8A48m_zOX8R8#d9+N&o7n>bs~dcEm-T zl%yYl``0I=sX(`CR>{3RSmm8-IH45IA!c6gncDV)=U&VTLj%1?l5QXt6L4U(H-1#G z2@$p}x!g3)p+-TmSo8Zs8liEOBi|+!wkhIQZ;lOml#cBJ1}+z)XfF1v)UYd-74V3h z-LPJ7?)qoenyg!{nd#FwXdlvo!mOwiW_G9y9tJvA;TRXp7v38&I` z4g}EPESnWXEZL+0tpC%3RD`5tR;A}Dmi0C!2Wy;h_{c|zH_Fs_L6`j& z$_IXrJ&7S+cT&*yXszS3L&YWAZI{+E`?^j_X22l5R?Nz!_Mc9D`1IxwvPyEsH z$JzVeViR|N3jZy^fr0(}bKnXENW>k02O|ASfCNm@-mqWeK?gn$QBE$3$58BiGNj<*F~FD59P={&@I~r$SCOVU-ak-u&xjk6VlyHt^x~skd7X zXu8Oqx7^Xu5hsE=X)e$6-|%5_)m=)l7RMH67%lvksHgt2 z?+Bz3PiQQMJ-1+g4Ye!8wi?(c9y=>3MD@gQB}raC#}~`gFr&!M5dFpr{W6se@}W=8 zvmWf8HY4NBK=EO&=L;ZdQ?~{5l&C>BYpIq5}C^*7Hnk8+?w^ASDxfvKPpVF*!Q}yHP zYeui1kmi2zc@fM;d)ZL5{uwCigaFJ3^Q&X}s4UY`$A(FbqTWcryiF=RyiFKPAU#E9 zlB>LCIzxL|{T&)nZ{5NNokxXjgA=|9yAMcO!!*W8N;}Cs#&)@*cA(PI=B$i!yj1~6 zLwks?G?Jr->Vggc=G)y%*ed9_I9(R?xdTq`AkA)(EsffCoa5W8CMR zuT2M+V3Pkhx~~rGktXqEYeYpsd;3MOy=?|vtL(4i@(1q?yr{9)swyv*tsp;dQ)t)l z%L$(GjCREcD05CmSq4Z@wwM(Km)w(wwO$^b^=wGww(g_n66Ri2P?$tZ+Y ze)ZP+%F4H~%cgsM|F(2F#{s-Sn^dZsIrJrnVVl!r4|P=@4eM69@2r*xg&KH4Yz4ns z z+XibYQiThBU_}4c3LhN_Og52JCNbF8xRLhyf)tJtdX8Z(K9!0ZClYvqyCg#%FdbHo zSxx4qwA%aiVEA#)1yvBPNE2J}uv$a17N5Mx8!3boEaYlp8I|(WBw@2oo#D(hbnH;X zxgHCN-N|mj&6u`Sz~u&!483?8l(3c&>S*~gZyhp|f&#-PhKz#{Nu3H*HFc;jPd=n2 zz3OK8tmfy`P=e9K;twFceA@`)p`W;@_m~WRdc#*|%V1vcjKcUEUnUxER3b+ z989vaS>$OQ3-@DQj@?i}!~KeQU!7U*88;$s$pY@6$i-^jp>Q?D#!-Cn_F_e@^1OoD zUAaWswify)F>@pJfY-YTBw*Iuj$!A-EKt@Mne&p;E-GtM$XGz|m{^6iRMf6r|8dqW z)kr-~T!ynIm#-HbcID-`rS)P2v*^j6AS)4}Im0->x!hn)f9YS`Wm(gl^D0hNCxiYGx%YnIT zAz>eWn;k$K`fY7wfPMP=7o!4H0o1zrX_ZG|_~pFBdV1?2OeQChK)D27A{V@6A;bN~ zX->oMItIFI*UOf}FJywmCIw5GNSd|Kx2TrA@TrLo#7Nt~xxG?r(ndmkaX%9aL}b{& zGc&wIrTe%%k*vgoZ#IY8rjX2`@+O$a2BAN{O*jhz^P=Tf;%h}dy=mz_FRnR$IEazs zFT+<~>qUiHr!}*b)>P-tjFNf~bDhM3;rPos@XV^|%s&e-7-|WV;B6h1TN2D+cyhkM zG31(MFF;rT!R3ayrQogEBZ49|ux&JF-Vpe7>%Sy(X}wf^BKr+>;i1dnTxy3pbX#p5 zg&6hdetDRa?SDhnVVe?|_uGXjd_=?dQ?qH}%*F^ zH9-HgYc==x#ffods4mWl%l?S__M}Tn$%r8wuk;Br@NL6EYEsY$=8lLlg__z;C;dlO zK-oN4ByZ)0G-DjO>+-=eKohc7`hh%XZiiAU7BxcNB|1r%WA;+~d;8cE^X@18U8>nZAL2-E zJ?SkZ;O-oiS`B#o)%NLyeacT-0ps(BH?_jYIo2x?*!EgBsdozgHWnC7qjn4uZi$pt zRi?Gegeh=$P7;44D zhxRrRe0EEFarS3-77cAU%57)Cuh`iEm?t-21Mi{OUvfoK{KxyDBJZs-gL(y6>kcud ziDJaQW}%E86%S;m*D^_}P>Zd-z0PKhz`+H5k5_#lxuR@XG;((N?N)&DS^rO=^SmE| zbBya~%>KO`Ptd1+X-{1t@gSFnfV~?ov%Xjt=@8#l2cCyjiseKm@1yR$6%*D9gK;$RFi`9#cPa{Ekm?aueD}Mo=@$R)+K}nyU+5J9BRM*$iwmarj2ljG1P|t zy2b3mnvqua_841P_LCHX_B^H!2>}lys3q)5{bJ6cJrn=8ptFxlm6!Ypb{^aZU!3J4 z3&PlTj=s@@r!h@BamMCuBz-$}I{`5NHue+! zfPYL}&~rWnF-XaX02{<`2l$)mX~7=`1pWgXa;E@3|I$XJ%YbRZ{{q%O;Vc;7e?MR# zl2-r~i0lsVZw~%Dz~a9-uwDWA|CGTU?|`;{rUy8L>pw{03KD|;Un?mLMEF0m5)MN6 zFJ=L{M1t`Cd+YYd5ZeE)PohG6Bm3Jb$Oe%O@!t&*G`^Z#V1R)=O8plXgZ#}2AVD+c z1Sr4_{)<1ysi$n+X>K1)q^}pX$*fMhE+}^#e{=htUbr&$&6~tFB~M&>H-lU&u(817 zwP(v41q?hsxzd!$-FfkJmHg@Q^3tydW>y6C^|S64)?zBwOZ&dJL6f#_c%+^9KsEh} zx!IGzhlM7`?Ws!rOXVkVniZYiAH}*Y`3Vicl@THfiD~xcJtUu`$_+Ed*X=}#a)BA` z;rkVu-u6w`MIwyvR=4$!5i^D1D+cQn+Wy8f^>h~X6*hB?EcVmd(>KOhP5w<-@Zv62 zkN{L>g)S!cPZoRzKZUIaz%}MGG&=1(2ioZlk>m{zR?2Ck?uAjnF>G3Gv9J}LsTz5J zT9VKRYd4kFj?-8cS;5DipPnG9p7Jc+C5C?I4KsKjxvH&76cs&+k+i!T7!f{@XMrkXTDOar1@_M?od1-c(wvekOx&J6Gg`Q9m1h zcG>Kqpt|CvlEEpAg4LNxF)xYJ5a)Y8*-lW~-u$0~-R-mt`hj}RI_2!2BZ3y*PKwc{ z7HS17g+C<$77nD?FW+|FI33_`A$ziL)v@Fz z`vRQW5Rp7nO>@1XRi3?p7SQf?;F!CaxhfhP@ag7LUYhI9`G=3AzGO?&qzYoqbeP_MF>eOL66yGJL zw&YoM?br-axQt`*#0zQR=Kg5aXENMmDgdGw@0y9vT0`SrYbraotJ*Fb0FC-l9J|o1 zP+Gn;#~l5>j4BPo6L?YCgU;~+U~Hz%?S;1Jt0Iva$7ni3_Adz7>kOdZ*_=E3FGHNi zG5s>{n;wup!Gi^E#|zO~ia4%G5MPXE3{;S_<8Fsv*?y*rm#a|#0K zX$6XXb|I3?-dPA@6x1bV{o9j1+*%Ig zhdf(%h|W>0IYiPsaf|9CNfeQF_MQ^Mv#h4&1)LOQL{R4JA&XINJrP2JVnLY*On%_s zeD+}~67{RvaUgSbtK`QZg-5Wf0DlFtVr*MqX`hFqAW$p#SXKt1-~BH^sENe(mPd%$ zH7u1Rh*?BKfOC% zWmm01L3&_v&4f+pw;|`pSD<}sq`OE}!?AhtzY9fNt0uD8)pEtdAmV{REjlxgrEQM~ z3#6QH23~YS`v$xqh`3_&EJDJ}(3>P_8gmJov0|Io=oN%BpcxZqYoZ4}MMJ)F!S}oP zDM6N5n*WUIqbuDa{~ZhwOb}>!HHMz*($#&L)34j6Mg{=I9%on18xdMpW>8GK~VGuyZiHlT8UUE*;0G6 z9vHP4r@7KmKG-4r4VP5(C5|HaZV3^y$r*RKe)f+~O7T;{voRO~fVWzG`-Imv zQV#`!H8z#6GE4MnU_m~Fe+%;g3kWy;_W>)Amx`MYtBJ?*2umNIHBhMipqiwmHC+`T zjI=0$k;i>DF!N6d-!4;vv~(IXUb!6e1t#X`zjbv*K~(}&cjHj4d)@nNl;{((ZiHRr z&s0tso|BHB-)a^dT@;lVITjQ>BO@Zlkbp>tZ(O0bJ&30E4qSI~>U{|!LD$=D*4<0E*0Uf%*8bXep05~S9YK2x zpb92ZJGwb}g7uYEb}cIBp|Um{0M66DctyJVibIkYs|P{BhkK)O4UTy?gm($&!99R4 zid|;3VU{u&>DMJ8x+66cSA*34i{u0*<$`CyPrFIM2D~dg26q1@3Bg9YfOV#T9_IbY z7i?gPtpyeug?YeIFg}-0Cb|xALr#0izOFz;;%jxJ+a%JXut5$V&<4dF>$7>ogc$asRO_525c?s{r*vhWXan&76?ZwUnFgm=&>E0LXEe7s;J2x^Y2FwF6H`w-CCCCo8&o~gFX=G)F!Q(%xD@DO#$f%Sx8`RRxRz-y$E=r1PHb2WWLJqyC%}&V!rgq)&Z5c8 zen4L&E=@K{o>=sqE+*X!7!SFEyX~l+CdKG4<8L95b0M1!T585=4mtie`S#X&4ATP& ztSB{_5UFb}%50=u@T`^+H7GP2Wh0|NBQ-bSa0gXJ+$UJu?6@Qfa&reK4op$1M~X`M zo`>vUIf%!hQiNwtkqvY=72Z(gJlKM(m?_7XGDQuL%@2=K~@z> z%R9Tl<{eM>Sb9H|G_{hI5*ZiB*AIWOa$uH(t9219{DPtQg&+B|MwUpabem0aqo+2} zBuXI?slEd@qD7$ftf9{E(j?X$?s@_YyI?GSePhu^Uv8&ma4rfX;%5U@s2b@J@b~9g zdYFCS0tTrXWVpuTZl?!UvYE9O+(Wby!Er#fG+^ zVxe`EH*tcB62n7j$fzRg>?abU;@}KkB0Bhb@RHzFUM#GvBha&?Zr%Gw>LeL|y(A?P zf5g=uIBSvQ^kIKQ1_9Ss42J~{V8`~IQaPC2@i~lK3JgQ6iRpMn5I1LD7NH6}9$;8$ zV+K}@{{AVKZkVPxX+WRIvM#D6oWL6!1^Y_4RvxqnteE5fH?{LnL2Flqol0wKa_HNL^A z=ws>QJ85ME zDQl=dsL_g%z~oj)5;(801`iW!;pvo=*&*XMhI0X?Qw_ZdyWbeH##BbHyDVN=b7``d z=e=vAw&9y7eBefvKi=?TgN?&OHMvi9SsA&-f5QjTxCx7EFpV`MTSAmHBjU@7Q}bdOktk;ujL5b z6R3JIehiZ0lk~{Rqj_*lbBZ}yUFU!w$l7>8tJ4jYH%q-hUv1IjX}qq@6P3#&mk3Y? zb2DLCV>M_{_!jN%mYRwdvzd@1nYs+VKamJb@d-BMVJ+betm<3ksZ+)>g1H2Oxx~Q( zQogWcJK{k83s~TF@?*5lXrG@8;dDV%SIU|;nqKI&T((4%iOlY#ZIGf(-Y!uyfM)lW zO3x+BZuKNLnQ4#IP9h#xXL(JW2f$T?PA0493%lh1o9A!Y91f*Kx7VgBQ+&c3A^3|z z&_4i1&IxBfsj(u`omKsurq})A+7(_VRt~!LV|ga<4Cp)@W#2k=u}UuHSqvVJa1Ep02Lq873(LdhFO zndy@V1$6mW|7xn`tf}x(;Wzp)^~^bg{B}K;^c+CJ!@qTMGz~SiMwiiac(y{0DmzQ0 zDYF<2WgPOrZqhEfdM;Jzc0~1-m=VDol=lrHGq&G@-FRuEjrh7!>^FiDMJ334>R*=E zjE{ZP>^_Rdsg7}Z)H5MzlH%yMeZ;gr;J2RZ2L?o%Jc0!Aj(fN?sDDNcXDtc{ z6e?Fa|3Hl^G|@MdA$nIk>{4Jf%Q%SH?e!{s_OQDo_G}^ksBHDDw<_JKA1~2qtw>Bc zWz%ylQ!9?{w4?ZL(*Nj*{t3&b=A(NwqA(5U34L0I31828e0@?co+TFHD3Pc7>$HVN z3*;FVRTq@-YTjZ@-(e~V0y*;#sO&7kc<71Vmc_v3Q3=O5f98v6>V97zr_KBdec!fl z$!RV>vlwx(jrjO3zyFZK{IoRjGUZb31eMHAJCJ=p8y^=`;A(pC%L4)#m6X@!;&veJN)Etr4F02WYZE@mnhF`ZNqG=h^;j7s_y|AOnmk405)x z99iS(__6vxNfs3-no1sCzIP)kN$W*T*e*Zfex|K*i%}u&LW_E?5^<6eVfYQ5ppR+m z2_i`i6SW-LQ5Onb#-8rXvqwt92IjQ}bFTPW#n%N+?qTm>jSC`?$SS#=&dTCe0?Y{% zCRAzn@o5iY=~jVlL2XbEy^(@%UB1y9DjcM!IQ|DW7%fBnEF?{};8K>;R_K^U{kp`S zeVP0W=lOYvodHAk_T}?Yu=5==sATh&j2iayfDXMA(wx`zBqoPj%t!#CVcg+#(r?dS z(P{|0U{#MLe@v7sMrO8bO}pc83g~4}v_!#@e3C1*@U6)G1|JJ~IcjT44RfA~s4{F#m)Bx1zPsL+KW8d-TsUAi->3UVTYT?%ZC~QE zL0OF)WoP$!q|RlxO@WPX>QK+0tzWZl*gAo99BAXDQoLT;-`AY1FBC+hYflqfn29e~ zMgq*NAZ!G&lGm@_PvVoUU1#9BnB*4n+YtpF*wOiJYBrf2ms;98n?~txRhbC7j`#Er zy1yodN)B@$BzJ%*W{%kt3g<1`I7o8MFWijY45s?6zl@ml6Qi*B{sOu6O&P!2JYI80 zul+Jq<|C~s4GoUX(hXDf{JLpUvVGpeHM^!eJCbup@qN3Tn6Ssw*~Jsh#UcFj#5?HV_{Wdj)Kw*se~keJ>D#N8FJo!v_p0h0=3AJ= zkDd0}nfB4fMO~TWHoS&q<6(1JC*>(pTur1rCCn_e(9YM~AzTiZ8w|MBX5MRNH1J@# z9eghry(OB$`e-W9zrk(v{if_aT1I9yMER17Ys(!7{UhAnUEh&!kievj-!P`r>9C_4 z;GvVvZ2tY)jgYZupDS7Wu!~?z1a?!?Fyy5l-$O88gNspo$AMb~7{l}@8?C3}}F zP{p*KP+};mEi`tpN*^8KSQpYy;Jak}PVizCM~O+<_OMLwuG%Z^fjHSYS@_@B&>5PQ zUHZU!bf}hlbcJh=dab%fyWt#u-#6bk8`!!Y@3#x@7AIgK@At!%7l#iYFNa%eQ4cQzbu99W z<@NOCOGlNoN%#pul_e@JtW0u?#y`H7nmvIny*(9XW=a8Mp*2JfBxTNK9C*mkru2!b z0XWIY;*ZQ1fW|h4imojP_OWwcSNX}+t}V?+N~y)nVTqL9&0M^aCvzO-iP91L zlzCnBmCAyJ*)=`>iJ&s7er%?g;3Po=;M)2}GDVzS-lAsGDfulH*eB1H+JRh5Ec3J$ z!bxyQX*ekXSLpgF(zX62Lz9n7KHNYNw!yXa3=vbES@VmI8d+Tkx3Ea~)KX!>%K1Tt z2uIcfM!mcpVogK`xv1MQgy*9(=Mcxmxh^Tt>)3$FpIL#V$KO)#W2_nBN1Sb{wl&xR+uZi8Mh^VdkP zDJw31Q(r?mH+i_2qX@lxp(WKCMTDCw=Aqafgr*Y>k$d7n2E)18RE}kcJbn~~#xFI^ zK5z-BO$~BNuJBUL84#eV{B*n9I9lWJ-K-j`SIrlW5b7!l&w)si%JNp9$^ohPV#^ZE z=3Pm(FpD@CmFqv!(2)Z`rVzwJUfYFhx?%7&f9bm#l82Z_#IH3;5~M{I$4zBW0uT^y zlK-nzK#Am*6)9KmmsfS6#ZHXSq5s@njRxPND!gXqID}S9V-eanQ&u!>;Z3RiyhwWy*UX; zffe8%yc`8FSDmH;0qL`LHXbqgkp|0vPMPi}2T0g{}%q-c2P)=2${W0}2 zxgr@~$z+Kl0pkBg8VGfEQQHs;a_|@;ZN7s0>0@t$;R;3m06L?Go1clKatLX<2J7=k zN1FX3lCf?y*oi4%~f??|JmOX$*HA+O>XDR}rX<#R-XZ+u#s=<_?GG^?rb zDizXST>xhFN79ix-a{Qa#gj+#UAZh9v1)?^DKbRtLCtt)9DTtYe;KN+H^g-kkry)? zSknG!a1+n6BrVars4P$_w~dCL^rBDc)O*cOnQiF(Svp@gta6)XBR$u$o$KZCPA zvJ#}BoRV7nr?q7x)b5MEXjzb6wFjF6J-jkiqS8J*rH?<<#p7-64nyCC%SFvq|2+6| zC2ZYmU6bEoS3np@Rm{_k+Sy=e-dPs0TwSK>X+BzHgTZmJt0 z`M*i^oHgdHFf78R{YtQ)aGfG9b67)5E|kw~L%5G$B0=M(AVLZ>=hiMVM&i^|Bdc5U z_i{^As@$}MAPEM|N4c<|3KnRGrWhxU63pvW``Qp06}1^&VrHwU;Y;SaL2MjJe%8<*OrNhI-s*23`{oRVD*`p-u<4{36gav z(Smk{Snt+sCBi}yF6Pp1j>%z)LfA41dnBSmAgj398UE_c~ z69>$;V1z3aNncV@0sRAN;XLkrnmw-~d_cxlqEt@^*8;|PMV7(k0Tg)OSgL_hpUBp6 z32JgRma2J33}-$&E(VzLocri>hse1?tZv;eEG}><0tcY~GIK&fW$pJ!D!w%oinKXHaBqxKc>?p4;TC#e zC6zeR#xhfFz#&^z6f-k6DD!|nJzB)RTN7ri-JR|f8B34BppB{NhLfi$5e}Y=J*NL< z8SxrE%San`%vQFts+))vC;v}8nvG-LaR-L6Yza(UBiUH__C;keP4-v3UNE2`HZJN6 z;%+9*?{!Y+BB+_1JaHzbM#)oTJ$d~3WmOJs*Hu~hyM~RW&*_i=b}+J37as^_Vwa-4 zv@vfIZZNm33|jF(l5x54!phJIgfu16gaHq(L9D~DuBih3IyJt_B9QPi1Bsu87v+Sg z7oh>FWZEVbApyzFoqpO#Ob5Wi`{sCdt^D8)3yy_-?VVBk3jR9P{99#dlg+;?P*w!n z7goNF!uR zh@zToC#H0dm)T6Cog+qK%S8zzXM3ES`{<`6g$iKy- zPIO&vI@F@)I(*;u_v7FBO#eZvm4w!69sc1lQM{Q0=54SONU>kuf^|A&#dY+#vCA0K z56TeDeR(l&5ME|m7m)^p2`T;V3GVXtV*8BCtGzQK$h2ZCp2eQqIqc}t=XYj=f~>7AtEG3eZrJ(*53e_n;CF{{QY2!|@EEkO zYY3T^z^JZ(anu>i60G9PNL}!LaK@`)|RKc z6^bk#Bj#*Q6;-A*`8%S0Wn=s0l8Ml7$h-|bj(3RY<_$+TO_sl8CPnM02M=i>^4+=r z*jm!rXA%MX#EKzkoNwIruly9lBa+G?cFU8XWEj$wxaKT6_yt z=o7Xm{K@yq1f5VI?v{$kqSicE7FaCY#jE~|DPXc{D}*A{fv`JMDtoqo6)E{gcTK%ia9}aY;3>B587$tuTYGJ~;6g zzk9G)!-LD6rbEs8B9zDmk}8otDu?G9W&cnTT^PG0 zfj4&H8gR&vAESX$@vF9nxb}cc7UxaRGEqL-@@<%aj{q7Pag#7EYz|rH@%}Dn<2hGF zfzJSST9ozJ-qQuInwsl4+lCO_=T2~>&B{jNd-DwQ%5)^J*SnOBs31a|FF}JkBhuki z%6spcvB4;$)8>5{4e>_a&Q|`Aw68|!0Qdd7dASXz{LyX-!Cb!VeYum z-L^eTJ|iORxM1bw=>R1^uf0WjRBjB?SVjg0^3VkPFJGQ??)W|PN^*p8jDVnuhz05c zT9he{MF!VttYUIz?`DCQg7r^?bJ{11mJ^ACx?-tM>quTzt>Rmiq>$k{rB`!)6S9IH zmxz0(g@%c0$9azC6v{ck!y(^bD)+GI5_4L$z&_2)77*UfNxar6{g>N?Fg?B#k>5Zb zGNAG0Vd1AGr#L8?($npoijIP}GVRvBI9>Z8lyuA}+hU;pu0BHmB>;Zh5_EjB(0mTsr*o&>cbPDtwWoyKkG;xpowB zbcy||9mJ;x4b2PB&*)Y~Zd);{tqsU+Xb_7bVh6S ztf zJS`ZN_ds+y=pJ5((lc2H+7T}KEw*oq78JuSYNBn8)iJZVLJNfCnpa^;vlJF&|(2fI!s`y%=H6mQN^{pW8d^A zFr2VlRg*Pk`|Q_Nc4@#ad4OWdNFjsaaI)xHXPF&y92x`#CHz0NfKGZFsok4ErN~97 zc9+HyMp;obJ|eH1&t8Fd`gc*6-}ZNfC6+ODv&bCpd!i-gH>~6WDVbdmny`V8WUcN$ zG!3(pgWfeQV8EiG0UV#rQooouIc>K%t+uxDKnDYUYdJoA?BoanzknC7*ExcMf&E7U zx{>`0EhkI!tUMw6JJ;;1-s)0A0;Yr?8{NGIyvc|8F}tVPDOWefCf=i|3YehhCY8n9 zzl+HE=a~XUg)FhkJuO$1zap&D{X$QiI*nPq#`rRiD^&nhe(vgBDCrb`j-jKO zq-_4bMLxUbAB|ruB_XNOG#h2?h(rtRT4Hc?(cHX}7CiC(l9E6vro2U5KB4Egi(#5j zp&s>x*wVnmIHE306EY?`<_FDPxIirL@n+2~aE)<88lhCL!TSw-)n3!Smd6T7z-EwT z3SBRT>zaGl5M&x=Nb@GeBEgF=_**jMr!zF#qN%?UB-**x-m#UK$+L!XYM6*0+u?&; zooHQj)NiT%av!Vn7fNT(U5FtzU4~B`9t@{yPI)pM1C^)(Shk-qpt!B8dB}Yk&9xmI zJ69iPVu74bcj^T($Y)WH>C-8`ItYbbyP9lWYS;>7o5)%9vmmE_7v#*A9o9O9|GN>{ z>Td{mCHpVr=Nk<0{-40$M=0R!zgdd^n_3k5pAcXnfcwAZVybr$K=QxCks^Tc|GY`t z3y}Tq(5n}q{$GveN$Q7VCJG3My?pA{5P%Oj7QgdX^Fc++k|OP>niJY7Z4=yaWxdM5 zyJa?o5gI<=_@95G=P1RHN$N>*(pyUKd1ykn!O|e5Kxh-crT_1_rlHwcR$u%lV|GkT zR!Yyrp5;+wmaKwTKBdmPso6jz04}DHnOszi9Q12}>OK~el!^|*MT3mVQ#-qcFU5TX6iwW7GU3zQxLCDacCk!mbwo)^-rW~7_msV0;{(E8wxd9wp!jRu3H9+I2FMi}yyv8ZqRy)iUz; z(KnORUww7Vx%J5EszNXIY>-4Ivv|ij^Mx3~6yR+VNq!?jZXW>N_`+f)Wn9(iEH|>M+~F4L}Yqg-d%lcT(2d zX~JqhZ(OCb1bd>00yv@GwN%@l#DTJSYtq0q=BdKhy^`?`spJG$gh8hO!9uaz4Ql>| z&V2uy53doHX5@AfV(QgxtG}dF(-;`0q#M4fu)Jpb-984%Dv2(s29u^zPPMN#FB>A2 znHpDTftLJ4rDBg?-arZHc+y*I`nW$dh{);g4jRS=lE)uzphkaVLOUQBK-iX-a{JT( zzbU;{Z^@WXz(e4i_qOsTG9YQU$S?Qc!`D|`<0oyb+8W!a+uCfuco&>02=~8w{~OWRR#&cGASwSt)B$YY^X3G?Ni+y<0C9Kg z#%oc?b(hd-goX&A+wi7^mk;xPzy>4-@u=GPND!u|%Gb(CQEtI-drm-J}>R;I}i@WRRWGlAtcR?`d;FMcO^)HAH<8}Exu+L=R6s$!?_V!Aj zaJ$p}HWZCjuecx-nW3LT@c zY3Fn=uk(4o2>qLq5sYNLG)kt}=;s4MHoQB1V{OK}b{%LsnSIcv2jh?%?)5HJU0LZM z?emR;DkZ=LAhHKsv-EK2YBiX_^GRl`}bgeEUGnT zulaF!B-k0u73OWLkZumIE`k9?CP9cN`$?{DI}2OQD9~Oex3``S&EdJaUa%A0JZ=%Z zsLl{N6n0Ns@G}Te@NA#h=-#%dza;FKB>!koRH8aSS3@D3I!h9zX{_)l{Jm-Ch68f> z?B3*wIterRbVLO#B>c^@^5rCg<}eZPQz^CFH14FKl-33ehorsN&STYaCC7x(;VgZC{HjMv}_{!OZtg1y-C6u!9H1?hA`1vOdF^$>i z<{BC6HHZK&36T_cX!gX#Yh1D8tZ>2jfwKA`r03^#Hz^wamRJ*ZMQ$+OE*J9UK^G?m z@k!bDcK#wEt`I{O8!rtFV3ecq_>faS-g}Ak*NJFp%aihK`j6S$_^jycPca}8DwyC( z!<4_7Qxq}|by>lYe5c3l1w@IN)IvWO{U7J8@g}u>3@wQBsA^MpevlOrrG&uBh3(^C z*9Zy@%MAQs8%g-4PLaedmP+j#fqkr~B>ReRS3e!%z!HgFlr{x#Idj}bgy8Ljuo{CC z85Y;3Shw|P@ye_t&_qfPH06M@CEI2wk4H8_oqS^X4>Mece}7w7xFhK9;|YC>b(zX| zG~O%_t;ESY=1s8hR9<4(*=1kda@?hemLL$xtNg|(g=k24npZyT$s0h{2JE5~Go^*- zYHXZwggIfULEK`d*=h-=6W}u%uahH~tE`W!kUErJZavt$HVw!=c=iIXRG|5zt#?-5 zG5Cd`*NI`5+CkwrxpzaoYs55?ORLA8TtTfNMbBD-uHRj4${Nw;f5G6Gsc$*#&gc%! z_aXCKogm8wWX;{5QPuxkt7XKsF<>fNPGDPBmPFWvt)0+!0t!YrwVw{r*8zh?I<>9`+(;J5dt)d@ z*b3$rn}O;1I)KBheaUNg(6bNJ#Uu4VQ$g2k*;ZT4N}?MmiC=C`l&^}MG%__C{tu*5 zjC5a5I-M$Hpr;`%VVoKGw;$vnH+j4`R7gZGZqJ=N4c}#S!MuCgo-A6)47$&5F79a$ zp5RxR?5{K{fO_ONMN-#O@}1K^&Z*7itBQ9TXZEnKbEsh3bOW@F9#nF1tcJ&}!={;F z!s3%@`8OgEO4%pnvrTPsUFG{4l8{t4f0cbDhy!C5-UT1&ep@cDF-YxUlLY_GNV|k; zH%8;VYi#MRU~Mw}QQzxRDfg_G=ns(a`N(~ zmK5zPD@~^Q#k1B%l5Y+Jk2hOQ2`zL-Wt%Vx6TDMo;Jk!u2o$Sa8{94&Vd6;J;qcH~ zsGKYAw`vlpIsv~%f)i0)0F?Z=26#2R(NQcCrO@GUBPQG}j;=Iw8V@gPKzAK_ zgB&SPK9-G#`Teaznl+rAhv*{NoyIkLH5{@YEnY#EYp8&M^5Ll8>8ecRnIqlTbbOA2 zhFg4({_6uLA9SM<;fdCE8Fq1JaF_#D%L}4ad5^HB`}g>_aI`52a?Q}1T$kaw!(jK> z1eA4-7iq1=czz7xmMN)VEBs$V>oO{ihVfpY$Y{hm3M;@qs(=@#j zgGP9d-$*bKbRD{rq7iSoWgGB`n=O!1`Ziw^+-nPNlj+aDIGi#oykt8KM*`_euk?<9 zrtTeUX)2wN?}QQ&ZNxJ;=oE|)zQe{0eUZ!-^MAoBqq7|gM^S(ftg(iy^DbaeBNzo* z1>!KER+;v9t_bHmrYkp@O~-TWBDlERJR3cNYr5mzMiH--#S|Rm|7`dYnuxO$fpF=6 zlXKul((qs+sVNb@>E8{hf$Vix3PO%RgfOrEAJ*PBWA~j`@TA7C06C?|8F*oJ%bIp$ zzQ#5!6@2hE=*JZPpzEJg)yYB|ixMOzs0ZND$fY!gppCXXqJf1?hnYd$#5J?n3@2#M zbx)dTE0o&E;B5-=Zzt--q($x!3I+8H4GBWp*@iq0uE?yEeS|8MSbnnbdobxhdM?l5 zO3+xWfAGUh`RpO(`+Np7(EfH~)R}+CnTo^&J!X2LlFdRsj<-3-E#o;u^8Gi>SX4#b z>bR9KSb0!LCEYtU;fRVYg!#Ixhr1GO=;2s8uFIA?WY(!Z z1k2_JA$sGrV02t3@ys2m8=Y~W>R@PtLRUqZ+Y=#zGZT;11?jL-Q{Vrh|U^8XHaA< zh??if`#aW)G7HaJ3uo)91~MuAmoKpWjb26LReho9Hj7Qx^DOPdT3yt@dH<++fA4zU zGHiq_RsjKo8>wQ8UB+qWKe_tsMbl@#qFYnhMIouD!9q;*h%NX$1YN>T5K=O!0d~8f z@9iTYFK|D5ta`zSErN04(=c5`4Mz#n{Bof(Hh^m&9vtEB7ikcZf)$!=3T>uBQ9YF zcQXZW0b6>iYQ0CHvA>`$LnWZSuM8dLN^g&F+4*x=ZwdI<9Omk>u`i+bMAtb4`HM(H zyrt0G$nw0K6yyiluHMg1nG*JV{k(Sn6b@NDaR z2v0u~o>qXUw_i%IQytZR49!xxk_OID+i^*2V$3MneU<2G0T2*SxIR>>Cyk5XgI z+ib@6MH&}tmru1a>4*h3Y0fF5-U`7xHUxMQdQO!nYe`~d89!VEvH=!o&+Wx^pi6^$)A7`r~pig^#}t6`}TiZJ*-CDOcu9?9lYE* z5zzr`SF{EBwq1~?bYea}Q>~z~R*g5#yH7yWpwQoIw;BAr*I;1Yeh4VrMDf?%Wtc>; zN2vP;A4|u^Q+Y!wo7tTqrkbn*NP&QRz7*!$J&azyqtp5j2}9qj`+`r%6vtC?1c!!e zHfyBvblA=6RL2Oxw|BPBXVWV%oXoGHe}L-I@0saRO#uOd<5ACs-1laM&;|hEr*Hq) z&o6_MN%OPs|Fz?)Q8;x8Mg#&f=?4PB@UMXDVCBGQXq9?8^smJ`VT}vh%J>H$3QC5p z1=KJ+An1gVD|m+^%W)U zy$VU)>e08Vu{!eK46!$O=r+z3jL6qf7k#XNz(}3LO(FLYoh%{vDo=jLkD=7CM;I*m z#V%ja8Z6)>4ffihAMTzpRiJmUx-EAQ?e&&GP{L!+u2V92O+)=sWwBO`HIb~P`EE`n z?rr$2kIR$^Sg95Rl17%;UBh?P?p~A16FL}_(95$dw{7Hy?cbqw8eCKuaE;V$eoJ&q z&!8NZ_Ca93@mr<~XdUTPo5561sPA0LD9QVhTg=M_>+N4mROlx&UF}GufR7F$bTkTP znd66aXn^C^cLgyF5f+QIg2j5#xdZzAd~&ck)|H zgZX$By0YYlAd}$-ARNh?&>N?+Zyej}6%Yb4xoYOZibnm$hU|TjCBJ&@b^X<@;wNRF z4Jx5y*=CJTt#>_?3})%WEwqaygp*G~KwEvb9}0Bv*}{h>K#NwXg!&6HE4}(YDSYJg zF!;ngVF;{Jxi3Y=$sRmOATpFauLPMWt| zMs|EOMQ1+0b${tL!LC-f+B@-A~nluc+i&^5o<-PdE+)Ihx+o6y}pTGvzRT ze{B99+}BMjuNle8woxkPyJ z&i`x|K>*qAf^>j%G-@Od+ve0Cg<+tap_yOYh2x;qJx+bw0H|Y0r0JqwO{7Dv)=E_bbuLaw`%PGoIv4Go~geV*jv&c+EYbRS9Sq} zU}%h~&%1zqF!}J*jXi(?)N5pgS4WH4-Sj@XL9=MjBh4P_Pn>oduf zAAI-E0vOz-e?{H};+n1DB?s~kD`UgF|6fj^Hep zjt5V$xxkCd-T~V6oQxkvSJ`i16pK#Lbd8nJaBI#Oh@i%r{sw=O&2s^4LLj{%zt}^y zCJ+_QOy-Ps^J9j>hzCph5P1wQ=> zId2n%+3|j@e5-=*XS|NN?w{U$37K>TjsP>{T2vcO`eS1pQ{3NeGN9`Jz+^&zpDft| zFe@o8-@U#Zvpu!zyhv<=3)t#<%00gzj)9h>MPybo_8^J@2Wqa+7t6f63P#AJIf?Hb z6AT!UaPa*7y^)dBLKTqf{q`adM4b{K@cOr)COdc_EB&G~f9a+m&!6EwScFB;C>jfQ z=4!$LBpbx}|5TxkF%%_xW~;=OXB2MQd*K%+NxRg`Og49A&1)A|+y`yC20W>RQXuYu zWFwDtnls2(AP7M~s8_XJEEp>;VVUf!5@M#bfln6Y(uOus>|X6oyxDGTs17vS^1gVu zXWDAAW!`SQ`mS)lJ3nrDm2%_c6Id=()lj<_r)S}l6^gyyTw|cImVBU zaB`Bif3PmcbbXdTMd#LiCMo`h8%vvI<)_M@t-i~-g|)sUKcUnH2x4Uu17P6IiM2V# zb`GFXb7P6mLprNPysuGgw)}%AL<$$u!lc>SJHu*0WBW(JO2lc z=(>G?fvYS7H5a$F2d|^Ry7>BwiHQRyAUW>odOzJeE~BP%<2sDeTpPw3OrP>lezdzn zTFECcKxB2V2Q{>`jBNt_~9I=Tr(9836Z0s#2IspjWIySG7H$e|rufb$?Dq>#pc5Zjn z1y;Z}bX`s@If(60s==P9#mF%x|J~vtMl{P*w~djgr?dJi1vC&f2ENUBPCKK)bH|H( zuiecY_kq9DA7y(9U*56L(JDd|ci^Hilvjx-Y2^_^s`m-#anw=7c=FJ71xU2-SK}4p zjCnCg!`7IDw7_HKKiY0-{ICr_sV~G@B;cosrWie@5cM`Aie4Az(#{UHFR_ZgQEljd zk-pw^lNIpGG2Z{12rOkLT5mdtOEKvYBWz6UgvGxPcZtvbX@g5$FSC>t2j?b{)-XLDuiZTAxGo_cym{v+U($F-c?CG4gf$E@%x0hb&*PeqM+z6OZ zlz(hT;p|p7wS;=qMJH&f$WELeR~*n$cD&5H3J7I)<;yu+1uixlXs+fcozW+lI2@($ z-W()v7uTJJUL@>MAhBBhtPpQj35bEtg54khNUBl!XZAf7NR?rB0-K+}bnIHvVKmlo z;QRU^ZXGcol_43HmO-j(FELprEhk+M+Q=!?&thCu`bpu*DQ}U7`&_|(!FJKuEuR!< zKLuB&#DzHmfv}~82a`pgRlnD-M^saIR$f6z++S`@0Tf)={XQx3B`^@y0t_rUC$;q9 z@o4`jR*PX&_>wPYUSv2*er0j#v)T*BxP-|GvznFn7syqi{a`0H%jNJ1G=reL0Gl6> zN~1iEbJ%Ivk4^_pT0NS=>TMkZBRca$E%0ghAUL{dz-(K*1?t8ryhN-)vk_Bi*KDbN0If9T@s$(cWmfD9`&d zYO#a`=fJ{gno?0+P_ueZi8Qu)heD<#Sduk9ha(+yTIktmfOiUR-d~jaOx8ovkt0O* zzTr7^U`Ah2gb61@I@T9PTpn2s4}8FhXE_Tu+~B91S&Aw83ojf(Z?y&qphbV1m?t#K z>v?RO%x!`*OAdLudo;=m4?beIA589_Hg{2+%P|LuMNNBU7AWFNXp?2s1)Kfz$t=am z6)hBc*rcRvKG4wPuWX%wVzH8ptm-75l^qlWkQheez@fmWe}E!8-f248-ia)|Pp23K z_Jtd{33ipT>2lCNQ!u~`%}&4K?JZ0x{!)xDPd1YatOp7)1tHVKh!zlB#!e0&Xr9Em ziswEsHuVuz3G${5efRh!mu97we?9BAEkIrw~3At}jO(L#4z%`#)5jb8uuq z_xFQ|Z6_Ps$!25Qww-KjPHfw@osF&C7#rKR_2zk=t+(p;$5eGqpL=gtpSp9V`<(uK z>k43+L79Ip98K;J5F%Y+o+NOQkpOxJanxwH39!(~HOd7DOF9uH(6f8NodsS`NhQq% zhx*k7)|r*R&&2zcx0E_ClgwO&B=s%~)(!}#7Togyg^Nwf&xRvV zuyZ6qK}QUf$*T-2u7k6vKqATdLL2e(mpN(8JlKF?J+)XNfl9!?`Ew#ezAi{(?utw6 z?uK3Kjzhqw-C3uW|Cl7l9)V#^TSvFowDAhbc%O{#|Wq!>g1V*vj z4}19o>}v-euVG62D7;Y{#XuP^lya0Y{uYl3k;x+qb7!E7-i9ffZ^)qFnp!BHX?s7L z0mi}%f;@;J97F(`DDn_>%FnXI0#z`BlL(4CUaihZh$G}iHsu^=2Fhdqa?r%$K{bjR zah3fgUE*-?oR~K7PX?thtCkD}IhSQBAQcH)0MY~r8&vbHwX(27odQ~X-{Tlpd3UsKcJdP|RYD&CmD-cioJ6COZK{JZdn$ZpXp{zSdPOe{79h zwF;3EB1=45+`2j2OrjuWoeHl^Gk|_2ER7S^-2WUABf%&wVl!UTTNjpu3DdV;<7opgrd?A?Ac#{x=Kgl<=-FqRxd+SN2dFxa>tPga zS{m4OCY?;NWCn;0E<+D}pfpdLupaEo#hZ6@dp68pNC>`xObM!=f+I-gY{+u?SP~8( zoA=HWMdKItt9`PQfao|K!7vq)+f5iVh!`dcL~j3($dZ+fs*0*y^Df!rdx+XX*C=b) zUIYnq#w?pYs>(hLqB`yMtLAP6vi4hs_F9UWkcGBbQi}C`HRF9aU=<=~*#+li2zt-P zwgfs#t!R45iSLJIy@=Z+7(5gvSChdfC$WfI*)b6^=bir$s4y0VOb`T`;5`x|npj`& zQrHiUA3spxWw4?7DSwnH4V2b+IwfzsF@#GzOnny}-3cQz5Mi0Xu+`)2x4e#u4Soro zopm%pvW?b3I*R9a0b)}83h|dZq4F7*=Pn0HGcxvK-YlMdSDb`>@>gBS9TwncH7M+0 zeKsnwgEN8ihX6mz=tI@KN4EOuiXq|N7MhD}NC`GkJl&1>T5)>O@IE<2bw8$>Go>7jIVETrVk6HU(p)26@sUSud`DFYl)@TaBwV3t&oB~&s@maX$_;w z*=`Qs{aMxuG#;8(6ME838mXy~WHtu~s+W*y!1FtkkRWzuthR{9Fg>ZmG7Mkh$fP{a zRr?FnIv)M@`NQ~II+NXP z7L5w zL=s*6&b+lSpg-?0_IN(2CJb5quj{H-kqDVbYM64%=xX!XCu`?fk#?~2zLU=y#}8en zxO@<3O;w{YIf)EcH*Wv+R48!`Tn_x9&@#CX3Y5?Bj~`pY$m2zg@wrzxiKNnghvr}` z+V>(#BPzg>Uxh0INKrD^cX##XU5X+^0vW<)9(t9>2qs_b(zV~=|03oPtJ#JL$a(q?#I|4 zfxqBf<8HjoX3gmu#i5kfL*!~+7+)T%*3vX-sm(kKFS~u`(Y)=9Oky!v{oInA(1oMu zlXt(Z{*j%O8)~m2>|ddaiOYNy7iwB-3TaeI0UJu$t|m^Z$Q_Z!WlNNkXWaPu-DGrd z1lsV@n4x8Mc5|A<;x<0c@u1hRT8@#+1E2Tws!Yp^X?{-_xFo9oHL01E*IC^*VZPU< z%Xuuyi^2&43h$bfj(HRu`8Vqw#$F*vh-2Q78aowb!u5mnvv{zBaOG45Ar%f(?>CCTLd>Pxym$WGMY$@gRsO4QzLM z`f~-scksGUJE(BoD?N2NUFoqG=M1LoGTCo2=G1^U;wip2fJQmW@mPU)l{5KJ$(_xa zb$pM_9t$EdZsvJ@{G2E1p19kJvEG`N$-Bo$1y+HyM2AX`PN#Sl{2TRx`57Ct?PrJ~ z<>6G$djCgLPv2&MM+^5^g?WePBd{vPk#Vn4p8tJO|NVv9$A6LPS8DFZN#d06?N}}F zwsOilS2EeoaF9^h(wJPw`H(Dj)$*e5cAPIEyrLT4b$Mr`##Z>ZqQ6i~bJw#*J&b2u zp;1be;Omy;5=N+1$EDfMuG5b|NO4NY5@fBB6EK3RFko{EjO>WY z@b5ZjTqZ<{B--dPEGOJ8lliL8`RdD=;Cn39Q|ES}0ck4u%MhNnI?|!wFAV&qDuANbJfk zGIYNKtkn15_RnPEAjT+V0pKy-O@?v-c@7^S7Q(QW}_spDp;5Y@>{Ch?baxSHuENp0#IXjc(pz%7)PRBTwj!3)FaN%9Uc}bxCU^X|$P+h8dowlDG4v7moSm27MV!u|?vB|ckV-9`` z$EJ}NR+78;$}=}|Wrzj(z#H8-m>HR!C{c}@UzNoYL>KTlj}(z)9{1H=&+s*DA19^C z9Dh6Kq@#NbWhaan3&4#bb>S;FYektr{H~96f#ELs*pXl4gHubu;^LBKN-PQs9OOWTTmK>IAx3Ex z$Rx}_`F}rw8Pk#H7|(>EI+(j>Up*yC&T9CC3qe`Tk zC(n3M(FdmF_U#es*~xbI)+_OGMWQ(di;UzN6znE3m+vTiACA`uru|@>8T~5a?S=*J zECT+bL0M+-f|Wg@Q~CgfzHOyHW~ZFg+7R-2M|zyQ(d2uA$U92%-A@zsnrBk30JXNR z$*5H>zP25;S7J%Wz%-m&Q*!QUKu;RMN+cya%YYmyghp{-K;_hKs!hyK6+Q_7e7#{Y zso2U>olKxZ&&sw(p1`4?|6A|d z><4)J$20Xq0C4@&LFq;T)cuc`FVOGsl!OEV!o&;$!kD@>2u|5rf(0Jwj= z(l#c*)W2pFD_|e+UtVcTQ+U187snqIBNg}qzzA&q(kov5j{K!p?4ZmiBSb(hx7x(M zg}BND4-wM56yb9XpB!(I8BGRDVmbnSv*{*&65N#C>hcFlyks=gZHm{8hYLDD-($?W zgtLw%u)3<&koFFj@BIcwPq#B9*Hn?2zD9)M4w0`hIYL|zIZCXc%%nR1pwN^aZn{G% zi56%f4n&iR-8L=`rqkpLnUYPujnCE$r;+>KIc`FrPS<5dtAlMVa-kMV+S?<~-IcPh z->C-8@?H2>myd*YrDM)?ziS-}ZB{(SGli15NK(e4>cllGwPqiZILAc@gmfZ}xhM50 z7)rqmRs0Esp-a2mG)P+hCo>MIBjH~`>>glP!qw%^r$K)Z^=OvvAWTk_eI*JlisJDi z#PykSa;un!DB_r1Q9$XX;IXGw6{ml!X?LKQg9Xhr>RrZw(!5mQh7?ui4s4$hD^|AU z32MN%4;oP%%qmzE^D`XgTs^vi$@alpb~3Eere+Y3X(vMAr0WVIiQie@&Ac3C&m|T&?Gap7&I@ z8m4>Fv)U3T4Lc4-vFByOZ$gEDGzf&SW!bZx3WzE3Jfb<9MQbXqaeok@F#<5M0bv8C zEAtVROZX(Un_Y$IvpT#349zLBIq`bRnl$L%=Mo<#Qz{+9E?(6x#U24b3SZ$`3nFfh|I6q8PGav_6F;Qn^Fnkp7**9ldP@d?V|bA?b+t&9`B((D_RlpnahMWgDs;_rrtV zrRTQb?gl~LVR}Zms}KIlB?`MJObwZmU>(fjGdGBxDYx{#Fmcn#}J7lM(2 zv(z`-(im8aiVB?|Uw`R!xmzplTN`WplIK^AGvO4p)ssu0W82Ty*0J#nVr)?nyXG%c zD?lHp4IJrMb=}YfPCJUAmzNYs<4@7CBE9@%)I>7R=17KjhmOz<7gq?G4No2W?sCod zDy~gg2OPddK&sq8VB%1gBABf6I)hfmkRMXo@>0Z&V?4*ZCvPdpHOe*}5&$XOnyp`K zZTuB|Ty&g*(&>$(P)X3)2|euNZtpi9v&}mBg_4QjDV>W2QU~+4A0KF23w~#RdzuSf zV$UmdTUqIJ>O3nC&HicdM+yNiB{F~UB#Q{C#9O1q(e|tu;F!LHG3*M;TVNi5pxxSa zPJGPQRJvu?(l=Ar_YhUL@?+0$_;a-yh4~`L6wa5jB_6>^h~Y#$BU3d+MG)L zd*=i3u|QW3aIHmj<=(*6u8madRqAgIz6zVy@9mZI(u4;rJf~uD9g^p|5YZA<*~+}y z$oKxf&7AEE=o=)l!&KtW31W5D&(kLZu47X%qR?7TEc3}&v5TPXULIEf?^)2R@!xzj zL_5~Q=TG)e-@r%TyxVgh$ZdJYQ^AhvVqenPRo)jU#Qz&hQ|ODzp@!_umnnaan-)zi5Z006_R>OQi}}`o|Lo=mHY{ z35{jw0fhds!naHSxc>}3tO1z_|BbT@*TyG4Uvf5mIv^l;|BbUi8%rl+278Zlo_D9! z?orqxrQuU$wW0BR}8 z#}+3ir_?bG5>Or4ErJCBV8aihWakiQjd%SnofZR7ay?3Xmn$Oey*HI3Xr3X@mE#JXZZ#!}51wai-kIu3k}5SDiFA<^5DI>eR7 zxM?n`Q$02Le5{&vc+Hmjc2`PQ{=AxNN?iGif!3D?e01@$UCZVNt~C`u22)ZG1v40aESqeDDfaqr5_;ODmOTow4`|xw@-dpWI8mG9 zn;IVuCg-jy;BS2sCGp#a&U)i+y9S$vi`-vszpY3$Cl3tr2+8OtohosnFwPvD`PDROkl{q@bO#+dRzj=KL5&*S{a!cpzGIssGN zyFU}KM$FCx;Y?DtC9=YQ?731A>8hDFKE)t;g0> z!N;wF)(4OKZvXJY=6Ro*yYZ%d_i?rK|eXV?*#wS*&NMlZ>+j}fl@m9YCM5HLr zm@YB=b8~>J2_lwCBXR))q;BlzK$;F(1?vFUB6Oof*2DIEbL#|`;!%A%=B}=5Tp|jp z1D3-w-zvbZbH5x4#+htz3lj$Q=q%ngAU|rO2lM-4dwDijV`Hb`Aj5xi&Y`@x2sLyU z6~WnoE=f4__^J3CtaqLSlf7Pob3hLEZ(~&vm``X4HKrYoK5t=}v?As_&fPG38ZSU~ zN>DXOCI$w39s5p^XiT3S0gm{Cmi8Kw5*XyMphU6!6TyUeUC4ns71tIOYpZZg3I)qL zILWdLv%=)~#a2wX69 zXnO2Ev>=Sj{J_s}(KbCRT#`|c2xpAPO+kvbr{BSY(M{Gc?S+t~my`c=efS_@y91+L z+(8JG0R~iK$x2CN^-u~k7?GQB@5MjG`8(As4U((E<3X>@t7tA$IlLUa?c9yDV(}Ng zYOB$pU;0xL`n=ygox1M&7qE~$0yVr{`h6WoCBS-$!EGQdA^|9Yx_(!HFk_hwZjZC^Zc|ym!f$(EDAzf^7*c-@|_My7=*6`P{j|AID_0Okn$xNx9c0nsJcsA)D8~z9aZvHWfl+2AkE}S;=t8% z8r38NLdSA1-(XT6POae7?=TJ^NeMD6$QaPzk?7YwLweJ2eAU;Akd!Fa6CVL@8shH_ zU4XwLig#a0*lW7t8a!?(Fqr*EMZ&ni5;PYD1V6xj?ynjYclW?O5hF7hER_z-l2gyT zS*u-ptv$6tnB;Xn&^I#15FyERn;E(X{O^(j0tlT|YZrIyvCDlu2E!~Ze8v&pRYouC z7J;$j2}EolgOA)1V-G%F0Ex6)(yEh6j{grX!VWD^2p_#gX?D&ZK#4zqqNpkb4Yfrm z`ToM)zy+QfRbur!^vP4vkFbGdOpo^}54a^M)`{>wEXw{mdy*{K%ir@%@H%&{R8?Q- z`+fcx0eTQD((sOP$%empMbDNaRDYA1+!TEyD0j|J=LYN$a|1A5_-cykW|Az>m$>7k z5Ky$fEs6ZG?Kosc{bMqe{kq)gCpV#sq!8O!# z6XC4J{=|tI-R61}Kp}TT(t*7rg%HPG**aW~9?UlaJ~&kQ#EtCyG~+TqdKW=aiHc9J0c)GEAZNnAeb@;Ivz<1!}s1om#}P-Zfb~b_(!8~ zW6ZnK)^19<30g=wQOi=kFTX*X?loDvvvYiN(4hYB2h&$(mL2`1=Ij@J>V5R?*h#)l z?v5WW+mtI`nCkB6Iq(&*)eTK(2Nb}mlizWp9xK|Lxm&16&SWyX8HOFd^Or>tEv~!*gR5f+4_`11YOQxu(?E)W z+ZJ_VFPPA|FI4*OB0Jugxy|vNN~(t?e}yc1vz_Orro;~5jJxTM7xe1yNCf8NP(;X; zjs1P-2*@#3#L!0%eiP>?=my`;#LR5*mm!d-lsW;O68h+pd0dCmShyEV*t;6c3K~1E zEgr?nvo)iz=Il2&1E_>S2R9oH6CanONi8@EWZ%h~(V0>?lnff38poh&e2yb=azt%$ ziO$&AK)7s^QhSj!pAkcf>emVrBEc!_mu~mt1Mtj7#DC!RXumSD@s{H#$14w&+3k{XNBF2ZNkP;gT9Btv$l-H22ME=!wKFiX(QfFmWGStf2G zPF#C;BCYkG0w7Sp?$Br0isIiXA3p}8%irYeEjkigHu{zSz^W~+BNPlynJPCUzrEI0 zgfiP8g3aL&J7SFMX~N0`wZdF{^uckGp8+#tzCosi>^f&3EY}n}%AB^u&4V}vdB9~p za^MuJv9@#!rL?+Tq~>%j?$DqYtC=SI;^5XZy?^5UI@;|}06x?Qc$}gd>k`o!MIw%y z&sd@bMIer^BoTNO{n}LxeAC~-+KMP-J~%zvlX(gqe(dV~-q4Z9+*@{5{Rs9%iUIVU zzk~AJGS}?YclLUWdHBfNz7hZv39SF4r?|6;^WLY&=Kh_v5b05fSi*(+S{)x)(TwH4 z3?AoI)C~*$NF@P^tjmbm?x(Uo3nNb}@l9k&00vX4vg-PWkURUkzv$PUw>Ar78g}V9 zt>&CC;awP*5)UShkQhRH$B&-vL4cFn0?=bGO}bV5w+&!sV@h3;XHRPbP+>LZOTC;a z*QKYp#V#qS4(t1}0%^EIP7coQ?h>5O0|N-1W*fY2+<6aI3c&Or@kXfY-ABES56m@0 zgR*cPzg;K?R`@;XI72!u5P}n%Ntnc$Qpw@P>n6|qrmq;cS%`-_;pA ztklyF1zG4uL`O+%U4KwI7g$Bp32BF(jO{IqIO2PjiF)(H1Z8su#NG&8C!G*v)85bN z?*;$x45-)yXZbRqmkhL(g&TMwxBFSY~XT%ECT(riOwNaI&KgmsZA+V#Vv>y^L%kZ!DxqAz(J3ZRrS{Xu_ z^+QKQa=_s$SaFbZcr4Ritj-j=#CtW9EmLfbCY`mV0GT>eN#Fb}QH|vRN!FO|BVsdf zUoiD|o0t=_m7?-^w2ZESY!X~X<&O`yDeT5DI)$Z`B@&r9fnOIo=+kiCUqM?MN`!$J z53B{>Jv$H(<{RMmQYM2D&v$NW=+x@(37Bib9U<+`g{7xiU|>yFDbBUw1Ts59u?U0c z%23STC(KByjK|@ULYUm+L@l9tTA3h>4b6?D3C^rLnZoFmCo|olXYZr2v0b~@fLv`m zqZ=(dUUNLlz!k+eOdMKs74w}`j|z|SxJvr#dd*H8)&pR$Q;7yM71x#=4K#u%jU6j8 zEiGN_AP(+T@O^nH`42l}rN-ViKh4bvLHAy4KRK6{bY&>NqGB9ZQmg>a9V~V3lW|B_ zN9d!RN+q=02ENSRx9>_pVfmS^-@ti8<1G*Ie%Cq@t_$X`uTSkMRrd`?RC%YSmrS`; zx3g<JPYI9*|{h3&Iwm)};VNw)wvIhYW z`h)F8LQax83&;9*M&M1F#5K3#=xgXVPJ76Zn3Xf<2)`k0=*(0I%Bd@<6;$FvP3*Tg zGJW2Ll;@6|QSYP#7#FHhbrAX|3ZPWBm+Y1Z@N58G?*^MyiIY18+}6?Z6~`#m9DciB z=SQSUDzIE-byIOhBMfkp!VeT!NFX>ejz!&(IM_`_$CDE;$zD#d1Om=Atnnz z8dP1Dn4e682C>ZY-5zFjs#K+s08M2R*|Hk4R0*j%VY^&^-(reloTV%G z2_t#K1ak-1hz~>wy>;aw)Giz5Dj*OSc8~{N4~wjgvUAp4;T$1z8LOy5-)Z&w@;5c= zd*TWKW}s6ZJk^sj3*E9cz!e_5l(E5?`EU_%ZOKd{FhUgt_q{$kRhqvGYxEz_lNbna zo{cJuk(7OdG|b`%YN|agXsoD3Dl;qu2<}D-%Zl6W5FH{4JyRc_?zewCg+nNEc4e=T#vtj1O35ZW*_2XX z0WAWk!_YBj30(2^?DG?RrqmvC}=xzV*r>o-K zW5-A2YXL!|ak#;;d$(+wZF&>?-KXzgVPDv_PT)k+=F0n zWt0FPKa>E$l-u6j39x>I@2kjKaJA7l!^6X!BkZhvVboMd9yas2rV3JjcLPPICO_>I!wNiM-5LXc)33ApWliRql){nH;X!rl|@2q<+%5A zSWZdUt6&1i5VbBAld9d}_Lm=$S1SZsg3%wB~x)$ECLlz zT$2%mefSz#F3WOkD~hRVu5^x=_6x9>v9t@5Tvh9{<#_TBFRW_CCPD?FZT7Jen()u?USg`UpJowP$>8??{qt#p|+Nv4pY&?8{DW#4xoD z;E`G>Pk4oJ&Ow7V%J?zLC ztCI&VkRD4`2O_(xBdvh;kQg+kO|0F_d@H6FcnI9ht=LO9^_*m>=#}TSV(({R##CAZ z?uJ`dqOBDDYsgjAi=hKNhS?(kbW z!wu7@sjgQ`1N`mhn}ekv@IzGVT%fSUR8Ci!e75+6V010rMM`s!4^Uo0q)D=|cu{x) zbSv_}KTxN%%97E#?=S*ltg3saDxPp#11OaSo;ZSa{d1aj7q`j;)c3gI4_y@NMTs} zg&wNk7o931RdK-xqEBSB$rEqddk3*-6z92%#0YS`WAwgN>)P!^!pv}?$+d;h(>6H^ z;+Zb?z?H`+ob~@{Q-({pv)s`udtY$p>TTKjnGk$~c2bPc3IcbUS6!KX*P(#NrH_Od z5?-ZEt@;G)*E@O0f4^+_kq?fSefKJzQqs_ij=7s}0;W8Rc>Ijgxi|R!rLR%|ZLMBV zmf3kyYjWVT*pK!23;N_!_5Nzj$zSl#p@HJg9|Plp8aSUYI2I;rM?URj6-pwm>3vEB z9%ORD^A?=i@3Ga7!8a07BkGiIBElh;FGm9+w+|>lPN?=4bDC?$xF9{X?G`P?G}*EAN@T;NDh;RD)~a&qp;FPgV+Jkq8jeUY`S4q}k6 z_<4dYwH^Wnn%hAHGxcyD!VMhxHwIFh2$jLtDaQogL&qktSl=7KQonC&R{?AkOP1+& zwf9hfZ~E7=?717e%RH|81AiuVOIzV()s43a>^&$sbzHWE@+@t8uOD((5B)BrTNdHZ znw-n^{EtVKql79Ur-;{91D*8~FC+3v*Y3v*T%2u9FB6q%^)9X2LTtx39&1m6oT(qS zrC)==5!`eCg8XX0K{~9NVn6z27I8h-=hzhmL<0E`EKwm{=$pB2-An*{{bx3#u}osw zZ8xi!;UM@u`ut|McHkw7>~O*vQ@Tt6^_+Ym)ElWobMdfp6T z39%epNI5RXS2s;yjw@&gPdzH+h^4NtR^BK$c{0V1{_ZG{!%s2u^iV@?c5lQvlNTBb zG@ExzOtGZKE7PAM{WVwIs`Tn;{?fmDHmAa_o3FB0R&Qbiwm|&FSiE0pbtPAGdQk*z zg*JuFS&P_+ds?w;LK5&*xfbPe|0Vo z05NzM?}^8~OMXPt<&*LyJo3>_QR!A9L`x^K*wfip8^fb2R^MdQwYY{--Cx9;*m|6uj**jLS)k-8kjZ`% znv1v|+@lLUE)df_LD#la7(YD(b@K+>O%3;jT(i0xZS#3IySu3!r*LK<`df6YIHhx0 zUy9Sw-u+w+nuh?ijP9tPON9Qw%AQZU<+6Xhqq;)_LiGPYc|qJe(OsZbz;CC?A!MjOL|Bvv{4sf^k;g>fyTp;J$x8gN$8FS4~Bl^rm#5xdgn%j2W>OV zj{C=7@jSy==19_gCYS77`x^)PX(G2+{Y;J#8~VOO-}50N-|DuU^@plaEBiI=NkOCy zzWS30;DKq5RcyD7vZ*q;^JwiHZi&DAKnagcPxPi(l}$*@_myr7#G{H|$EKRl?&^!5 zHZijwqNn_48N-So6`D%be<>u}BN5t9;kcw>*=GAFaAUZ)X1UJduq&C@4!?WvO7@Y) zJ-19043DNQq5w|I$FY1iLnQ^??d6e|}DaLj3=a@w% z8Z;Q)IRyTwS>{p91h-JwE0q-V$2^FaR@=jC>A<8^<(`X!S};PPbijA@%V>^<(I{J>;5 zYsiW@RbiTKCi84vnyB#+HA`&WPilKvmJuO21+7 zRLxE8X3!{K&1TyJlfSchSZhHm=wc=(YyLsECz0ZQzg7>07RbgQU&l9)Kz*!FTo^nN!A zcTUrR-fQ1u?O5MrAztB7Jr~*dE%@>)>|SKUo8c-FMth+xqM7cdODt4m_<@RZ4?LA^ z&QLHhfk%E_`D9amesG-^uwDlrW`X9e=&`9+zW{DZXwn301!-+vz*&Bsrk>cM6>+Ka z(?XFCRj4)WUad9seW@*^su?sXNiEb%xAK;V`-Kp*aMc8^(8|L_T7uXQxc{3@uKNMs zHUkamI?sg7W9}5A6aR0+K=F|c%{x#z|0GtBL7Wd`60`QMB+WA(-mBPa-n3fTYDYreUBRb~k;0sb)}TYZ-S>;Fce zzX7<%_}^VM?f~rnMiBo3$U*#X{@FgXe2@ei1f)#;KXqQjO8|`2R98HdRx=m~I_&>l zmqH8?_wPCyDhPXbg#XlzNln;6t$o#xNu`$NK@b52Z3n->i#}!*U{M8-S+T&WImOY) z^1u)Tlsi-$JT&7B27LA~BW)g-O1vu5PEReB zqRh`Zq(7h!vaQ=lV!H)D)*xL#hFo2q{2~tf4)kAg?=*JTa0y>I37O7hpPOcKXu=_n zh@)FhN%qVjPDESh2p^p<%}YvNd7jVtXz=4i{9%xCB%Y3Nw1L`Fx8@(ywz^Rbx<&iI zc8pHL%T*T$h&aYt$xWWfg!RT0lTl(j!Ok+|NpNh=I!1RC1IwP3%>w>kKeyK8L-_p5 z?K2ia-2U?}MF~XHzuf*tH3a5A=RMUy5TXBX67hy0nEx3lj61M}#?A)wa3&Q_9ON`&Dw{yOp`}zN$+wtGQk^~wmx>DD5@!qlD2 zlyhvG8_9;YN|KaffD%Nq3u5|n_9C5UbFv=u8omLTISi4=gAx*3j7236%zYJ@5VS*} ze)lpU>d<7|*&NK&si5V!np^A`RW_1O>Y%ZT&eF#$x+tD|D`WA4D?msT=s&egXlwW) zhdkXS)2B3g{A58DEO3r33HKMgxT9u}?&kYOACFrW!QEKRA9vp2HIfy0)H+-F@ou_(}Mg(vNsx!MtnLG zZ=^l5vGq|6NGoqprysdnweK#2=CC1Vh)@xfx$%ieZ&dF~Bf4JsN7ru*iAP8FRf-R! z_+GwMEd+E_Cr)&C7Oq>*4o>XBbs7DfoKykU_JFOwXlLR*i6kWDTyMCdWz-q_(=15jI0A7AGYw%TfX@r-{WPi5e@uhZ*{Z*rBjAS zLoyuGs!4w11ClaYloR!i1wB*-~YQ@`&C- zQy0+L@8Xdw$1&{vGpfa&YjkGmIgt_OFI{<^l1n$@4ZMNixA>ifZi}Jxbk8d`k1L;y zn{2mf7*W4BMtRkM#;vR`r_aJezenlWuFsT7)eO)Nh=D$0#z=S6!`V0^sJmI%39>sP z!!}7@S2BeR^ColAO2VhL<(TDwysN;L;-C9WdUM3i&(uXYSwj#EY)Yd(&dwS*WIy{9 z@TG1w1U_t2dKlB0GQFG_Zmy29WDCTiEnREuJ8$x|?KH=Ot9;1)RgR2#@!?n9$+kZ0 zU58qK&Uz6WsvJin8)jKU4(2%(bK>qYMgs1M3GVFH0Kx)SuPwa(3rBYD8pVH}Pt zN>Mt1!o$#1)|;}^bEm6QC^E!77Nb2$PL#XA1lHO>)~StDKJV-H;Bh#l~@^o~LIxF77R@ zSPW9ebk6lBb2)3tKzIHw6h#floJ5L>45)hqVZlNQ$id&SJPL^$7Sv75rwFK5`2&-|v@a|AQ<8vYe&kJ!1gui~)AbUkasF%q4gDv7LX zPOp+OA4HexB$f5%+D_^Xbi|vss7*Zt(@>3PKd0`sqwHZ(d>{8;JGoL6nx8ADP*XGT zhM|cT|2&fu3*zEaRyaJm*0S8No<_Gg0h*bzyGRA5Vq_oOrDQqzvl)C zKF*iM_#FppZz+MEP5N-`u)MZ0#CIqvvW3`E`2fK_?1`=U#FKfu)%lzWg^Bd}=jmfw1;J%?LX5MjiNJMPvLE*|^kd z5Q9aD#Me!+ZVlUjEUvyX)}{Qyq#Y$v{O0_5*UR;DE^Kc5b2l@ktp95+ug$0xOb1VwD;u~yPg_%oCWvtJBsv7;BUWW<(J8T zD{k8A1-sUdN8`_g$lqACQB8?@YbRRE%d%nufygxH=|eL zRx6kNnRqV=!gZeX?UF|o1XLlV!W_;a#KSxxn4cy!W5G8p29iDW@L0_8y%8*h#ApZB za!?)Cxkcr5h_&zhz-F>vbY?alCs-(Khk0|LRH`fUU>u*in(wd)tA8*bVv`nnd;^gEQ9hCA`H6K)pp-B%qMohxdH}0b#?C) z;zz91X)TOL?ri%zsv6`QHNjL+{0B`vTUVLl)t;aTY6*+k66H-%L2q@qVnuyX9H*NQ2?B0^qep8uibmH zN!3*_I+0q4ZkI(|vOF!pZ9K;|t2C)GYtpJG;$?e6%t>)E2O3v^4uuTGm)U5_I@fdu zP7EwZK=hok#yeFvFfQ?wnXx21$vDbhz12liFU22`QGZSpb_S9je{S02fr576-)mWi zmT_4Tt>#NUf$(d&T?~{=#Au?%mk*=n`-;|{_FQWIT>Dl5(4#tWJk`?m%RE(fF)2Bx zh1B_0@8{&CnufCbS!pMfo0L9S-#wJ=wy)q;A39UIM+4JI+~A}X;H1sv)R7Io23LwT z!Cu$YQ-9T{=?lu$GGF7X`XJ-pxnHg35m1CQkgkNFK+9elOoB7}PVne`D*R`L6f_%U zSb=Clg~$+c?pMmaTui9Yc)i9f&4{8~HA0inP^WPiLBx~CRO=#DU>CTtMDe=pAWO4v_y zyitGOhlxPCdtd{D^rS|aJLTE{&cQ@qR*U4j9B*Zb7bL6*>u9VYJL`4pUNeg}#cCVw zehrZ{Eu5AXU|j6cz$|@zEE#h5E|sZXO87L?Ti8Bs&2*jI`w6|mL%US(sWItN8!jwz zv42yU+-u6N6ls`xpzz%sNi&&3!F@i5JiEMGJbvI0j#;c^g*(H#xvDug5xx#WFd46)hHXbiR7yvrdSOIa{bM=W)P4G-}4HhPcH=@@5qu>Vx=p%T;gSY+fFCBk#sn8~Y$DJ>q_anIs;#G;8a|G?;f}dt<%+vyK%l@bbq7* z8ve$JI5|H!S$RT$Lp)8IzIYeOWrWhA;w#AFEY)BYgCkdn1~mh0Dj-BD zj1NWhbw8&8q$!JzZB(JP^_nt{tUA%9nBUI>g+GPtMF*l=wO9C%N}PyBl*T41j(t?lsh z1G@M)t-Mtd>l7r`4fo>C$lsvjofJoBEyp~Gu3{Rj_92s)ruRSWzb+!TOSMxuwb3y3 zc{Z1Q7d8KK+21I>|L_n$OT0gR`LE?2NiYaWGbBa9@cw`%DVU~+g9?Q*IDdl>42}`l zcPH!UPhNKPVF1JMCkX;RR1x{f@F70nCi06sIx-x-A1?8UGuyQmh5rj(HXOGhFh8cKoBsy7lb?N(BZT%ZP1a?HYc76a<>|NNPDS9^?r6lm zX7?L%(_`;*L?CfG10mHXKCj-dpU?Nr&rQ4?Je9Z3`!iKV4m&$!qfw<_R9+D zlkWujVtdzh|BHcj;ky4px=_D#Pe;FmO(ZXU)8Aj8tN!1on5LEeA42 z7wcdnOZ{3tuDX5gGErZ#fyFBy7c7hlli6*Bk&wV8jy>E{#+u+Dm8@ypElD0o)~9?M zX0;OeBv&Ssu9{+AD1VL8syYV8IE+)T=882){9+;8-|=jLKicuA!IN-BUUZjU_e)Bf zPn0@L>xQMz;;StOKEC{H-EGuJtTT2wLxxLn**Zq_%wRG81AXxIssw%$?w#hUL%r|@ z(Ls?K{R`wgx}i_8vvQ9x=PhdJc_iti(2^79vBT*F+=F(IM^3NZe~a&)Wsh)g27i{@ z^o|I*v&80MaXQys^G7T+44!yYdDb>-a#{Mu;HdJD31+hVSnuz$R%Dm?X$UNTv2MaZ4BhW5 zEHRK&C8Dq(B$m=tMM6*!+BG^7C%HDciyYI!-`5GDLfz`={QRE%>>I%7ZUl)-jm_t9 zm}QrPKX)m1_wM{|Gy)THx&O3%SM*-#9ruGz)I zjoj-y2YoIkI$|3sW>9pn*I=3517YGQ?R{S3;`dgAmEr#>5<<GV zbK@+^bz0Z*dyDH~f*g6>VO|_tU11#iJt@#>dR_K*ao<-zP)h>@6aWAK2mofX{8;Y3 zH>EKv000l70014AqiYCe1gjs3ESC*z2x5OFtwaRl6-||0FPjqnx~bB1o)!(lI!{ya z^7Q!R)%i)-ylX^MB;xq&)y13Buijpqy*bApq211`D(7N4oh_SXl}@K3n=i@=nqSvt zzHHJd|Gl##e=lqGtxDCm`gYl5x&CWa@9Z?y>S#y6NBvmmJL$VPT{Pm9emSYCvO0ef zVplAx=w==riK2wb+@}?FgZ5$~UX?`(BhL_K2#;!*`7|qL<>iwr@q6*Kzdz0D94J%V zoSrEp)ZaV;{V{D;i?oKJuA@5TpT_jKN#8Z}5S}fI4A`2sfVq=|`XbBI3v3wGVg}!- zmpS+8^m>`)O;!MPp^f~bHk%xm#Vk_0c^y?XGPOpo!ru$}yB&5}<@A&4rT!k{w`I|O z|EbIh`cg0QtdWoDy9F%It`(>N{;X1%e|irfeqNRh&1XvA06?L>RQcf|YHo{Yo-(0) zAD6yu2xmnm)tR{<}Vfo=#Re{UgYOxNljQ-_ zZyMc$0sz&U)gr1;*@_wH)uMbDi>wfFgln%6MVclxNL9V8kPp}ep4R0&6%Wx$0-G*j zzKg27QF@GjMlmPR$H&2KSvOLYcg0{ZXrB!6-^-%~ib!(x2W$v`!9=cxeODaQl(*B&KIEQg5jToaBuVn{O32r zk)q-}YT{dHs5^!93(vJ3oPIn~Ym@#8Z-C~D;9x8cRYUn0ksAwyu(Oi_$2KVbtlUQB z!=GJU4?p_gTtJi4v+t^Cf3eW=*46Yr)!wVhVL64CIuADJb4M5VD+&7ew7;cwU0f>G zb0#*d?kv+_YQrrof%@1M!K)HC!uUv)Y4rYen$x9FM}eAcQ?v?ZOKCA$+u<{dt{x6S z=|US6IFDL((1J#$F|%~H0m!S&9UYFknT2x;B_5I%&tOdvnrpTCeUNPtqV$R1qYR;us)<&X1!eWzufzeiHOJ2SMw=2)>Q-C4V&>5LCgf4>L`Q z8Znr%)=LW3Ta45^dN+Llk&B}$sV4^p5YX4(U3tMxejJ+QQSy#c2Vr-`_`BP*z~9kni?raO*k({cQvn>?*+2b`8%>vz-#Av!xn5)3c@9A155EV?u|una-OVfj8} z&22Dze^>19gMSK^PxGw%*qzWu7CsG~9>0o>4y*^@OWMarAaOdsPLl*6;1oatfPv5U zB~M*gZ`FetHp^h(s-Wc@<=I~VL{g&qqs7!uZg8;EI*t};ph!G2NnfWf{mxLd9y0V5 zQ7km;3(a!r`Hn>z-h|@e;bHGXMh6FDkw*}Ie^pDBt-~luh={D=QFkiA zf}lOSDL|15OUtw#R>|rUmzTB=M_V9>olGR4l^ve|zR!lJvW7o|!w{Y503nU|r~e$H zJql0%`0DJ<$?>!ElTN|_8XK_-a?Qw9*#1U<3bVEJwqgG^t+Lq)7k?x6u;HEvV6sVy zf9VPeF##AlPdqOCbiLC3!8V+i@m<=4H)#WEwn6~GXr%d!RNqqr?|}ir^>q08!RM6n zQ;Vye+cHPt0CvpE92IJUZZP;ouaSqaUTXOmwKRAl;4?9f<{&N6T?>OthHOs)MpDe8 zEMEeV#TmrMO8{1bG$6vWWkE~bA$g6Xf1(c*n)GvR2U5T>8)WTT?<^Rw%A?@jG-hLm zZ!(sT#K3~r`3%rR*Te?|ihp6|6{14Y3nI-(zi)zaeDdaEdiMI_^z7BMmk5Y{cysda zZ%^NxJRh_YD4j^hC#ylLOlUeN48af-Zt;V-*?FX9rPeY58@cRNtz12aT3^l>rK~LS6{zYI zc#@zNC&LPIo1qYiZcnjzQOe;~m(h!ujXlwlet&X-9l359K|G>4UWpGt8vfX29$1>v;86@Q8q!^d@8 zWs6oMvzeYSwB^ueo{TzvrOgSw);*qsWCt|yafwR+48TM`Yrjf;=*h&tV#fG0NC>v+ zIGp&SS48x_odKSv&Ph&5h&o-2EEKU?0V3uGRScT;!9!L&JskM_*$&-B6WyV>!=EWE z={Pl(M(7e55C8zsm!^>wo`0Dr%)_jxF>rh`ZhIM6Ge$_NlkYERk*3JL2oWmfGVVhJ z!DArgCq2Le>JJ7Z57F$=v0{Z4t*L;_bVkWf(|6zvsLYD;dzS6@7m0CWoJ@xm=q#UYGLtYZxU-AX|5B4Wa~uB~oO(C~ANFMR7rk9K|94a;&=h z?N~=y%dINEwpJghAqz7uwN&=buK3Sv@q)0fWuY3%6Zmb>4Sy%o55vzm_arHF7=F6L z^=N;|7Cc|}MHL`$6uts#%Ri|y>^7>Srl|tscZkELQ_T8S)9G;3@2@JT6AS1T_$N5b-q28rfuWbV>|uh$OS10LNNUf5kBekdS?b15DifIoe!Hh zFJT?HI^C+@fPeK$YEE!efgwT}lLbs@(}Gkr#);7l%*P>}NjE>y{y}G`P{Aq$g$4#_ zy+IxF*%sPxOV$&Ydyyi}mT_wDQGQt5j8pW23G5K>4Wk&zNKS3sthg{s z*TmW|LMKZ+e7$B z-TYF(j25mo9UiL(J4S^qR}~p-i^0XA&ESdts`!Ihj}4(aF+DExoHyVg0=kZMZK?vN z*{BA<+=nqCiLg#8&WFk1PWM-*3p$5_yjPxc2FLn$7A9x1=#I;JJoHIEZ zQYmIIcUZ3&1D9M99*P%~oB~Ofj72jDZc<#!@Qh=+w?+02N7#(jk<1(r@n!zG76bed zEAal$fBz4K6A&+AI3eL#`R6wCfF5>{PxGj_L4Pe69G^WuIX}4=xtS0N#n+Vfe9|Q3 z^*EKR#gQg!X$u3$!rdLYoUFdE80Xhu0C83NmVx+R2jFE+nkog5_PpVM)4D) zkAHN>LP+w#HmAK2&z3~sZO|scbYv2e7wm@+a!V3yLCh^!5=#93UJ2=@vCF%($o`_e z%_MDL%&9{CgEF*I%>w~+V*IPKZ>DcAUVP5Ur{zL=UGP0ir&a1~s1JD1%cIHF5A|Mf zx&OmMc=gF>VDNtm3w(;fOqA1{g0jkP5Px0U%%9FPFx@~lr#aeeHRURdEyIpMCMFdz z4venLWy4#^Sp_lR!C_mrVZ?thF4_@^!sXGy;gycBRawJeiV2GSft44cjuSB;l{7%+ z!Y`&(=O@t1D{Qi#3lSz43`oiEAK;)_UG9JW`R~4H|56W0A^u>3cE;Cm_H97>4u2%T zum?qBaMy+_g0*aQ#4Z4Hm*kn7pV!|px8-&j>0N&`P)m8viCb}AM`?S?sMNRRGH-K& za)*<p8QRk$X>Vu2UDL`M<#1Q_SGSUBTke;sk@KE`kO zw-P`=nf7m@rmT$A`Kff|(GSu<0e_u&^e&q(=ai;;$YSXt7&{Sqf;bZmE{JOF?vCVM zMxNCTml3T5LIv8D(5UDtfCT^)@BuZc!Hg-(Y+8%SJ{0=UP!ptJTmj~6Z;TfJF zD|CAek97f#EH|aGnyL_sbg}>B@YAPA@fq}|gf@cV38-F`8n0=ojv;@N)qiwc4QM3J zSKx@f8W<|A#>a?t;byF?^M-g?V zh#$FJ)D)d~TTbm9pq)0jFgtOb0poh;AT!H~WXb{*ILfORpIz}>s+(GXp+pbmEzk)m5bWI1wv4l!(O=mdmsA3LRD>9mg=-M#*7){3Is z8tiWD*nf9qHz4ALx{hD~n|fN? zY;j4G?I#_g&40}{;O)ZhL7riKJoaOWjWLoR6{F$Ku6ThpIc0H=^>2XFKa1b*e<}Vs znlJuEq)i+;aXlW;Qb9OpqkN&f5w_oCPIKSA2uFu_F+%WkEPgBHZdq`1piz5-529c7 z`%b%rJ9z0f4(>*#SZ9fEqpbKQT6|1Cq66$3-r)ss3xB-|E$c@evBc82!k|jyB~aoX z4flf1=uYjQI63H{vE?H=oiIm}Q&x2{48zb(;17qk0zqeq!bLuXa+gwO7|_w7Z<~!` zu%ry57JCaEMwp%PT%ADia7CHpsTwB0)UW=_TXnOjrq!}2WM-Cj!8@&}T8|Gs`?l58 zhpLN3mVa=YG39h<-zMnYa>g!n;G;!j<7Ts_nco5!plC-OhVT#Xb);G_HS1W5f;=;C zC(t}t>$VP}ff&TSc>JXwIUWB{C7{PmJC3lT0kc@{TnHrRWwOli0+WrmY4Q!QYtV2@+J=5(4U8PXBHO7}~f(RNH z03$Gz!-@(nF`x~``z(Dh(jb`WqsxewoPr1=K*gqjEf1UsK95UhonVN14^R7Xc7uSn zwYfaK8k5TgmK^%jvL=?`_>h)olt2yVD`(heoUYGxZ2pTA0Ob&#G zCNT&tw%T^dDfmVzbdAv|w8`kSAt0`W;d+G%p1cxCJ-hCgL4ybtf0kw>xmYeJQ@i1g zi<@xb^-HN|!{K)^)(&DE?IaU9Lj7xV9Q7r<$3Dd|@>4ooViAHjhSJ>a)ZtO09Mq z)wk;C2G%8)FjZY9(PEKpDoVlz79QrXE;L!QP_L7BSwK^)e*z^pEWVcDb|8bRkgvrP z0s6ZC!S$F&4rKCw)lv6(9gt^2AG4bD-#Pt z3?_h5br9!xRvFXER9?F0sl6H=E6b>rtE}ah`Vg0+G@nVk<)VOw-gEIKQCRu%wA zNEm_V-^wn6G0;ZQU989p!!P!H0Ig|!o8>Zqpn#FegrENu6*uLO&s$*`^kP}xIzfd! zX=9?q3U$aCZD2&jcPS>*YMQE_9nL)8|6)$d6euj@e}iCuWzA&~VL^Yrc_vmCq&pFk z9Yx5k5bdQsFT=WPyBZy+GxHUo9;DlLwL*? zG0L`OhTEkq>5;vOfcQIP11{kdy^HNlIxp{2bB{M7Z9WGX$SQ+@us*~a3uE+>)cT`4 zCcueue|WrZbIe+L3Po?qe`^vBUzNzedJVP0VV5|jEP3^%B+?$V*t1At(k+9~ge0s> zrq7kItAnab0Qsp|}) zcUkdKt@dEdFkGzc%b;u^{yh(;&4e@={xt1}nxFvOWE2M2!cFKC`=+Y;$ zNrxMroak8v;cA*ZM|c0jPUXIT|6W}ZhPzK8NC^0993jW${4uz1*&?kU|L(I-KmFqA ze`66;U#ROBzl!2JzV>%!A`pyZdV;+DYDpok(B-VZ9m7Ar!+$;(WDFf)SZe!y0}$gL*#(Ry;IBSA@c;a z8Cs83L^ig<7*3TyD;H#Ykr2D=oPi*G_anRw+wAaFcC&vbR64uK@a7cCs{`fDf9|^2 zE8^!>db7-DismJ2;EhBg4yrpHW#D0m0vxC`PwiW~hQ&PM_KfbO@g@dO1{EAfVM zl;4!V!Q1)B1bG6wn$V*1CXRcle_Bn|rvXQA=s}%BUe@!t5E#hNw zgSE%WaElv0^tfBpm`J&=%F@m>ZBb|DbjNLA{_m16+t_L)o%5!qI)NLOojY)V^g9%0KMtfI&%fx&dWMi=!iQi8qUy3rh z%&sOYTNd#AGpA@ddZj>ue-jC{{D~GK*N)xbN#sbC%_iz(G;0tCF0NHpvrR`;8%bN4 zXxA4!;D|8!$Gl64Ax-DR@||>cWn#%AzRc`C0m4rn)k-YDKlMJoJh*CGwBUf%Dm=Z+ zDGkr^(FrF_wsnKwEuvb~|vdqYT z&NsG_4HgCq9W<|{G-U3jj#PeHQRqdP4`Qy`K+!fz76T`A)Ss$<6SV_E7pEnuvn60s z!HD^-HVA!+S(Y(>Ez)<)6N_;w66@*99M}E3jqq0%2Hj`-#49sMK9|g)ENq#KoJ7ld z`Nfl+N-_^@nFGe;e|)`zs%ykn-4(LJn~bR48xhgIclwB^d|215xH?(DSAvf8>lq(=8GQGb#K!U(OPX62jiE zTgTku$%gFms5`eUG~q{@I~~%SkGC|CwJ| z8J;F3-(Hz6$RR7Fx20l7kj|XR~*x%XU*%UVo_+rlYz^0rw5f53P|qe+pqO7Tj9nGTMwxwT|NYIibV} zeJ<$3j2iKD0}JGSZifYi{a7=$Bzxc3{am4G_j}@Od6`Tel5FA;H|RynVi`C6SQODz zNHiS+dc!xL>5&aju+ay4ef5{Pg`FE(hPLS3eXt$I>QEQvkhYPQTCflt6dPY|6L>@!Z^C{)ZYe5rGV3UKT zt@}?4bq^|@Pa5;fkf{7gZ=@czC`xEA)H*hyen{%$aEsK3Wa~&?k){$LhgfDIDLxS3~*I5#eTl4D|>RP9!1`$ko3>f;CZUZU2y1bWLU ze|Bq{yP#0n=|$#^270#YRoV+Hn)wT+gi@<$OYeor7MKG<8M>}Oy3of_d0P|gJgV+U zCin*2VEyK>s_|0&`eKIQ?sA-5kBg zS-{6U)r%rbtI@o|&)ln*r;#_RG{f)ke{D$1%5u%EP7~$9`k&gR;m*+Na8PC>jF8Y9 z!P7m&j^d{eBdk?aZaJe82YrqGm+m-!UPaCr8-tqMKAgtn|bu~e|T zAAC5=C8txE>l>pjS3?JnD7f?LGL<$MuUS&bQ)5{SUA?AddAhX|Du)OdezYQ-bdDl0TdA229{tp`w3lt*;s1*cVy@CmPhi^@ei-kWD4Y` zV#rIoG$-17Nt0|wH<9Aq2}o$#Y{g9@EZTF;r0&JlScuE|+=nMLmZ0dqMSeV@Yxryi zj17jo#Cn;HWO+pVw0$m6q(j&d4`=!|tomEi2si?Z7?)R)2p)eQ$7ip;#rOBVes*zj^5&IuSN8DF!N0!#J>I;Zz55cw z&Z|#GM~@%lk9_Lu%W!Y>8}m%%cV};&_X5NFwtsl|WCZ^kTgK1N`iK7^^!GV^_59@f z{$7_xuOF_Q;ZFZ}x@N%S6Kg&sq*G-}=m!4xc4iFh$MA^$Q_Y692o7fB*$uXU|2!>z zG4n~^rDC4l+=8Kvibh3XQERvO6@nIMFe2BR<)quY;uw)atP?fF)7p_o+m{t%Na!A3txnwGO25wU=6x2q%Bkj4a!e zEsmk6TN62{DH(=$IVIiAAM>U1IEY_G5f*q8WQebc$M)W4YwrtFSgE_E;lOR;Ov!mpjb11;jGjiGZ?mqxoel9oD zweC`HVM7q0#4$74lyRAduHc(HL~4cl&Y2hr?PE~^5F1db8}Ba5TrDEg;K-~dcItQ= z^Zv_YJ4w#L+k!HNMkbNoIW0VnjK7XX;*I}Z&hfB!F!G31VtY-{lCOU}11WrR)osqZ zISA9g)HF>Di`q*PlQjJ+7copW;nh!u4rZ3!-iX^~{HR5O=hH15*SBwX>_f{LkoaJ1 zvVKJTFlC_a`oY%IMSa$4XY+$^d(4Urw+x#fKya(JbU75Df#*((B_6SsH$DVZpolvv zdCzT+?uRK;tfvcpq>`4E2pfN&;Umn?%gvKOp46mu(S224ZQiy|2RD4mJ8_E#woud7 z-_q4nyC$^lY;HqPSQhqfj6lTXphYjLp_6>PV`7A@QBEfe=t_rBoJ;qP_CfG%p7h2P z$H`Q0@md*s9qv8~dVl6#itdt>>pefIA&ebv$r9WD<^5@+m6zehxm$maT2bM)<5_zQ zjc)CAtIbxr)OXzkFjW4a2{`$|{s~Yv@ozr?j@#WC$YyS$q#s*Oto(X(yEt%|Lhl4~ zBOCl^o9Kk#(hBup)UV!>)(9q4?Bs35u{^2JA%7qG^@6aWAK2mofX{8&c5-CX`K008~U z0018V003}la4(k_mk1qyX>%J#lIVB-iXJHx4RAonmXDp_KF(W>M0*vhYbAN?4O#&< zfdbiPqZ{sSki^XHf8RXnJ{q9x@tfIw9i9=P69)O+AI0?}f--zIb-}`u*v&zw5K2US!YSy#Da+#hShme7o6VM+e$$HC49hfapKaY*^9QQx`PAx;iBlo z*I%k%SZ3atUv)2k?)s{{P;ah^?#lXEHJ6t#3F>E~Yp$EFyjvIjm3q-_^|!y2YyL}i zcB=aLyzH>8)4RD?i@O1XgiFLW^_~9UnKQB+eefsP_!U|*A`lc@{-SWCmlVLe~cyU~mXKZDMm{_ytAyASX6q(b?8yZ{GmH6J|K`N)+wTSe{{ltiO_z zI01-#kF!|zI9p}kWhD#m81IsVq-^-2h3h&8UtYbL81PFM>JjQT#F?${p_s(SJo(0l?w=hX(-uDHBBp`}b=98l`s**O_;2kyrtYltCI zM;IhxVccY2g!?2b6V16n)60~$Q6|E7a66gXwDvE`h# zFu6@zif)>{Skl^eYcVgErC20*pqe^=qyhkO5j1Sg(lG-4&~BjJ{;KTEbljFz1-ziS zCfb37tc5YQwVZ_%_oc^OXc*6}0ATPh$YVgSPz0h?mvU7S<<%f?ZCxanL`aP^N}<9J zGc|DRAe6vyCrZH7KmqI5fme{p{fXiY zCL1>des|#c(h~6X#g$?Fm z_Pxkz0rLrj0y;Op#w~Iy@*4qveHO5`4WNpa;IpDaX%r~=g_svW24KTku&D6MvfkX` zChEni%SK{)Ipq(}#YI`vkH5Rv)cpn)wuN7PH^%t|;R19i8`5@FmGw1j@~aXA3=m0R zqc>PGTb7kTl0xeu>YK7{Y80}k)?`D+Pmp|CD}V%tw|Qjg0aQH9i16UISW)kX>XY)TJrN z7Ts+LOJ&R8<2WpQK*{ZYN}vxj#48rCmC}@Yeg%D!o|ICVtn@MY1y@jMzm zFbs{c$x?`YmemDlJVI#7XmgQqLvwx6Q1v#TKSK43$)w3%n5639yT z9G9A?6ZOYE>jOVIA+8&(CaQq?pfDC8K7*}}a^%u%4wz*0BOectsNZS<8YzId3y{U< zLy&C|=HHPVhJpsl5U!e9^e`e&xh{cOfBRw4mk>5 zPS0)8*0{}}H8nGnEJ1gqrA}7qqGi-a0*p|zN7=~0ouJiVeqD(hQJp+-#b(z0cMF*B z=LL%~L~N0UdrA&0^txGZDpV`A>Q4kl!(i>Fzk(S=bMz8_u_G?)LBEuaLJFTHu4-e2 zQ~=z;@BC!ssMWPwJAx)`D476pm~U0A6-VwIuy{l0_|-iLGT%0B#bt zNcg4F9Kj#Hf#C~sY7Rm;(M9mpHVaBHd$EoLfT+g5(5{B_9s|z51H)fHK^@yp)F)9LP)M$W1Qc*98 z)+2fZ|6(hLgGVqiG@}P6qul^WXi&hek3b_@!vHrc(Ux<2dT1t~L4a0;Qnh~M#9Bt2 ze!CX3{~aC4#KtG*U~q_Po*2qBd%rI5>?he3ctDv%1#!{fi3TnVY+<>KIM~Ekhk2~z zPY82=tQ#o0D}iSyI7wuO!sA4*uj<)5PMXfix+-_T2v!j!h?yn5Nt+COV2SQ*+VIa(e(7M+pc$pl#87q;bn2J%%DX`T*Sy5M#g2r0kCY=VbHLuf!Btl5_0xAqQiKo}A*bt*{5orAQYftK6>k zef|{)5@GE`qr}yAG#qO@hSt*hNIGd8w_j=uE~uDCwHqU{S*Y~^?cB>vJ(p#wt-^MH zD!nXQ7*#7eAT}(ckx9O8vQ^UxW>z*QqH!XX6?bc(5@Mn3^jCC()hQU~=V)>Qb_TDw zJ+#aR2tSPElR=CQW*R0&H5;8jIm#2VhDE7Y)!Yi(I{G)?73!qxT5Piu?nE4ebl-!M zE%mtegHPLAI9_R=vA0&dTe?erQKIaBAJ0B>twIAjYNSotS^KxEi-&DXzQeW5_FGFv zk=lcrGtpUF>R6iSHPH743X^c)@)lsmXfcw;V)vWufRt4Yn}+RFSW;`LOTw-ydp@bU z5OmbV@T*?%aWW6F`(>LSM2ER>_P>UPyo>1^3A+vj=;?e{2@Z51uR*FN)#i+Ui>O*K zDU75hkNzYFhnXzrG~JfiJ18AVR~P652uLWxlLXDD)%cTuLciXgzCHTl%ddcL>?L;& z3Q{(HKm+qDvfMi)2R4FsC*qk3?ekGPvw>%ym`&yDQU0jQMvuC2jurt|S90}7DTDqQ z7n6LIBD<_WXA9qHYLu5Xpg<{q_iSS=+oN^@|AVIYHEabvG}@O>PD2sUqos6QbOqa^ zz}*4_dWG!pLtEUGcvOSuI06LqFTTuf3;Qr0sn4PjU7hzHysP3yFf=Jo_VS!Nn@*lv@g8a==0SUKhnHd-glS4)ke$wMcrEH^gH_|y}TA7fA2iau;X4|+qWAqT@7Y(#T1 zU=F=^Hyv3JO%8mPXEUKzRG|0Y@RtM^ddD2Dw_Ga^nvC+g2`vhL3m^~75|BQ*@FkNb9LBDw-t&aGli*GpJK~AO3=Q3-=;Z7n zpSW!B#0MK1;^A`kNV8`?ra)#AB<>n?m1+BOzNv~<0}mLP{=$wf>KCp^CyI7{rB5`` z!^N-6%m$dLad}~$t@=Uc0b+%Wp=5DVvUO~1_a~&Sq9oG6(Wi8OdPsO68*T+8MD3X> z6dns|0W-z+f`Wh)=^ZC`8w-8+u#n=s+z{*?ikoTVCh8KF@{&ZSpNxwK7KaHkB(fn{ zPGK$E^YE1MPNBwWNu_R!ElGZ>P1iG9BCiE~kOV&oDe{hxSpJE=6VecgJplO!y0DqE zKe*~F{c*7=K~?F0OCa$Jfi7^Oo^Q2tpgpelf;<$_bW28VZ0EXeZpoYPLNKxx4Dbj6 zW){E^8czuft~_@yE0(>P2l6xbJ>XD;6P@j@C_lp_H%%qTnCb|JYIboepPy^5S*7*)(!<3T#qidD?kBD!O6jhYfj(|#D#p_x z|31~p`+36pw8WfFeIPF(>E~wImDG9dIdr0%19q}?xinke#@YeTBI^uT{pl8v8n*g4 zb0#so$1eW7+Jewi2tY)_gsI%2myr>};N3 zPTv;grRV~GGXZ@PcYRxQ@)P#1SS(8Roy?Y^z@UiEV60nL9=uzXEgvC}G}>#xHPj

    =S-tMqSD8Nji}FL$m?n8D02wl&BEm@70a+H#w`xE8?%hjT z5%QkgjUTojo?Z~s3%LiZ!I4jp`iyh*C)jwmXuaou;0(ahs)P|G1{lbx1H*ipp;s#a z)Bbu!Om(Xq@z8y*xluLHa-ZY4ue;Mc#+i)wV8v)^@|I{{|y6Gks z;^tFZu&dhM)~o|}c4@O=QvM^w7Wb^{V=n<^qvfUo0j%oAq^4@9feZuRSbVVxp`=NG zY^p`}I4TX~DWqmbUR8aVkZ!>8WXk{eoz>_H#v@%^1;<~+S9iz zi?2c8e`0HGf2HQZ^wXGG>?y{{H54=A8SgPwdTD9|=0nSL=o$^t`Bpb;07w&IBDT@D zzWwmS50XP4t(t{kO|d4ATXd;P*=s!yseImSDt6(aYKrFqi97;HTSc0Yo{=;DIpCzA z3Gb)C!b^~m{6mQ3EU-D^z=NdhKPaEVD1ABff56&X@=U_0DEMfj8#{JOM(8oGPqMff zd)4n!*0ZP!bJVP?dmpoR1*Jn1+kJ3_h79|`CrkeM%nWwJx*d)RRHmB@8Po=3@K(iP z6@1{~k)TNwiaM4uf~R+IwplLAJCs?nvq#-Wlvup-X48bHZn|>rk2wE7wk08x0@0)H ze~&{l(L_SD{rYAg3MlQ7G}shX7D7{HM`A2l*6m2vMbVL<1oW8R#zXm7A!84~Y4(Av z0YFt?N#LWcM!An_$vYY|mJZ!5B=OQFnt-K<1q!}qX(Ga{#SW~2?`8=&b{84>E6*g5bQgN2F|Z$!PLX#1B(-f=*2( zYIue`W7BalcBG6&DtUr*5n>;9-=>9qHm8ES3_%karHK;x&{_0-4MR`s9d;q9e-5*C zh}SQHX_;p%;whw~Y8{wO`}qPRpi14-(1l4qGC&tazXUCPtNxF+=K& zw#EVPYT(Hr-y}DJbfceYmSry$dof)QF^WSWy?1c>FwG|f)$3WP--p?vagxnP{1f9Ks*@p*oJ zo&?54o;>*y3&~$!{DFVL|KYci?5^$@-VosK7WEvy6pO;$Qj_Jb#TR-jz6XM zmpN6J`*erbquLB9cq#^5xulq`EzrqiRo1MBT?jqJlkC;=uXe!WXl@2aD5wxF5HC7L z2Zg-7M6oHm^~mnvjum&Hf1%9F9)tgfBHY=rhY$G}tnvT?Q5OV3cq@+ug#jcRihx@- z6&lwG5A&1U*P>kw+LM0FWQON$pA&;P)ePIoj#D}AvPvx~346g4J2e;RR#@w7!`UXLzd z>H&_RZ+X$p%QE6A{iu7?kuJb}oyc~KbQ70slBC`fb2ux6g+7N!#Sar4)9^uUOlErq z!P3Wz4G8luSbgU$qZzmg5niy%l?*z>q&Vj&YM@-ut1~&?<{%`Xd)WjDBJ9GBC_h-{AhmJ)IQFa!C^hGjJI3Oy=Ts1<+AjqE^dmlqG&gqB}T6RHVj3E4&L~A4=C_U z22Y#Mk&t!kP|z!PMmbtSgF5+#nq*ngfq8+rk*rsC@rT(5e}VCI1t9Ga4fy*jA*$@N z>60%MNA`^>1CC36>xt0nU44*wRepK<>C|8MfEl0)@bgDfp2S5a6iExG>Is*Ew~6O> zn2JDd_YOYZBj7j;AogV8`^mwU`ei{H+$L_fE&NvrmMlO)x&@Qw%ag!uPl(p`pwY$F z@TK7#F&tyVe?8CpW<8^LP|Wg|J9}Lf7k4lh-DvGuF=)Y>wE$26r@hE&ah4IsA6^se zWw96;-bVQgy%=tfF0%a^?X?xKht5epk}v{l3tDt3(O&$hqtM-!jcH-?QOlarbDSOk z+++tHKr@fJ2fNRAt#ijYdY25DQj`Luk|G(K17{eCf9av2mFual-*1%u!05m#6WWH# zzlbSdP%p!D)rWAqW65kqi6PY3%GuZgc=n>V{c@9ThL{cHD#EU&^>3g_m z9+G(~e_~Zs^?I#R^p#Y%s4vB+0ty4b#*^%eaXgs_<1S?a>NZH^5hYKk2ftU) zqZJ=22W>UJvV#$hJbzlZhH6k6JWN#M<9vc}e>#zHfOe`nq}Z9wNfuued{h*NbY~dF z20l*MY&#WE?Pm4VBuF}S?s2BVxurNp0?|YJ#P01uvM9;f5Cxw`w>;4)BMSx7@scg5 zS~xG3ee#rQonl%GJHpa&ViV*J2wKXfs6t?}T}iY!u=iSQd2r-^Hb8RRWRN>3j-QLu ze__3C;v7ISdy`vrw9`WGlpb8%Vxg9TS++E@D(mVLgdZ6h<3#KI(p9aZ4)K4(RsDam zguh(8)g5$Klv&CyEO0XLN?ld{O5g*f<&~Q*SXExRC}wEHkVbE=@TvHbR4+_o$a|ql z5}rYy{jg;jMGc&h0kkk8a|`pI-$q4de|M?$G%da01~&{sdEM-e2zmYH!|5@fKs zg56iHkkM+1ixPNSTihc1z#W?kO|pq#5@RsBgl^QjLq-j8BapQ>kJWFJ2-FM+n=%s5 zMA@NK+dGs{5|n$(CU%r_X9I!!SR(2*aRh6helc8i!Q6Z3BoT?Bcpgp`AsTMloA59 z-jLaLZZ?PzM6|QK(!@hb*VT{Je@;*0<fMiZqr_D-{>wb3KVRDsnzXmkLDd3*3o1s4m)(Tm!eTWR!Hr#hIM zTpbIs2^!wqGEcOLMatb-9U-_j7dbU{-c0f$*h@VlHDN=>Ukx7LO<^&Be??FZH~x@> zuGI~<4^l}}xrF0vEmV=Eiyjsk<#RkTz^UgfZw8_pDac6Si?>7y9D9`_ZG1bx$c9IG zl<9StX+asFX=$Q46^yC}Q_ZSgCM%j$c%yu}-p(n}-Fn+36Wu+`77gi->rKyECvJA$ zh}u$d^u<4Y9j#5CGn_Qze?;l5c}T3~-gf3*|qeiR4eVFAlGE=ZQQJ=qe+S=GDp-$&YwrIGWXqjEcq_sg6$D~Qn_6-^u z-)Uw$l9^<;nOLp+?O5py23FZg5434^iVRn06sGe@`IW1wvvtYC>0Fb!jRq%2AZL^9 z<~XM~z8q0L$3f>=e=)}il?f-%5gc@*MV(6@bPaAq5Jb)LCrvn6dRxpQ&?8Sm!17Oo zJf5f^Wy-#x0r9p~+2e{iImFV-IQKHs;u=x+=T)D`Q4|ReH<*@LqB5GdPwr#YOBv*1 zb~u%ZpQKzpz&+ioy-ul9m3Xf##B_~)2CMk40+z1)d=L(wf1|lhw8OYClbi%Jhf$P` zL}@lN^y|S4n&vFB2{-qXYoRB>Oyw z6)=4nN*odK<@BF4D^7!z(?drx0V|fAIPTcu&5#Y~cPD$z6h7f9?&RBQbVUB#&24oe zNz2cQ@YkExlCX@3L|j8Me^w0(!`L&5qBoKF8Ab5(Jqb4@ zI`Gg%R!69>!kk@hC~#~%&0f?FER2#-T+*6?j4s-tPTx|P53yNg;B)mYhgXn|rfGW4 zF{fSm0DD7yKVqsFR^2J(? zZ?b!4f5$FU`x~PripThsX2AA^UaDTm3oJ;nGkv~_Z%S0jDvHD@M_&iNfAB<|-zTUQ z9oXB){?0MJU6MRsO3rvo-$P)8A4>TkqliPEW86um&9ksGe?1LqMjhvU`+cG9>gJ?=(abm6t=XYQ z$C$wR_}m{|=%cT!1NK!+B(KUNa56XmbpUOLSSpgfvj9d>tw)4o(2 ze_B+yTr9g8c2n&u^sbd&thD7kNfY}|x1&ir8^|~#Yd24_k+0^v&~;JSNZnaF`VK;O zyCCAitgR{gMUW7=0 zoVK3ZPyVQ7-zoMzF4R>EQ-AGtyRAl)f5yFhSHr=XI~rg@=~$9r7%$eSeoD(?d)Ikq z0in7YU6{~6qEmPH5upQeMHa3>ziG>6VWE;X=j)Hi|7A_M$8jM*6!{ZfX(q1=VGa+W@@wYl7rF1CD{&m7O@_6f3u7eVz;qIrs*7S+F^M*O(p7K(9>R;NTsWZBB(mlGfobSIkr_f_3`4|ZgXMZA-d~S)m1j_ zQSWaBHRepuIdPu@SSA)oA0#1fS{dzvnr5X)2nC$|mex=EjKYjL1kDG?f3Czcn2$iB z>d3kvhl-?b$O(;^DyUrzvt?iFd z492LS>LKTFoT@N)0-}E4ym_Bh^i?D${XVf*yTh(bShCBPX@98bD*~r3UcP7amDYmz ztZJg7)yt*j)vTDG9JE2qDFE|?IVXW?;Q1?THz5-)5)5DewDRF4Be|^5EsbtuZ-jjt zW|W&`^(4FT_ClwU9M;}z&TLqJQbt3DU{V_1c(?LeY~KCghh$rufBW9CUHXcPJ>t7` z*;I@d_b@iAPR0Yt!u=GER&t`iEMxV@5MA}s7W>unFncX@EHIjVFT@fv-s3=gSMp!d zWuLn48#P43bZ6E?59y9kJX3@a-6NVwho{5GJIq9>qZ&EODEHJQgvKQ2l-h(-XF4;@GB_=e}lfS4!C9CVGaA9m1UEc zt(zCM%$JCz;s;^zl<@JaYjG&*^59P%Xa1T}n$4Tby6jX!V5{AoV;EYIR_1K{^xjc@ zH!CGMhFbduQExw`g)Yod=`JF-x2nmUveW`XNuRj-7k8qv^l>Vr`xxlw%>Bu~i$q=> zPd{fCOk=crf02sE`PsH+nyjU*r1=QJJHpln06%wwgi^Zm{2V)``PGP}f-easv3R~E z?$e0n9@H|LWiBwss*cG+C&vVva8i@a#rRFaK~3hSd+}zUTy)8`>nkNs!Cz9&@UL`X zj}PTtC~waVd{!c^iIGkv0(norWl=h^ttioHOw+@_e_bVlC+hhD)mZsN1B?VzN0ueo zwvADds%B<`nyIj5t5qeE4DKrzAEE@~VfM|7A6}gTpRE^_y3)2&IhX!YuGKjdk}jP@ zApnB~p*QdkU*tR0e~C8Xt0{xmq1Jmc2ETX{=ZXlL-K%hgG$uSYOKZnm{~?b!)eObX z%YXM+f9vSp#z%rqb#w+3J%u97`ptG+PZcHCV4mT)LW^2oe~`4B%9}Agq{~g&S*BSZ(UfH>!2;TQ z?0K9Juj2D2yBlirMUUn9yE5CV#1o{*dcKabSOhVR8=Ne&+Y`x@Q1<@9>w5=HswYV=%|y`o?JbJne@ni; zu5C7#>^rmsJ$s*tkowR-U!()DrnD1DK7bd>Z8r`~cuoxmk2&Kb9U1OS%8~N6m}r-t zL0{irMxwPVhLL(LmyG+lVpw!Oc|yIHCx&rmQ!_xmz=!G=`I4QVBlpAXywqcz0atc+ z0O|U+s5^9prk->585l_Ax=>BCe>WQM1|8)7Kug6MmW@ec3#=-~> z+|MiJHy5qC>vAnm#{~4rRk^&*1bThve-;oWj_w~y0 z>S_GjsUu2vp22d%;czY_9Mdm$42r15f1hqawYVipKV$!(lekOMK~pWVFd-n6KD+yf zx%8jbJT&4m9_#{Vs|ltUzFg3?wnZ&XCR~(P6l4Zjg0s)pTPIJVe{+_IOHEmi$TqO` zmj3))R>UEiG@Y_PI$%iS!Vn%fr_5C27MCB0DS@+Zs>B}ijl3S}D%`gyi{;kkXW({m z){D2}Tzh6QBA-p_O52kR&9csg>GGC~(C1I~_NmMjacCDgR4#n9Rc$GWFQ2fA9%~18 ze2aINc2A~AttdTAfBe3LK{5@fJ_IpBmCo()3{CQx-SLl++~*Gd4-|grEJ3WKvmm&E zrJ%J+X79P$eyfPpo`Kk@L;#adjf2E z_)fX{hse400v=)|RFCXi*$^Ei+D1sj#HNF(5O%~-ojCW_9QxhR$)YlC`pt106ajp+g(G>VgkS z=+E$=gh1AKf3jsj0(#@2-O1-Jg1l`@@^`GOEjsaRE{J#76V+^bOjZT-k$quJf2pT# z_^2(F=`9c;=iWY8l;)~us{t14osy`PF>rHo7%C7F9!{FNWpN}Yf%dplfd{C)vhY=& z9rAQ#zo!%@G*U0`{N_T>jNHa*($yqL0G|gH>DF~se_sWr|5)eIeu=xDZlZ>2XP+N` z3~zSPk)&L~!h^n^1{DA{#ft2e?!D3kmlmckiDxXZG(vR=EvHn!lsdN z;6f10Hmw6Ljs0?*{n3xs)NL!8>>|})19G7&@Km2<51v~U9;C}(T+W-S;n?d16$_S` z=xJGJ!)W777fzC%qll-P6$hW_*MB*myw?_uTSM5K*n|l#Le}Z#%O?g$$taxRZQD9rhh5R5&wCvroEdeJG zMzm^fgw71qZrre}9y2jeiNJH&?l5*^RM`C-LwBOI5|`{I;lrdFQ)Kx%Zsc761yCdR z`2u-1DL66j)$jSD`ARHl)ALG4p!m7^C@YGtGF;9C!YO z641@|(cwn**Rz9pfsYP4f0 z=-)JO4?{`x%B1@$#pX5x?wd?RVWqWjPXfHVP!%5Vyk>L>=8S)g_#a*@p30NpTw&~5 zUy+EDH3hA?lw=3D70wZP;EnX1S%e4DlCRELr0?0qrmPm05<(~XC3=hkf0EuZ!E&OR zJ+Z|O3t}c8*j!n{b&!3nYELC>Pq^hxZh~sr%Q=Yd^c*5wLPMt9Mt2_R%tZb#pSUf^ z8FGNBdbSCID_N!oZ;JIHRt>xux05~#>2}SX<)?O-y`f8#dhip6eibkPDK?o_@&YfLn@=et_RoBf$5E#ZsL*%m0& zx`11y=R2Y6YCFV)^csayfea~!Z_?lp zO;MVhE|%g05`7Vy#`}3}Hm% zDtwaBCc10Zc#@NEQnv zIVnEM`eElxr7D~U%i-+x z&7wi6f{-ORy`$=C>QO$VtR3AIkw~B{=xNSvvJQvP%p@UFf8(3!y0JZbpgY=<BlJZ$YikEdp1Al8HjruxkJbM^_K)+FoTX2tzq8F4)Bh>E4Fw@Qcj!kF zpX7?{*lV$qf0%uC8v^Rk?T6d!L3H3l0oG?14fUd^PRC+;5}UW15u*?BHnPtlzau&|^+9n6*6Y%JK@sJNz!@ffceQ-8vs!-7RC z9F7F;ZiQ~MTA|m?NyZJ*n&k2=I%vPeD@(7MO6AE^pLj&8g&jGmdo91|bVl%xdSxXd zvP5L~V0xnd!XTzp^Y(n&8t`ZZbN$~?O9KQH000080A{lMShvo{2rCs1X0rTPx~+Ix z`7!_iH_Dg(X$TsZseT&^f9-w!ciT3W=QpPimPI(riQEiaN?fABi_N4|-n*f;q+ zE9xu_nlfmvv*6{fxh{*~b-8Hnk}3d}Q_Waw|&*SE86C_0%Jp1n3H?Lm&`TIBDy?Tu=q1@3?zTT8o6D)tuH%E)A zTnBj>$h$x9nyh~Df1N7wkGd?>?^U^6LPyn~WvzZ~$~u3$Nt$c*qRNh%YBxCw;E`9& zb(JMJPDk0>dA4bS7xeylRh88w2u^}cl`PlEBq&Pg;B8i!mDBBdy_;$fK~fv=q|DE& ze0IdWiB-KX^SXi8TiLis!|*MO}m6m6^3Kgzrq2k>K)e^hlwzqUpD`wTl*By0Ff z{W^o!W5TiX{It!sQ1Yj3*_tjMaKs$ai(^YT@e=2bRtzJ}>r z;e^-0pOZRsUXOz&d)rK#9YFTOdX)hpbu%WQG|uDT`&Zx4uaS%dP*ySn;O2l4X~AHP zt-oGEzsJEkf4Rw~DZG~Y>#Ru-hN}Np`FpH?y(n&zRi09-QVHO#^x)5v`E`~~Hx-Oa z)$GOrtcGosP3u)sLxc5Zl{eExzRGy`msx{l{{aIrrG|Wcr-_?Z(0>>)TpVM0g)pWt zep8^Yn!5NptKj#`s(icif-1^t4fy}eb=9y*b=3((f2xLVAcEfm`Wt+krGMj6X4mho zvrPB>zwwW8kk?Zt$b7cSj*f7Z!K|OFlSIg@2EJy!&W$9$h4-i$|w_y!>bsj*gyv{q)uA=WpDyfBLchaDI(r)4;ED_4~N;e0d&Nqe9>2 zs~kubJ{dHLeTJ2uud{O7oIi;lji{(*uJ|yHQawgIr<-JclPqD#xcCA@&T3^;;j&RU ze_xe#7LK6U(a{_hTpc)ECh~9|XZW86TodHiU0K}(Nre&%#MErNgs}|ja;>G>llUxt z3ka_=0P76b3C=^6g>_pMXM?ztT5k)Gcm$6CNwp}8modFL3!u#Rvu)9AgYSwguk!gQ z4qm{d1>clRn87tj4G}IUGIVu75FRG8e>tqi2Aa%Qbui23$rd?d6V%DBCS-V#2Vp4n zVQy#Ta$AoBSlB><^Ja?!O?9rz?J5lr0@kdU8HcshM0&&Cv`}Lb-!f2nU2n7CZ9s|Y5+rpwOJ>l`8i+nvmaxjOE8o9*0izv!4?SMw z6|5Gn9qZafcE_}j>p{Q8syEpV=Q|2l(lT(La9ib}(F0jhQdBUTba=LIL`qyhh09K< z87L1o`YnK!YHG3eDkS!zGuJdQf0U+%c9jwlG02hGiYgWhA)Ny^4DCK>{6#B?V17{q zOcz_=itsWTjRP07fWJ=aq-m;1x(;JlWt&wp&!+sw9Zr0tY;2c|VATB0WUD&sg+hZ3 zxN}99LwR^fd?-o#Rh+>`ErKJeYc=#3>BD^S#C`Ga?u#?cB58G$RaDMUe+%hwLtNLSE^@H1c(g>jQq@$zR}AM|tf^O^ch z|AI!(f2%UHpF^I&83=)D*MXNUmsBu&o6)~_*DI+2dirXcf^Ozk*`$kbEW>(Yh{h&? z-huQa9RRfSyX$;@9gxaXe+RpA8{8!z-#|{RlV*M$liUTimH^ukSfDBb+XGOz2)P(A zmt>jbMcojuq&8Z0DY(mBgb^}(e+P|XRf5J0ow5hK zO9vm}B;Y)$NkG{!o#q8-%2QcF%wWz#8DDedD+0sk1=PtwzXg&d{fIl-vTRY47}_NG zv&ckE)T^?*Au?Z=HJW{M)L=wf!`Q+=@A_75k+IQ>$`s9@67s?u;S)54R02%7-emK9 zk!N)uGz2i;f<%M%e_uX-c?zhMYf$}xeigt|62@0o;VN4u^Idp#)dwtRs!;VVIx;r z9F2@tUo1>xEgGS0or87+V@kFVb3l}4h&ZqHN|T@g2&W&`C{smkw8F_zSm+yx1zetF?8pCw|Ba00h+XG0v>6qs)|+M*jrQv_mQ|r1 zqD})EbS(NR0VQD6Dat6tU>rGongj-L>hpP*>Y3tQ?y)g^NPP3X_(Pyr&-zg7H zG&S8J251*i#TmFoa31w{*m#?fUu;FJ2Dl9J;O&f5JF5zCI+L*_ninanZ*23EyL`1m z*vQpTrO<#>0c9APoDSs>=sfiT^m0;Q?s7Ix6*S~gJ**b?Pa8%*~ zYh4o|jcd204=vTFQPZ`U}txs@G`OCtOeD ze@m-{kPb7yE=!=}O_eP&l>F!=YBk5Z@qd9+b!a?abmn^?V!ni&6X zOB?3adGMw(By(O**`s}~LB=5aYqZKD)0ULNrj_HZL(|b?3t65N)v`?*7N(Y8JtAj& zq*@#aOKz>Pjw%7eV0dZBXN?B3a$1MPe`Mk!Yk+HY-4jk{#~R_Wf#g_^fWe@6_HDv? z%)3y1$_tYXn#^bh+?7N(XlVkW%|KWLxRD67mgXtQCH70CKDLDRE6`Wnx+alGlwpS6 zZ<~onST1{;AQNiGKztOzZ5;2wj zwQB+eE?Qq)5R88GKH^I@@|8c4J+wfmYVh+_-t2-1D5({{RAMAbTgD0b=-Xb?sF*J)u2t39=o;V;|Gjzw=(ZP#c2k1XB2|G z4cIk^g5^QQ4wnb$Y%=Kb01u%JfA-bkAp6*_wJ|75rQIh~dxQhR{b|#nX+)Y80t4b! z^>J-07R&I|8bIO@AHBTlqYBoyZ7h56Gc^CQ0I&_t`LroD3z_D{qHNj0LER%W7*{NA zHRP-w2nP^RqM+-|h(vcfYIsjfL%YT;cVK;U+TS2^sGe8JMk~EVfE$Hie~>S;k*yoU z$mX#AER_lH;SqxRH2pW(t`3f|JICYTScdkvoORp4vzTP9`~}! zmpIm_@nE2e(LTLKqQV~+r;jg5;6Dua52FF_VgeOis2yF%#@g#|e^L4r>npY*W`IX0 zkFDT{cHycu-4^h%34q!nkS#ozc+_|N2>4mN>bXG@-QDj_g@Se&T+Q;Z)@%UQ!logJ zeayB8ZGqF@kY3RL>|YM>gxCrYkAc8&d0Be8ii%AcuBG^)uBK(na6RB9P^FD;iXxXK zsiku7JvWDoh2dzle+QWjNz&7H1ZWG2wmtVYei6n}6nA8z+l#Zw=ro~j|%P1 zTn^yzi}gjklsaH+KM%l1R?}Tl;TBXB<{b0H`~eYSQ^mKU*iJ4RhhGfZLHFhFsKc4C zdJw7E{G}CMRqv{8w-v02=w%?y8=7x6bkn+e>R&X1AbJt8vuZh_7ON* zn#klsmCu zym!=3-*X_>#7A+?{alPnw_M{Oj!AV4u6Y>0q+0(%o{7)B`Q|T!UvahIT;Go@)x-03 z;j(7*ixE^We}GOnjm$b~Rj8Z!ynYv;cTQPl3xfw#3>)D$`>ekyJEO6vm_=wI_4(h# zVN2SfxabXAWVWMiZ1Uh@!DB929~*@XJ(5io*>A<7Yr}t60g60e|q-Gr(^a}{PoLE_P8Z1H5gh9 zrF-PS!cns)N4}!}48C3H(w7}pj(y>%t@=6vj8Jy3(@%UsMremhX@Tcw8X9S9py~@E z4nGgqEFMS7}Ws zf*EajA(yKxDcapMt!7Y+p?E3^?U_@qO4BjvUlO9LV3YBt+(ayI+9^;ruvtX$Kg(j( z;opUq(hw89jc_4_?e()xiPhDOj-Vf~ZUvr>f4z?!$u6-zMnuWz(8lPkwwv{jcIQNR zEK!oK#Q}=UA5b=4I$X!#8W-^0!`9L4Ph$yfLU12g@FouaW=X-$aFbi#c_YRvZ{P!9 z6xUfJQBhGlgjo}fMsbp+wzGxCN46!UWw)ZC;JzVAU^8jj%gU^C9{O1Gw^T{Z5(J)41g8ss*EA#|hU5#%*U zZP%LFxr#tg{kVseugC&&&cH;*Z;P8%_t^fWJ$Uz26Yum4e>=)9 zs$$ztgmFdVQ&kxSp9g0!zA(VZg-9`0nilDkNN7NvU_0CP*$IXB;Q^3(+#-Ob#mQim zu2(E7E^KLYJ73l9$SDWYl*qQDBi*1_Q!RN_xf@oYh@FmGeX_?8^oDcc<8^;Z6S(FS zy6iT-8L%@k3J{0Nb!)>Ar!6Sne`32G+Q>!*fVvzpH&&Cm=cIo+e*9-Z=`ujZT7>I`Wz$?LaN$frehd0Bz^J zfqYapvgd>Mtx)JnM|JP)DM#<}RG*Ksza4mfQV;6?bUdSq$Yl>!f9yZMd9R?^*4N6c zVgk$B5YYH%%Mx5|M%tIAr%J|&v56{qI~RJB#jhs_^D@B-IAR{keswq&9wYZxBG-nu zl0JWat`D3?d(B}f7>hpue_yaK;L=~P6&_qxJ4OA)SDey8iZiE`cVQ1IAOP>mDy^+L z>Lsw}i_4yh^>)2Rf3J&KPu@9gi5I~%QX&PWBYb;!JXq+w^6HC6UHRD}Wm@Y#H-U*$ ze5lvKs?=kXt4G=9+A4PoAO%x*@}j4+`0VciBXqwG7zp}&68+u?AAo(HmVRmYpRd3B z_P*i}>xFfIRbg0~tXd=>R6q#AOIQ^<(8Sic#L6$W1v>>Xf7FiPQILf?Jd8pT$Qz_1 z9Xxt(q1TENqeJf@Pt3d%JnX+4NzotqPKLJ*JC`F;ExhnwCqC>$%GO|x(QW<^GsmFu zPias%^12=Tj_n6+Z`2Cn(`EEem;Qy@WBC{}z(gak1@N43G(xR&K_=NLAsPHkZntqOJ2&!6}EQfLXeoo(ZbU(}QW%mbQaRR*Kj*hKxP_$C* zI>V~5K^V0Ruqq9>17}}@PUX!16eCg7>E_tX*(vo($S2Lk`_%*6Yc^@J-D!dn^8@+s=y%rysL&^Q5Zv`qD5D%7WwTDq9=}Sx&q>U$3(?Pnrx`f8O8@VVb1^ue851s?Z_Kg%unFTR(20 zax#v#5l)UF=hFlps9CF|QZB8u+@i`!+8t)mfX?ba*)OjCIF+7&7)kL&q=3UgSY^c$ zV99P{_7;b~JyS8oo(5L7F&zB}Q2&v#m0`NFFDA#)7cW19caJ~6`1|qYhoj?np}aYR ze>Wk#3E#f*h&W;oP4P5pF|FL`2rvGgoc{di^pF4czdjm2oc!th zi~opE9!!7y@!jdA&aX!h{=-RwsHX&3!6BBWjA*S=)Y0m~x}4C4rl2W-ZSw(if0!X( zxE0;rz`P5(rHvc*=+Vno4wb3!>2DCV^MO&p(DIGlHc411IK>!!qiclhM ztK9%@Csk4ewg>IWIewFa_PE_34Z;Y-$M)3y>hm`Eh)fxZ0A(|mVs-ZhfqI%B`A!az zq_&){ypgGoRO2F#7npJhrp2ITeV7Eos`l)Dnk6e{thY&(W8G&g@lKx?oFH0wQLXYhqHLrTLTzL`rt4b4*n|^GXH1^0 zH7P|cPYE$=GZc_HolPi>bNmDH#9x3J`qo^>- zQOh$X$yw^Smt7P6i3vp{r^C1+P;aFbotrEjb+9r7+8XER$I5H=9? zuoUT__W04gw`zyBe`md`{x|WeTP4*Po~zNcKTnZo^kz?+)_4rr6_fm_DW_~Ex*59c zVzVTjUY8tQ-~m(L@;#S7qwQ34+bqC7UZBvCb~-`R{s z>S(}TQa!a}0?F${p*=~ll&DLCaNT3d)>QDgL=7zatYJ?quQXc~YCsXq*5qI9oa=4) z%(|1d$HG^|nl+|%dY1sxIb`ZGFo2X1WGD=JW`bz8e}nc4zDodq0X2gH%vsNH7Bwtv zQ~7Mx!kp`}F9+PWU;}V|*EWCT9V$HKhJ7TUp5PZ=aCNd{<-=6%2whICKerNXsF1^_oVDc(oVDX3N!oF^XXr6hjJY%En<3FK<`1maVg42#d~`xjst-UukZiX2=;h(#vwT{0L@SUToh^qx{G851+n!`-e}b zpMLxEoqqc9f9TyhnV0q3=@&nyAB}#T**q4|!ojn!-on+zU!V1!VWj%L6`IR}$MXP4QFtV$(Q zw7F#JZQfAKfleZ0C6MuEfT^-2n0Q%bx}H?3KF={UGjpqb_)NR0I>*eQsgU#(1A&$) ze{(}LVw`C@uaOVywEbkum2Fzwgzh3JOY{tAa8z?RtDd8opjN<3!A~n)pOyq#Sz)c+ zz;r3a5pzR{gNS`~7+~KK370(Rti%+A+2cJGu)4Uz&MNDX-_0e=Gu_dfsc1jC&QkYlSSMq$?CnYiee?H%?l8%Sf$T~_0racy~rQ~_tn^XRu>C!|*tts>5oA6~C zXBpT@9sf~3Gv)2QZtnO_b|sP3wn;_#FnqQb4kd<+VMolIN0yMjM;m;qi)a0}e}Gp> z1|2#Yb6u`8Bmg=dBg@D=A2T=r0B}H$zkD{Th1WEc)?a5KT)^M=9nc=`LzHnKD3=4F z>5O_5Q6Hz#h{6dV_;}<7h~o7WCGCh)+A7Mig!bzS--cb?p$7*)9##d*-f)hK80%jj z0pqo6@HoX1`tJ8KnFY4FH&-32PD3UOr++9AXP4}6t9<8ltB%|$rt(W`hw7NpFCtUQ zo7UsNJ3q8xqr|h3{t=gwlMS-F4PzL>z?w_FQVrlY~np*SZw&PgXm zT)W=2a=FZ0tI(_Ka)5GQ0+^*vSx!m=Zv6MgnGhpfnh9`GOp(JRS)i%{S^$MA^ndcZ z-s5ffEc9kRvIk5mh;aloJA-G;NUX@}Z>@^q6J@=}>CDG4QE->SmI?z1MLMZ`S9>HN zmeG757N#)fQEE3$_ICxJUZY2?X}y|zDZr1LWftupC5d|R-FY|B1f{Y<9dz&*pN)Gw z(;r-O&|#~?gKm1QISkdi!<(>D4u4uEmwIwrLYIIuEbCx$y4QO2rS8W^1hRKkyXQJz zD|Zc&F>a#f4LT#^u-iH(($IWZ^Mic`&tFV7EBPjXIgc^VRh?t>x=MPP7pLoNU4p&B zHhs%CfB4~t;LUemennbqb`1Y2U8Vbp$qx27gCkRGR$C*B25%`$rFxHV@kFKM z-N5+9O}3)jR<=dn?9d>8_+@tc48zo`htHFG_X1H^txn`<(J5W9cM5Xm{%Y*j$%JWS zXvTf7kYrm~Qv&NauhYDu)PK3^(oWu=ANeGaa{6>OEI`}h8v*f-!_e5KH%>e{o}IxY z=WVg>iAOg{E@~4dLu2TMCFUw1ze+{(Clipa5**!Ey>GJ55%n6h_>FWS-O{Z-P?w># z-M~K&Ng`ZjKLL7ju_>Fia?98GKf|YUWQ(VnY`0=I!5lSQAj(>?OhTU1ScU+>gjd{6JBhYHHD5+XbJ zYSz_;{)MW28PI{01b;8PW${MIzJ_5+d>g~^998M*Y)E%c_q#YT_)V^WRZc-`x`sw$ zJvY>v8* zW}y^s%mI{|;QR6KNCsWUb03A%X??r1C)8R!NYgRJVt#NQe1H7t|Ff$Q!I z<@=Iu>jPuufMdU0gG2Rd=f*>F`lJdNhW=(wgq$aUjCK0oCPoZl89`!le!#|v?sXth z_p5SP^oC&xOMuDjWiTIN4CfZC{ad_!5=aW&V4Po3?0+o0hh{rdi02u_oAgU521m!M zo04ufCGY5%BU32cq1>FVV70H>(dY+9H!I=@W$D;E9kRiq`fx5I$EgwZ`_N4f!69TW zwH**WCfS>n0hi2b{MSh(Elbn3@~FtKO<<;nOeHR}8*Y~~6b$4d0Zs1W&XP3p-3v() z47=*vV1MPEA0yZ0j806{V8Gyso5Xv4$G{CfUj$F<;Ev*8I4xj{h`mze9xBiU@s8af zH&Qg#xS4wk7^GOK+%B)#M^p0|BJ;*o`zoKod?yN#L+^ZQ*~xDkPl98J%mb6m;#Jv* z7jZVAtGi@%6IrwEaA0zolk=OZc|Fv;iDeVTqJJ1uqdn%)qA-Y`FaIVC7lmKZ(Vh!^ zDuKNqXuchqiCVcv&d>&P7alac|1^UjRB zZq3-bO&pY&_xGv&{C|Sy=l4qR6rA^d1aByWd`6hFPW<*1E_&qqWR9Uoon*x~N?;I( zPJi7*drNH+6S+>C?_tY%0|XZZP-afY+w@tFCQ!TB^BtmCuC^+qC}jyR$zSz- zKA`Uu8siC5z^8W$m=1iUv|m#sLg>*i4GK`zaYb0HLo22Fd1}Pmq?`D;GpBpm5m#bu+VZwOnxqnVk za|kL2b@4?!1vd;B1zKVTZr_J1|dIzuB!TE=Rco^q#LmlI+B|{M4xF^VN5M$dqzc2X?1&-&WEbxZ?<+6 zKz2g92>mWxk33rBcI1O`j^B~JLKR?MZ|4{d0$i(KnsRqk*vn42Ty>Aln^(~f1;k8+n^Hlv!ZIX;(~b-@-7K!*COOV5a)k2KxLf z@NfW88U0SBK~d~NO2-Re&tntU_cTg=c@Mg11(TIUzOG=ucGWz^9umhCsJ?U))>h(Y)Gfth} zVxb43z5x+^m+^L%I@^Z#pHKu=-c72=uoAVMS~C)H@D*J(nxKCB@#rBtp4$&k?lP1M7}fI`vI$$CzEDG$>ett`@|6ZDhCf^^&6q=)?kERAu*C zo$)HW4mb~9FaqQyk=@AHI<%L|<%clI4F<_pa=iqLbkL?3?5FkkVe9?hx9y;`;OA@SeH{zbj zDQlfp+NI0+s{Bbzzmb$$rhAW$e_5B+=qi1Nvfx1Zo$h5Jk2#6Ic_tgLWpYGq-z7Tz zhFeQ8>VFp{A+$!!uF-p~oNag*hC`C3w#%pWxAly{(=>JBFLX*anxmK<8kEF?HLf;G zPJi0PyX#U#T;PgInvU}zrE9L*4^G6$R z24U$w&Re4gnc^}*T5h3Mc4 zmjOCz-?w+Mx=luAX5!LgK&qV&6)_+H;=eM0s~)#vN4+zyC2hq}dp9W#l`gN3E7aS~ zhO%}3jaxnN7$S<_^vgO<%QVDQLxS-u;G5#&Mr|e zAa51^kqBgNNq$+C$@)7OOA zD`zCl^<$HRx~)QtI=oFD=XjpfcviB;P{<~~H7_{yhqueZb(uxW{TJL6M;|aJvVZ2@ zx&uVbpIMxFLYH~;p6X%0%(-)ytK!C8_tWsg7;J(qY@geeO86@qkyEO_emC1eHY*GR z26t822ICb}+`GUXvnzyN%)p52+k$%9G}(I7uz)2%m`kyp4$dc25U^NhX8_91=Q@dz z21dyWDe&XgiVq?C07a5ctjoKDwSQ!b)VG1Rwq*3@fG_k`-mqu7jQ;j`WGu?BC{ROf zEve4%4d_=_sy|m(fJ2ROFeoy*YpXW;g3Ivgih2&!WHHuVKA>PTFK9=OGxowP$&S1l zUmVhoD57RM5JDe-?)e0n;)xE?H#f+}?0+ClO(L&J_3F1c6+HAJ-jeli4u5A$1rm?F z8i@H%f@X{Jtv2xRqzRZM$nKy|A#k}yKo6O~=KPI~Inmkug^zkCvh$Fi&msxhl0Y+G zCfRfEI-R9})qO|#OZ8l~xX?ne5zV|#ILBRRxq;Np#T+&H6R4F0zLlBVf_Yo&^%YT! zNY%Z`ctAGPUX9IWpsG(=uYc`wH;orDx7rF}(BJH8Cz6*^vz_*`0M(#XUQs2RJT~cc z*xrCU1sKuKc%xYpX~ag3-g=zr=!z}k7=h|PL5?d=(@u$j{~wq%=n`I!)C@jJlm#^OyC@-SK+1b?+HRv^{s`W6(3EVdKMU7{k0jR{qbc_kEl!Dels8Si7N!lri1 z%*M=2wi$hoYH08oJ!;(dweYI zPvpOYwKKX}?`X$U=?`=|t>!G?42o3oBpveT?rtFe`n`FC<`Z=_)yxoPBigQMrYay5Cr6DQ~QtKM5vSkT?#gNL$d4kOpusBcJDZEt#CiN&A4~rW6JG zh0Mh8V2iLZ`k7|86AZC6u{VM?E$Lw{Z1*l2)~D3niasw7K2AnbUP=!zHIi8c5G!qO zSyv%&8nm_ocr(JneUR;W9#bqDzq_1d4`+p{UEY(1G$o0r|Bz=rCP~`QL z8!)oY#;{)Zu1?#OX(=x$sMMrpW;Ii_oTyCc6k^K}p?GHrKTvd@-7Uu}Oz#H#BQoSv z%S@tYE$ANgtfORgy|Mlc{}Wtnc(>0co&5>_gF4<6R@U6jb%qZsrsp2KJg!~o57+sC z$6+87gn#3OplIQa@4=0f^{d$W4^Fh z5d|{koNvk^t8T9;aw6Mtnp9+5$gBtn%DtN1x@l51HkdVM@}QpJf-<)n9K5nY?(FH` zJKB0gc`ZX9KB)D8NMm;Iv3qK4WG3FJVSOiHJAZ}-`Q>y?PaJ@9zbYzooU$H2m&$pe z${dm&>)X<3iGxfKJK$4bQTSbH+MCkDjmL*ERrQ>pid?j4=qNA=MFUDoB`%@v7=SGh zqn)uX%eOuh>L8zIz|2-Dxr)kDY%1}+By+!^u#+t8L1b@=ADO^syluc**KTm)1Y^_Y zH-9xetmm&d93oX<=;pcZ0CM=&eGvP$F`TYP2w~J^^?UNAqO95P_?1pGg&$ZR(?JvO z;x&8^c4k2_iyqiAKAk;pNx#>WHpu&gxajF{H$Oe8iqyvH9zG&MeG?Bb-}{Pi_4IgJ zEEh}9C=S!=z2&yi87;pz!}E@pvt(@r32pNfaZaf>Hp5N@j}ovM(f~}2t?gLMcK}Bt z+oP!n*+amF0^r!Mq8+HIw8+q>Df^-9%Qg8x@>EmhQ%Z$upnxHy@jjZT#`|g9ZjYi~ zF_|rkJdc(wZWZchJM>5Y2T)4`1QY-O00;nPviw-L;SULO5DjLs{8;UaZoL-=005Mi zVIT=Gf2JrTwTh6o8!#Aifi88oU|AX@X)quOTt=d8wi2n4R1%}xe)}$8GWE!c%}8vS zJiI*j$xA;)`_K1L!Ar{4Gt{cp{ttR2dKx`NhrHPey51<19H!{i!OK_suMS?lMwgV~ zEjq`aX_Eu&m==UdQX<8X+7NWQRU6LGIbW%Je=G=k&s$bvMPUpch(H+jV@lB5R&n^Y z7KGG&G;$X+W=f({4`5fJEXt6pts5K;RF*mh9Q2s1ak0I0gP`W|WfU!!xT=;*G)Gr4ScQm25Eb9R z#O31b{OH5U^7QS+#p3J)5P}$Pv?57_;3eWDxh`KM*EzhW&#&*Fr+;3@!=dZ=f6wc^ z(coShr@K9VdwcdPJi;DdBz?!@*}GjnEK(rq9}skLQ5+Bm{UHH>CDn2suiHU zq)aNz3X;gIXU^1kS!%ZqnFP`+3Rb~Y!kt*ULX4|WEFX%6{Lm0?7X)11<4Tf{9KSla z(fh~O@m}mVT@&SXl0$929$zMPf4&yHZDf+d>eQrT-BZH3EQj1HHG2@E?NwA*O0>Ac zRjVUD;kTbjp`3i3mHP8yLnX3Ec<{MI>>}dw9FYfz6KNRc-E#9_^GQ!=Z*~TV=ks`m zDqP=|7(HZYJ3|kUU}&3#kZ)uDegvXz{(#=}uumqiPx|7193y`01O9cte?Ioj7}_WF z=0A{!&>VwxyoMky#u$(lLMR(UAXY+%3Firz9rw_`tbcWc`e;ZbsaBZ@hRmEW?8kBJFl->iTTevSRkhmNY^BXsIyg)u&FJ2AJ~=_~{k0Aq8Dg5aDR# zlPn;2gS@r8d#Sr4@8B8}f97Z(pkJUwTPX;31ARH$e|gix5J>s)Dks|8*s&ht%nq2s zL3X}n3O_7_fXt0s_J#3#hVHPS7>>jldM0D^%*ae1r|7o1(y$%NF+<3HBGlMcC53v? zL-NLJ9`h!?8JZ8KorXG8@?CM5u%poux`Ly+sf@cx>9qAyb`xG-f8L}6V{c?f2%ZOw zhzO>|Xqxsbejvpj72`=GBYjMnp35kM6riQ;V+56)X(j9*s50sK{wvYiWuS9r6iL&l zx5%n)?A#@&UEx!+2NzRt@hBW@lOz+Tt=_?$cvEwh<&-YkoZwanj0B2sW(sYlOK4`w zWM)qT^P%6Ln0F}tf9Zs=t>SPnYARz__*pTWB^8})AgoFuLu&dES111`gZ{BTBGcvQ zAW^P(bCVb^_LjJIz^;O`efjR_=c9}BewQX$_ATWc#m{7J{&X4|SYG4L9EyZ}rYv}do_}1RCc?%@`W?8; z2U5MNknY6>Zl?h1w0A>EI)!(p8N|wLWV=3KT9ym^aJ3tn#zRObQipImGL7C@s)Kh9 zox;0YB;aitnQuwj!%kenW;%Grm|!}b1;R_equvREf82ld9cGZPU1fW>c>ng}@x@aA zD6#nc0-|bE!=SQN!_g=r{-Og*c9$Ms2)oN}VynJxV@c7Pcyc1CUu+f`38@BhFTs z`(m$efBJrCa=zmHOWTA}CzjW^X@GZ6X(hJ1l4CnT&{HRP_^X36q%8sDp&*Sy3-jTA zZwTtvM1S;==|;9^0B;m&aJpTZeY08GxzlwXQ0om@>n5r6aiLF^r}4kiX&ERA*JFME zh3QKBvcwAOhQK@HKk?CfJ4)U?bJI{Kg9z_vGLOB}?c;AbZBv@QZHP?6RO%8TX!Lql zw&)*FO9KQH000080A{lMSgj92qh=)l01ca${%HsrmydrN3zyF)2?c+~k-zJ&*h=*S z=#aETJLy%eu2pP0(TgqnNpj*;R$+)(NvJ@80YJ%W;{W|-X1{?2NGZA3Uf=O07Kz=R zot>SXubo|NvC-oZn-xi#U+%JHeKGnke6q2%vBjPji@P$tysBCFe848#I}>(t#o61t z`l`s;adA=K#wBMji)DYF#C2NaV`y~DIeT^Re0q319oL^~7Uv0j{^s!H=-}&jCvT39 z@gdaP*xcMam@hIu=Xp(yS#iN?0RNiTF@THNMNz807V+#lzT_2qJAGS?H#Ux!i$zh^ z6^pZs&8-IW7#@Z@P&G-W8F$Cmpcv->MD3Wz&#e5Ohs>N|#0?30mLsrqZsJ>h9 zYRGE-siyC;)jWSLuK|#1mgX?BMagSfysXVwpk*1)>gfC~y5@I7cFF6Ap2>laB}S?~ zmRGYYn8hK^&9i6Q(f8A%vFTJpqjKw7c6XpMg}+ywl*$S<;VR~>J$8Q@sBVKH3*LVBO>}Vh@=Y{7eE#Oe!QnT~!?#D%m(!!8=?ldj!5MZl&4u7$ z%L--@*ECUs?dK_}XMyArgWOPvTIWEE!WCFE&0joAE;$-yh=oS8c zb6yo0uX*JG^0q8)(u5NlM?#uanqIH>kG=zTBZ65#I$AaGXm-`Wi~4x><$u_mypBP) z!T(|+%rDiznzdHjH79X|V%0yB|KPJ))mML0B&(Zw>T0jcg~j7EnSKI=oyVD;HJdY5 z_2dEF=8fj?e4P(n`nG}i=xvh)v41xC>i>D}{}j&=;PfW0xtXLEB!y}+`kIJ-%wBHkYnMZLlG|7%(KkHrm?R{%RMT9Wa?vu=IZ| ziA(;35yq^T&6a2>7WpNZ9%+u&#+5M|$Lt`VWy^%)(>at=DIk+^CY=p1RkGq1Z4@xF z@C#G~C?Q%#Uzj~3@P>xSDf3Oj&EPS7Bk2j((zp@sZz3jdKLRxEL1V%B8ydIr^ zzu=*lpJSR@$fzfrH|*PZ1j{H;b@vKKNCx#aISG zKrY&c>hazl{1a}5r+?o(`(m*9O90;j_!bPH5_(z*zhKI%oRAlj!2$^76X|~^j z012Nfj=++jT3&pybM9d(0IX3-)0HW83yRUtsM`jrJHR(fA=QaR(ez(71I1wN z?utQ69o#TC{NaD_pcQZiIDw{_P&iW4ph{Wh=}$mkrBf?0I6HodTl^mgUk6Bt;0?HrwlA>@H}QV{2hHVS7DmxeJ!% z_6!uWrg4XeL9qZ@EzL977W-M%>WoRSU2Zoam{lkV>{B= zIfvzNTa?$rQl|aDx+t>Bfv|)8iC#6j15OEI`3d|11r)(yZ{U#EFr+vU>&$9H8;MDR z8zLnbt2}?k?Ze8cFrn^Xlo|MNRjp=)Ocb0gUCW`0 zsRyt&+?EsCBs#ew0fqWp%uKn=VAkXF0_GcB=e&Qy$RRo*8WN!-87;<_%W5q=pe2LRh@ z1eAa0@_Azer|`wW^Zk>l9fWPg!rIYbH?%m|P#kdVg`>iWrk-6*NAGAm?D)hgvO~CT zY_x9^Wo1yRCw{ta=-h-uS4Ckv0@A*yH#!-#q(@7}(e+9b5G!I0VX)%5lB z@Z?xAV`#;^vHacP!SgpSY!D{&?Z&n{g6e+=y)BSl!h(N1 zFQ7*PjN)8bnkcW!C1~>k^+7(0v&B`MFXy}j`3!)Wgvw{B2^v3 zJO@xx!6+Crwx1*^jyJ}AZ|KGXQ!SuXr6SG>6_GQ-&N<8scRD7l_n4l*akV@Tp_d_* z7;{p`m3&ndv6Z9?7&6W{7z`W<{iuH$fvL(KF?Xs>R>&)U8PD#NArcDyGP8~eNf}i! zhHHVbkdkLSuDG0RoiL=$sYZ?v`U^NwDpx3smvu2pC}_o#!I+`VGmC{K0IotE=XDC| zU05@ODb0$Bwt)E*mzPV-Z>m^G5&T@S>xexDtLf_TkecIHGEeLTlZ>mH_#J* zU_FVlQI=kFx!6RxPNuMQNVaQ504@yBHD)_AqodWOBIj zmjo()wRa~3etUuJ!;b#K`aFL{uo|KUb_T*|35G9)>e!#zq+$1v>5Cf_(%f~R(9qOS zaaHn9vus(x)L5P=4r(09NG53O&u)oKT&hTTP*m`&(Nsi5c|J?295Hok$cfpL6tG84 z>?DJL*w7uFL?BK=tY=Ure^iZeb!$RBl=T}>6m|Nc$Sn?8dC;x^{%3!rxLPZOfngzU zIkvQzz@CWH_IC3b$}T5tZ8xy`iYNh0JeMr1xk?Hy3<_I4%i=LL3_;GJPyQjdMIE)U zJ3$?Y)|y@xByU>=G2sYRLm`f>_LZl78#NnDCBdj#HnfCieb^8&BB?9{-9YdFA%z|f zU1d?1cPtnwbwlHt4k}avq-|B2n`FLYX zVCd+eMOiF>lkY;Z)a}9{L&4)bvIdE@F&oob0i^cg3)h6l&jZYO{2W|pkoPv@?V0ns zAVgS^m2@@~WY&K^?3o#hHR7~#6h&iBp<~(KNx9@^iplK499(0{fyHE_xvr?Sq(w81 zVB&pg$BC0KBUVerDDPa%BAGVM%v~%1L6Mn12KLND z1sqm7I;~u081Ko9;U0a|G;J29?nO#&m0SN}F`i{b#ch9+#5A{Uga-^%nC8w<=hY>Q zwRPA)+F*X$v%};8P^W?FesrTs%!1ltKV0#gh6BW-V*nzwYWC+b6e-;yq6Vf$nCMf7 z9wQTzvM|IR5g_7Lzz?eJG+%OmOP9ggt+ym*BGybiE;6ig-rl>DlqZUyKE&j9k-5;` zw`*ZwhhBfk#}~jlt=?9rry{R}evhjK7+Ya51P7Ujsq+s+^|KZzZN3p!Qn^$v-fbl% zNF*Z%NEAO!B$E*6cGo%w!Wyd~0d2472U;94qz<(d8qC_F z_VxjyR(Vt;Yh8pNAbBllj(Y}D^ghHeXHV!X86(0_Xr@anubd;o*pZIy{5xS)v}JX} zP}P5uLwkT8S%p@xHC{(Fp&e{el`wPZRR3;vhM(1<%)dG!R|;4%7~yK>qm^Y%3#;WU367cd$Fti@gr_Ie*B2t zUloaoaYCg#3NU6xfnE#vz$Nwh@gs=~Xm$q8&~K=!^fKpILZD^QSc94ZCk<7pY-4{7 zr~sTkh9>KAfK_CnJ+|*~ZIVBV2a!rDNH^({vm;Z^@>c;6efR^qKN~fN@J`NdgP5DljQQ#>$AQX1%ZTtYN*}d zZd^hLPVa^$SAyJZUJX`8aC-xlzz}#|pe)Gq$jNGIvfKDR9NbPNkMh2Fy* zAZIy)5*Zp|D`+OI_#q2XpASXG_+Zdx*>(&2?6!w)${atv8Dq^wDCMe_n3jL%;9BZ_ z{h{HVxeOWl!gtQw#aZ&QC_{A^{fGUd!-K>63&Xfhz0RXM?|#z&H;wu1+IUM+nV}+~=u(%tLq`h; zaUfPiRU;DcybNy?kNqhaoNj;n3`O;kKnU>(w zilpD=P{K4h8FOED#A4&BCi zJw_K)0Kn(82SvuU*e$d|r+LB9>_L0@dzHQhR>VRiDJJ(0f^C1X_wV1c>IxH>wUxq6 zMph`%rbyc@CSeyb^kb+bul5--sQ?6@4;_Exbnlnrw zED6@0Qi+V>OKjk32uWi~7ZP5ebbaRr zCFo($MIYal2W@}r{nrvh+N3x>&61dUA0j{GoQt5d;0T$Kx9#YYuJ9=BG`L5Gi5Oxv zwb;c{pQ}uFCk_Ez-!j`Ouc5NaLjO(dY&kqy{l0PIlJ7J)9+!V+Ym1T$eT?AWLBk(rntH8a z#nx)oZT^5)Pe{0kvoMbzRhyV|=EO-Y2Jc@i$o_SKAxx!uaBD$U?n&Q}dr7fMIJKgP zFRf$v`hG59PJ-M6L9ndEUd%Gb^Om!sfuA1Sk^!~7;$WKAe*HSD(D@q_VKO8!eU_9y#29jVFbI#T};ojq&c( zVB?2xr_)!Fx1PRMNz{Bb{bv99kN&FaVbFiNFtc@>g#Mn`zLRs9L@EjQ-dfOC6&?$v zwz?f%A*qj8lGjDIa>xfmm`-+E#tYPt?im+?qZ~|tlg_R{6y!<9#o0xe$&paulG-$e>**DULW-L56KtdhcWy+c>H^HTo-BQZxH;Gg)c}`;eMPT;wSV{mEB_dMH~sNgAO6HofQtkS zvd}VV1C@N&cwEV|cJMe5$D4woESGNCt;qxC2+Tw0uJQAmrD)2a- z$I!pdmuZ%mpT6E?m;A<5lR=7#mWX*=sLSCZaQ(YM$t#wsi03yc7}cb%)G&XJe>^B67EHE{wMs0 zTn)Uxn}<&LWaW9RR)U^EiX4CAxaN@rqRwj#nQxb~H5ggjsU*+zth!#T3nNX!#>r5{ z77YaVdht}l3;U8Ld=n!{i+s>VRGb9G97&amevSzQ73(siMF;X+o@dKVGMZ;^M7ETN z%{=XKmqo^a{ksTw_fcA>K!{2mEzS!(x{8;}fIEJU*I*>Py>aYJ^v{3Zf%&8%HET*l zzwH=Y@|>4(U6iVwR>tiHpBrTJxrF)_4jsQoJ3=jTkYsNr9Ft(fR+g*!co$QP zUTC$BQA1XQf2aRdMXsjyjH6+EGW? z#)MnzVvr_U;_VDwkSN$nmq2uOMfR>LMUq>kIRCH=I*W}nK*%~bw=@y0ie)+DBJhWo z{knHvllj#kQ`3K#_FWoowfn3L%FG(6!P|5Ak`6~|vlBBxjjR-`ukSS(cQgvDC1R-r z!LO@ZTYMcjmYWRVh;mngXV+-FUenuEL^%b1n!4YXEnaj2;~G}c-%;yUb<^k6M=x-ud!yfgR1I7Mz-$MmS?^FCao?LSl?;ZHSYDs z-!ZB_?l6Dj=sPmvM_KXwN|bTMtbw+VBktSPZ%>`{LbbrR{YZ}P?nOiQBzRfULx}xm z_JM5LFZ7+eQweUjsP2}Q4Jhp^2|6WBEBQ#&we+L*JM3#944t)ueQ54Al9aah9a`bC zMz%FjhL`zJ#Z1I4+5VPZ1sBTb2uM9UgG0@b0qcLsTESZlME_$-!3aATNHYJtL!3Lg zH_kh_HdSsa9=FM5YO3C!-(1P$jn5|fFa%Ov#z0&9Vmcn+5VSwY7|k1HoC_+v$8j2d zs^Ayjiv0IBr=tIDQ;HY`9B%#MLoWEFH_~;QU7xiC5q_G&=(jaxRv9uzqNx3KABG*r z&)0u8SQ^R%xKW069V=tVe*WBP=&fN#B&RKt!9O5}?CaSILgCtoH%tXT@&c_+%63z? zw@@R7IbY*}MJam^KEm71 z)m|xT9O-b+#n-iLsp4OiTG0x?dmBzWS)hOW<{Xh3;B5U5DMNA4)ur0HYj$;i3+c3R z!|PcO_xUU(X91auWlyaWmT0ViLe~4iU1N6BS=A~Mo%#?_Iek^Hnf6zO6{1?pGp8*Y zc3+g`C1o!KJ%XW0NsGe_)%?d(IufbsYA9YrTge?ip5HXBOgqi)Ys~f&$EPvailKj6 z_j)REfSYc#$9xX?LR-}wuj7d8w7R&XU@w?z;?i$WP}cshxK#5~Z354`i@1#E0^wb@ zPoXXZU#WM6jW99{28)AChctjSaD~Yrs&0+1!;!n=h?Rv&3j zR|)34s_kRf3QB$hAUXzby*`KIJj#F9J^N;3y>&`dTW38sgrg+Mp7mzpP(A|`Pvs3= zWAVrp$~cBZEIXVrpK1{?*Fde$M7+4dB7`aG3dSsRU=(!~uV2B)*TC(GOzmcIx6be& z*sWY>XGf50WSD9MY?a;FQ)>#l12X*x_ap5BNOXh<-|;6LG-ok3#w%N{9wvWPOYk=1 z81skqDj&*o1=ff=VMzuzSX%Sm9kQV4BR1@hSr{7V_ixL0RFQo1ny{%xp1y zllN>{V!{Nt1X(tu*D$2D+6R9Ga?R5mFIxu7Z^k9k9l-EbjdMa8;bE+$4uS*l|a-h^({HMsN)*+y+7ERNmdw#Inz*;q`N( zR{N4sL&Z)MERA?SBv!i6o%eFOueNtL(eZmDJx$rCHmXfCt2K;!np%Gk8I8rF@{XJ4 z)i?fx0iEika|RqFWuajE*0$RKv-Z7-8@>Upd~smwNS~|c^*i{in4k(Ly7`s<>qcBx zRgTQ7@749@t7C;0vdT7ZzM@MOd(ZdD6M5E=Oho)QI^5^bOM=F>|cf4q+FPjC*v z$@KM9U!}AzWBX2OIgIjs|gjpI}g61|dw5{yg29y@xK_%r#$T}P_j-2qzzG{zEF6-awGtwq1R)aZs5 zif#_s19QjL${91cV%a=t@}^B$s+l7Q8xKv;X$3c*{@wP=h>bZ=Rb)Yu@;zOB>Pqc1T>WoWZVs?~x7NWU`yr5}DG( zdAp}`_sU8fv-1fCwwGcBpUr*ILBRQxHv2Udq3BSIC1faw?PLi;E z$X0_6Z3KUvr(VdT??=LgBniC7HXl`+XvWfmUhw(NiXh2)u@n~%jJ*>X<%^`C7xixN z{zQ}2*)mQ?0C$b)3H~Ha`LNVjblp<(Q*GTz;aE-LO-_!@B-T>fYHFklo38S?yL+k& zw%9rcN9a|Ac&+5McD`Ia(CK_NQtT0kxEau&kWYW`bDu$D#)+yA1_QNFIXV^nvzUn5 zp4Ex3;O&THYGW?H_9N4o+poHGtR4qTk&hp$x77^UL7wnWBALA#rb4%U*@yi(FL~TH z$mj)i(0mQL8akEue%z*Trpb5C=zU?XN7fImIMAdENaHl01Y;*vw=RAe`y!0W_Gu(W zlLdc7Z(j_(+W@Lr^JKbiRqnJa`x3+LCKLP0c09ViORxD87sRT!CD2Y#pN-YL%DwaW zn~($I?v2LP=H#;U9Cve1x&6+0cQv6!fd+GYA`GUVz|6?*)NAeN)nX`?>=%V`p75l3 z(#>N%0;V*g;geYoq@{-B#gR`1!5TW}qS zeX$~cTf%o{LO2d|dpjh~ZsWVwI}7ncCOaqt+KIBEbZo_Xc3nktu_^0pgi?WjDm6k> z$6KYXKk?Qx(XKP2Ps9D0*?ziB?8ceICXL5nB7w;lf(s{rt;5(QCNT()NHlUb{@5>6 z;-7IBbGx0T&7irH0P4Ra#J&tPmzM+vVP1$jn^KLBVO|kyXTwl!sH|cHO}%LD9-+jV z{tHM}8u0kh)`}fa)Yp!y9G(9GP)h>@6aWAK2mofX{8+c{ObO}>5oWUdSUu<5V4oHM z09;7`03MeCY6usWSqTb%y*%x5+c=W{^%RJ_E>ej^#mQ`L)-{@0Pa@a&&WT;NGn2g} zr=lcCVoZ@-0<^5$$D`c6$Njx0xi`7)20#KNB|EcQrXAxfpzpPdW#k1NJ)2wt2K%N#?)qu@}#eUJhP7fANz28YSU>mR*NmqAY-6=h2KO zg3p;unOt#pxs|IlVb|$GZo-_i)3iwDp^V@eeBn6@VZB7=(X@~${95LmuX!R7)-~ts z{OtAk-Ss$-_mYLloV~tycXM_2^ZT2Nt807+<2s8xU9%`<(K<_W$$s8SF3v7G`e&Rj zm#`o6GZp4r&doP}A@gD;%U|1ah>X@;f&Ut1izw!1jem}^vt*HW)XQL&uCq|G3IObB z5iR>{&LyA82~QMJ`#A9w;OKcd7ip2txG+-}5fVkeEo2m%7e3Cq$ao$Cnfh$W0q8xx z0oXF%_K}Kr9DV{GtA2+yKWYk}6*8R0T!F~qP%hGZ-DgpMGP&c)U9ZzYdI4WXhHwE4 zJf~;=WRiqyKAFI%$(!-%$@}x0$=j26XQ$)q8<^mEUgw9O?$*chqI;Fo_mi$Y1Tne(bOk)Y%+p6UnWE4*t{auq;o3>>|xi^4TgvCKWkg z^Wpnf@Dg`9>K+RCDz_PY-9*XD7v15H9f1C1FklMuVoM?(W?3A~luQetZ;ngm{I5ln zlPHUT4F-r8NSTl*HjmunOWKVAiQf?_?3m9l^XLwL1Y*gB^LR}B1w_vI9gkUz{~dKh zahBYrw>VDVm}ObfF0UP4@>NUSwlnOAA#oqs85vnAQT4-HZ!|r z(Zc9`B-kvBW3UJC1BtbWasfISC7f*{xq@df1SAJ>{zvvneh!}Ye)@yg>s-ISI=j4?+>GB|o}b(h z0Ko8Ihh1wL0#JR@y+ePU&Wo7$O3gg<)fZBM9$1J4GwQ%bAp4WaIs_$}c*mrC04WIb z<=w~UpVclzG4*rLABY(cOMVKn?dIP;1)z|Bhh8wQ6#gT@O!-u zY8g=aGUtN@m$Ma-6>TVr8cP=p=*=ocXPX=Z6ck#>Vk*$A-XO{0U$D^Oe2#=?D=9N^ zJRB~8CB+meKFqdRILxAK7zqKwF+6(l>eVZp{@aR!?at7MCrbt+`fS5the!*;C5`8Q z_!vwI1N$=LY|6pLfay#?@8cL}!7zZ7@QqQ;fSoN6445*+OKmJHaeo>vB+sA1yin?; z`dXaGULJrk05wiHOy5MX|0$7VQJ^OZ1r5!@*=@Mwxbq|hXjDpRkPEO=()R)owWA&gS*P>c)yd`M`05&l{VPda9^l|(`2Q4ty(m2c z=CK^Psfg{@mgogwKFKZl-L8^b&nola-Y7vK~ATBMR&9?7r1 z_Kpo05B`Cbt5?URYeSyjawfoTBQFRXSeaNEWlI5I{wx$6RRcVnH6J&|@56NlgNB93 zhtnt-!VuOS&ZAt}Cjq+z>Bl#JU&!!uEdVId97F&hcO3waoSy=w`6dF~HmekZ0`mqo zUf~bsHO!@=qUWsQ^^P4!56}9{q9}0H2BWyoi2KgGiq7_AjJA?g+Rq$Ry7lD=1$%Mt zP%!kO9y?@y9k~hx4vy%!ju5W=`xiE=R!^05bMW%EG<8rh|B3UgG+bnV#ljq;ROX|A zBj9P`<{@}7h+i;{_yWNLD+Cz;`J^*xiZ(K$@etOY#o$NTHF<=$Ak4X+ zP7#{kU$?7-<%Fq$pzH6v4!5thNUZoYOqN0oL;apiq9l@&i7$A(Xo*J^vJ=pgmY2Y6 zj85BMg3p@7A-F@}hZx=D1#cNN&$pAjNJgg^<+QxVSU$~0xCcv^5Z9o>3^rN)a0gWw z!l3GhJIKKl#yP*;p+LkL5|!|EU(MolI;DWzeF+{#Dtm`^P{3e+$h%NR1ea`$j%6C5 z7$ofS;`;1EP}?OSOoK+uPX!rk3S+WVME70~rN?>x1w5tKmR9Wlpb-HulRA7CnY%EL zLX2KUh`jG(Fyws*{^Ip&11E}aFiw8~H0{L5*L?4FYV#s70Vg?P_LdUq{mt8K><@s} zH>V;T&<6h2AHlmZcw=9VT_$LpEp%V zwjkG`jVr{5cHjZNLYr1+wvW*|Bn$ZZ;0Yx|y}ippRNVA<6kenmPi(HBqgrIm!4@SK z8L%mw`!9MVum?}P8fRualgz)k9_Klh8qO~0ON-A{m4aXs<_S1I-#dj^T2+1!Ep{k8 zhop`98X;wW)?CIcqWg)8m-gvFuMswsLax}lb0~fwl}Ynk?d#Et{bQN4V)T)4K393` zd6X3QC~n{%MbLnw6GW7{PV?o0UIa0?$KaYkpnhKFK%!i3eNS5-PjQ2m^7itt7Soh` zi`}R(PB#!w^r{HlQ}o0*ye)t_VDCkhEW#k0B=GNlZxH;a8?lRtJFwDk-MAG5!;Q@< z6F4oRcs|kLM2p=6_aVV-g7#(B9&CO286lwFDHx<(kX@?kgNP{Dr*y!tHq}Qz;7%#w zZB`MaqFN?osxn6ku8b0^5Wfc@AV(l-2{cMYtP)B1UV}C}vw-kK-66PV$_FbCKmymRlfKV+V$qE@%vX6CJ(CPG;>I%oh;byZ5qGV9S>tN1@ z|FI4Q)rp4a_tQcQ^(KY~F;A9q)gt|-K(ShX2J7@IWcXo_=F6e}KA2$nHB6%1vR z4Y3^1Djw)2wZDH-h9j;vC=MDS1pkCF=ALZjfSt_e>?M%{%_%q?4)#kWP<%tRE>SGt zk!w5j0u!}qEV$Ne`7?TAi#$Af*)VyQ8zdVt4?(7q79+9_?8u2~{c#25ljiAT?DNBzvU zEAVUTeM-DN9Cu)8t+i)ZW1hjFuIG8Un#Z~it;@Q9c;1Eo{JFaq9+uiGnbt;Dji|+w zHB(xiLs$!srWkMQ;IW<(>AbbB1;*8XwiRY*njPQOM%HQ;20n!Ws7;XS7F0?E(_)Hw z5>gy2^%9AVmwO`O_guL8M8w|_NbZE{M6(S+uP~+a zmk=GZ&e*+PGf~yyv+|hkT=jJVHq`gVAI5gBL=yWT)|M&MnM+$#fL2XA45FxiTWqj> zC@i7}R`d|$xmv)6rt+&zBSY4zs>Zwz(6OY4J~1nUmi;B4{ZwyK-lpEqXYbyeUA2+F z!297w>ifdOSjP8dOQ?L*`+3^4pg7x>o=9TV6Ho|xl0#Dd!RbTIagTf=XXi`OBOURk35`aN}%h^1dBau|aV2h_ewuDC#!kjI-yoBRHZAb@V7N{AhN3M@9 zN(oYovkn}XkLVW(%7Bx{;Cdy^?@u+gkM7U4~ zA3PzDiyT=AE-J$m-zJ^k2)k!-T*vY&oTf+2MLzA~voIHQsEp>8vP?V(GvzMLD{A)uDU?xcGeBbGMa3_DB!7Q@ z5Bo`PBd%1?Ac>}b3_wrmT-kzPH4)%>Hx+}`d2kaVFgrnVPHh02JdzSjq&k_V<6POg zZY6yf^;i&SYn2!_Ds(2tCjc#SsLO2clL+6xp;1(4dzjC8h_2?>FfP;#-?M4vALcz% zu5htesso|GekZR1^_u2O^A;O6NV~}7hHO>{3D8Y$!F(!zgjmy?OzHK%mEP9+HJu_O6H2 zGfQ)3crVNB5(sQ1TxL3*H`PuU55W)4q#N+6mYWfQ2~-iq4M?pH96d_cRm#o=Y-@1m zVB?H4Sch(Zrh5!KBe1+v`O_nS85Uxoh~G&nO+CwqPz#> z*C<2kgeWbT1EN4@8(`HeAdhSyVM4w=_{}dr&s1 z(MRN{?xS2Igx>aAWXaK`Xf#@7(Cr68YPKAIXt{ey8t^``q9k@VY>nhlKOhf`P2}XYqOvN00J+??Lqgjh3!OwhQF~H2&#|8YTswK$^zmJ4} z6uttpLjv#v<8!_S=`%xB(MGD{GPUsqkbq%_SGyb`1oG)6LoHAVo`db0`u7nK{AjMt z`LqC@ogjB8?@~>+7SB}j^_ckaaeUe-{H?@o9r-u`$4`cxZa`EavGlK`7)5)TU=K0J z==l-;)@}tzWZBE8sa=IAJJEnxlFn*>LxuL$szXWB4-%ehmkN>b)%?X1&>Y{ajdfM7f z4lxM#T{ht458p7`E6e(t^7U3TyhKm~AN=K87cgSHOHG``>jZ8`YImsC9DVe;nT$40 zIkEfE&6f0yC@U@6akyz$%&=hQ+4O2XxPbTv11+c1P65cVC2WH4LCZdVZbU}-rYZ0m zo~L_aJg_?yZQ=8#0=f9&n4LL)S-s9@p9Sl|PKmFgQF+Evf>N~w2a=WhtE_)FA~clv zU_i8Lno7|dN};h)jh4Im8|wvZL>7{{&Av1Ia+FmYrTw|-cZGc;{ca@QmQZ4!wkwp4 zITqI8{7XTVh+6xvA$&6sx7w$jn|~T%OKlz-&Nr$T4M--f zVVC%BD5FkVG72*f1@nt+AkzWnXuUE`>ecKN+5-E8<%G_hRO6w#7!<1uG$wMu0I$e7 z9dFI^?Et%?r~!H~aLSA!-uKz!ohA?B4r8G{#NrDb$?TGYHd(;WI+Rr7aBdR@^wh5qE!&l+-lzN% zb}LikPp`2*n%H8zjoLb`G3MhMYXT80^R&qPBWkZlcT?9b1+me8)!I_gp^#8C>H)SQ zk`1Extgei8_zy&7fB%TgjA+W777KL$zGsXA+8IR}Z6XFgs%TbkY=1~mS-Dohb#}K? zfvV0aDp~?ZYgfDdNYm=WsUq#K#c7uBc2>7q zoT&NOmj_CpvNQXCXmPzClh2mRRn+x+O{ZHU#a>;ZSAWiQ^L6R2HP^{0?l`}w35bTI zf1y%HP1riTKBK~2{gzP;9uSL$m6sd-=8Gz=H&gb@!vPytMTD}Js1LsbYuI^pevs?4 zMbn@=jpVeL-Ev9&xp(--Ap(Ja9Ac=OGY#Bft>fP*h%3T>-A2j0rNy8ASVJ%iLwHDI+1 z)?nt3PoP16rPdX7WB4%%U;PSji&w6ncmfh_F3StxTMoErghYNvxin?@f-#Z31SoilTlCW9ab`SaYN<;r!q=7Cf zJF17pFc;4q@hb!x7{BU+do(|-hNoGrsyOw3P)h>@6aWAK2mofX{8+aia0ws14`#Cb zSeyuY0Zu*v007DYm;Y%98JE9+8w-bVEef}BEelva1ZJ}QShvk<3&_k4X0rTP)0B`2 zok#!x$_JOPeGVFzy*LaBe|>-3Hj?oF`4p^uIZ}yCJ4w^_yz6$ouG{#U#C~k2?Vjwa zv_#unQ=~#te(B48_GjJz0w5^c?d|XMYh#H727|$1Ff$koR-dc}JLUYY$}TROAlexO zFE?JiTzk3k@>OtrnFa@U&1G2xN9B2QlT>N&O}QwhNt2aD3^k9^e>8Zzx3l~HXg6+d zn;mPhJi>=i?nySAmsJx?%4s^OpPX0a48G@iIzd2nAg`zC$3@zf zEt>SU$+I*4q`u~=7kPQ6>SXjIse`(ij_HRgo|Wp`KkKqk-}Ca~A}cP`&$3qE=6B;s zIh$v>#-P&Q>T+_Ge>P8=>TdH%03Y?Z&Yz^WlXTt$d-P*6J2v&o6m0Zk{&7dfu zBiCs~)u4Q^6}&Ht7VzS1UX_!y)@ams`fHO^=LR}WI-A?SZ=0JcnLp{^)m&C-GR0m# z20Jwfi`ndMtU(}YTX?F0Y?eOZv4~Z@ud^BlYnNJtQI*$7f77H(`Am|@Wje-I#&x-fpr=*vIL z48B}8%^a{A2Dh`knosbnAKIJa=gTE(rx>FYC*~Owtni8H|CwT_^HBF1A z2LP0BlB~e$e-o-YPpTS!eK>qelphBFlg+<@0UQSBHOvE;3q7C~#m7b2aDNEO;K>u@ z+q44q5eAMg(&jBai^k(3nWf_~lv;&3c?h#8tI`>u8enZ4L~IO4atpQ6#p%}Km=`h0Lwe?JQcPrlzhJlflTKfbrhv*&H4 zo&9$Q+lRa3{ey1dzixf?+n4bF)~_#zuYUh+^si4&x1`#;?ZfYO552Xp+Tge7OZ?ZY z`CC@}R+P<2{X8oK%6=EavN}2Am)q~Z?!1N3SFhhgPmd0^cXr)E(aDkO+!08*_ur0p z-h6ofe_f~8=%hws{Q7$|c=mjC^Do2ouRi(L&wu-mr$7DY)^C6P@?>;!iev&XPo7NE za~i00JWHx87*PH*=D9N9slBqYLVpjiV2}h{Cg3t8cdN8nR0X`6W+YaV>W+jcFeMdH zRnpse1p=?Dj>O#oo6Zc(#B91p8=P&({= zP@V@Qpu>Ryv`%d>A1yQ-TmgfRgaeFEe|QqEgqs~X=s_bZNC+FOwHZ9E!{BM)p&@=s zM1tVQRXWd;Ng90|24OgO0O4M2+9<>PY-%Vm`e;ADxr7PdM@#%@9?>j1xm*-i#3$uP z`tuRFU1m{}b|3(?OiBh@TfxU}JNP++W%{b~hDO|lQ1*bJe#Xgsj(k&o9C!;oe~e_@ zw#yjmgOkCOp#9Uqp#}2I%ny@r>oQJ?e~{N?0AzX!_w+(q(r-U<7VWH=`WP~Hr!0Q_ zghY-E!x&Yah+6M9&On4fqwxN*5R%)3bLsskTH9mua8mCt(F*@BDBaVK zE}_fn&(T?4dF>y|LaJKh7BKtRV;ohM30&7~3Xt`{Kx43y#ahyK`x73b5+O8D>TH3A zgEEBBs6pGIT-2frf*yToRQ@*h5sx0_xu<-zc$5?_1>m`eC?JT(2f2lECKB8p7^T59@ zEBcI19g$IYMMY=W>xYU8Plsno4%Yn4?X3fn@q2)Ri+!kjM2+B8KSYE<*;;8&!-r{4 zlX5moG$-S%XRLd=#F%D z(k3q_g9i=OUW=m|st%d&k<)#~f&~Lt|W=(;-xTHGOHbnRc5=IC~t4hc`1kMV<=$LZ2~r)9p_{pi#&mMT5*%}>{F z=czgB+8p>{e>s0#EqdG~_<4Z2V>CE6huZqR#QaeoBv04l|1dHcwLxqw-FnOvhL4hva!YblSz!LXp)10r&eCtx|e<8 zAt*w5e|ZDt)Z1b)I}>x+hL*%<@dR~lO$HDIY<1riWpOtnCFoyUQZPD8C&>cniHe*D z9O`k{wCM3km1n$t?QwI%uytl@?NLSW({k6?McF*{Y80v zf415&^cMpYb_>j^p0@^1ZB@1MU80PsQjSup$5Sv_aRF1mtFlB-{{oDi)}Q$eEK&iM z#Fa%Gkh)TUkzlnItiX-r<9X76=}$26s}-+Mfb|Ky{|m}x9Wp{e6~xQ)bh@^_R;R$Q zvYa)S?eGIgoCEMj@28h_jAde|0xYm@e{8MeV`6=Hdw*&4(P9i9y=}dp*p@jHiAk`~ zC{GK1+s;D*DAX{>wiW!!E@-6RSyE%H+2Z^>yB$(Fj~{ zki82llc^^4kHQCmzsu{C<9v#yx+A5j32tDiM<0%NW|(6V&&zpa;Ws4Uk6CJAWJo*D5gPofFg4|#2|`3hR)AJgTt_mpK;g@z z#(jyXzN7t>GyyooU!gU{ud}LIeTFJ8yOm$75L1 zib+a3;hfdW{0`$+^RmFW$RbUr=mtt^0W-Nw3baEpv<4v7&EhP`lSMJXe^{JZn&6I3 zLgBV?i-q?2mAGICBohLxUi(MEkMf4cg*uxj4SM#-#XC)^o2&>)!bYK|FJW8%-VaQ5 zU`ZH|tSSplBzI_xG9zz`r)|Y{)VpwOaBmGAmkb(%e>DQQAyT{KI*!=dD-%d6*j7sSb0kG#r&EyHPwjJ`^Xj&Fb(2H)u?%bgRrsW1OHWdQynme>ky8Ig~D2<1&#U zA>D4$jL+`ISLs~@6C49lTbXF^0=^gqW_-E93>3z?MStRYo@Y(uu$!SHK%qb)UoBHZ zo=<&&LL+#I(tb)`dN~Zk=ux*ULBYXL_%xZdeL;>F6%|?Tc`r8dmZa*6#-8XhQL||9!Ooei)nsE1(^5 zmQ1ct=;i4}GPy(YfOX3{7FZa_Qv@rVzHhcm?W{_y@S;Yc=m|TCB30}!>49kdSRquYKT$c zWpySe24w_8R9e<=>-wk0GIZ|~RY0#=j#5RP6;O#L#s3UQzb&@Yq0ml3e^#+*Jhpkp^D0+n@mt@nx9>f0l=BM?hu$x(XXtmnWXZYoLo?Lho8O6t=r}ZfTu1yz~ zQPtVqf1`QUppfS>%>K0+yO@Tym03^z)}CRE0j5~?>LZ+%h7B>5WXVBJkw zkw%R!1@!n_r|inTt6{nu2lCj@R|jORe_#dl9-$EkHY2O$O$nnyCyOpHKy^VUClKsp zQK5f?hcClOr4mHAcy-Gv4ubdOEeX{A8zO*cl5$MG4(Q4}s3%nh8j=dU5#LC?m;*|@ zC-yoe;)WF(m+MK0rqyl&syC4_*jf z!%)R=w&Ty3J<5?TIB;oMcL=G=KCvIR4)teLu@%jD_Ih8U%QQC z0WB}l;^%MnYp_xmRXQc<(im0(z2s(KT9(x`GNr8f980>5@+$yJC^;(zf6kCGDj;AY z``ZOl)#C~1CXIoaVeBFBaiW0;8JsO7)QnqP9P^-q2r8Bl``~p$MT!n62CmNS!*m2$ zW;6qH?>Gf%nQ@Fgzb69$Yoj<7K}7)Kb_e!QcC?xbtr?j6TmokPEm#liMguT2lseD{ zb|b^$Nl}CZSAz<<)sW6;fA)Yh?HCsu3%F5DgXpBiT-xpge>*64*pyIY$wQke9vKBoO{3t$kMRt(#^6i+N?__&?L?WB z^E>gF&}0ECly=HM<>W79+-+gQ_RPU_F0hoH^I;>5?rA6{pt@T_nT5;;tL_ArRkU&Z zV`PZ2F>3CL67U`6e|#iJ7$oBa6*1b%QHUsDn@`C+hV`U_euvhkTy>5+)EBf=mVaCBqGpvM`q15ZtqBGSFcc?aqz$W)(HI3q+1?a8e-gQn(C-~TV?8jJ4_(e? zXmM{Tmgb)ZQ>VqG#8fk`bs?T2o36x}UVQ#M>x%2f;$R2cNANJYB)Dh&9ufen9Y&bP zCel<1Z_b?I@jFaR2;y>b0fh#Q)fXGIWjgn;1E^1tMGx1ji=U1O-9fYCM}06eu^aC! z!;~eal*FMPf08@aRiBoy1gR*G8%<~OJ8F{ds8(&ekqD$#)95hHG{$H%o_FD#i*L#C zC;N%zC|pb_+9LJRspSWkVqF@ywPLE2M!K%iBB++=kWfvXW$0j?CH2*iRSL&xP{(|% z5X9fSth3t?RiMax{Lkb%Ve1#3j-ApD*)d|gksLI7e_tO_0FXD7o=G-@5FA+J`E&rr zqXPoiwlzH4V6cI>0JQVY^UICgtDkv3eZV~xN zzc^>r0-ydyWEcehNn`PcupwDkB2Ghk>W_#ae;>S#bSWxUTM-{)!8A{1XH!^o0p@0a zcDC@&e@6NG_3L40 z77dhNHz%Rue^r9MKg)he*$VF|M9S>a61-)=e?c4m5|>GhZd(Zrrbw)gLi{M zu}B;v2=^!#e+QDRF-aWtSR`wQNAF#TSe3`zT3Cv$YZI7PE|FV>h~k$b(=54=-ugG6 zd*x40B&Qh-wxlr~+o>Wa?Z|aurt#GcSWV7Wru57+km8!;jp{bdV-F|vf5L;i$({lC ze;tS^clGIFIr26Wnh27>3(+avVi$WFi+A4a?tHiV_4v*9(Hje_QBO@cg9nD1#6z-E z@9p`sXLcVbO%IzC6uNVt2I8=>igKi|M^}ooel+cMH{CCTEpt!HGmjZYreM#ckZC$i z&lVR^c!)@m(X9RFxSbXxO*>nAbJ*O!f9TtLbiKfqieM?D;(aFYssE|gcPAlWcNOFp!lT05cP;ZIXKI7b^-M0fw8=z_l%6~LA6lPb0^~h z6glgiI67Ly_7$@~o>Pz1olwFDn>A)kXEMR`wIXid_YdR!@47_m#tw22ub~)Of5w=L zmnzLm#6w~e^@^hR!WeTaB1F*>Pp4*$>vcCBVmnkq+~lAlN__D!U`OA%@}N_40&Ydu zm9lf`bJj@7s^=3qu>PKheT z=%1AHOeBpeh0*6}QmKtguo1ILc+7@Cg)vG2_dui0U=39R?7!{Ml-%uD!Q%_aISvY_ zgWo*1f>4_;YzRGHOXC zBWvN}3ga>7xE_y9JoHAUflk)}*yeXFr=A8>@=*hZ+Gvf#K^KaO=>TX$$>E46ZZ-@T z7xLI#j_NiJlXc*zF#EYbe|DaCS9d8CZI`0_?%iDFAyScC>O2<5TsclCNjX*6+fML8 zAJcNe$>^m7qiqRn`z5u5@iq9Dar~PTx1$lK01#I!wiF<~Gte2D&08TfRXVSv|AYP{ z><0AkuLem23A@}RJ?Be~T2tg1ngn^YSz}^6Gzes2pMGrPe=vlif4fE)q4-o{XmUhn z&&;cxboD`9Ffw>yi&5ZTsO4Mipx%0d4BL;crT9eEa@eJ(mN1a2B;<0&&s<~2W3)@h zc-~ycHSrUeKfCXb5C1VfbaIXJR13o}`s(1X*wrti(N8C%)03lTy-7z->StT{m#@!PW{b)%D!)TSPNIXbynJAEE)VZE=SA-scc@)H2z@6*9o zJ@5c?T;$^KcMt!*f3!QWj(wRC!R%a}kd-TZg4-90Dsc_#Egq=1$|!Um&hJbkj~f55ao$dAEnh9W0oaXO;h zea4b4dM~_CEMUBxlsTV?r8Q(yE(%yfHbcwfq{?i$Nib}2EkK4gsk>9Sh4{%<@S<<0 z@_&`S_tAK1ht$eyQbjEoKveo_P?Yp6eCyrS`%|3ve@q?KX(MA*cXV~3bCuuH*cPGi z0wL!x9GrHif2pZ7EYk_RdWRAxthS!fkwU6v?j0*rOQJJzQ%Ayg=>#{0B@*pO>t;H$ zhqsP{Jq|x=oj@3ELf~}7$q-LIXF5tI4nEWp1;b1Q3dMDX1|!19=hUi($g8z+xDOq| zVJwJ*av}QcNKHD)&|=4%WEmS+4TYtW9o|wzXF;VSRSL0#{iD4fxpt}1^kIo~ z6if*%4E?OaI=C!F-D_J~lMq)t>Uui|&{#AI{}m44)jc>8C=*Y5aI7C7D*s&6jWgWl zKoox1e{$e>nsOE(+&nr1ZH|J*Xor^reL-03T||EV^Uus7KmXhbH|;w~q2*MIm#Sj! zmKF`~m4+s{gzPH-okB$|e?!@uq`(Oym6hp8i6kkfCWS!BD1Z%b z#@*T;V>ue!c56}7zOm`yQdUsM$0ceASFy#TI4f_G3KOtI;n$jPB#7{Ap@Z*~LvNEE zndWI_o8+DN4BW^|u2OUtScjZDozW-%2`D>BKsU2bxcTt@P!I$V5G?0R6v^E}XQVv7 ze+=DptLgWUOLIui5g0EyMP_Otz9<;%P-I1ilD;@$)eUX5Nxtdka@>@%y69~MdsW%t zDE9z^*Ch;_6)>QbAxBvN;0mzd^dJc@F@_vU@{1A};BtmVFh*63=hfH}^q2|r=bu42 z`T1uhlBN|NA^;|m6c$>(gllVSp%O0ue`g!&mJ>c5Frf|opMU1ab_C|FZ&rr~F<~8Y zan6INf+7?}(Bt6lb`yq?cRRlIkJw7@R43$sz77NG3YVor@@BdY&eWmbtIek1ITU>! zD4f-Sa^7^JF#pInNKRf(%!DOrx7R&sw`XDVW6~&w=f9#l! z{D#B0&FsP9-$+1nEyMF8+7LfSHwb-+E77X{*2!=UUOH^&_tSR zwMw#XAKF$GOfjmPlS|Da^~gA)z`#v^mu^ngQje@L?jootWoUy-4}Y%5lBbCd-ch#Wp<4(lgITpQI5wlo|o z{Rs}NCT*_@e%kF3@%(0;)3$C84-WVLdw1t}{C@l0?s)tC*JDX_=kZd6(I{E_Wn=BX z#;4DtQM`7FKgXx~;j_WB!CL4Pjp?2^_7Ut2o*ew+_|5)%e{e~C>%p{{N1HPa=!nFS%g!x2f0N3Ip&Po%;LCtRIod)4N=d1knejNQY~^uUPm+1ssZoU| zXW=lg`_kpd#@g0^(neembcKSaI+l&P9iG3=_mHmQY0=Dl7_ztbn&%kwqK zACJwitESZ*UMJzx`Xr=?KnCr1A1Vz=xek@*M&$+?;wyk1o?1Rz%(94gt}N1;HZYTG zu(v34le6NrXU}wLdD{L4**KC-Lk&j?wZDx*sM$OVIeCk~e=zkkd8D?B!)M)XvER1Z zmh=)_^+3eAPqy@$_g*lHcC5@4CRo9`w`I+Z3|kavtr97!&Ruz^JgN7hy=i27W0kZS zv;Z}Hk|&jWe}@9uiKftP>0~&4F&>JdT~J<6Z}q>3edUwjCgnRa5v1ZSuFvRh1F?}% zQWiYgO;?+ue>A~MZ;7SH!qccg=3^`vEvnq*WC<^{4DMBR%WyEGE3=i?0fR#pc$akn zQmRVX_OsKCuA1^$?w(Nj(q4V`U!TRnH`y(Z3R$$J5tXni7A;tgi@E{PrkV&Y`PSmt zWOy`^1WoFfes@VZZ8)WLJ?E&@_U!~aEk!W10-lC}e^D=*wPVmX?QH_OX|=IBU9Rws7~*r8h=+Pt-h3s8zpWXwRN=A`&SCe1RwerfXA}@Tdr^&+q2hx+r7ydT1L}q@mHo zG14Fi#HaN-{3nHCVg;2h2y@Ua=5ttDStKtc(_j+KhCjb_H8p^mPVZ5$8;jwxs3s; zK2Ah$^ogSXq~kdQ+}Yjmih7UZo0U}MewbI;H4rd#^C?r91&u=Y9FLhulpau3BV2`j zROq}ljtIG_nC;+Ba^u!?bs)2^$^z|kf8@$k5f`oxppopu@gd7O2#xC`2Sa^n;3*ck zuRS283Z%DpY-uyLki8!jE~ts?<~}Uk2tQpCOl4<#*n3K}8yODOXivyK3w`bj48@NT z^69jPAM4mPb9XN!JI3CH)}Ij3LsE6+j|oIZ*eKOT!eMrsciAn_uSZN%o`sske{?Wk zoWZzUuGfn*Ot^EVr0;za&yABCYXFs;jM4Q_XXaUuUeo?>TXZOHd`}ty{e62nMdMo6 z;Y3sg1M5mrdTbcU<+)MVDcb_&3PvNc89ha1K0-qq!&z{10{5 zGMGE1jw?bL=OiLL8BrNZM2q@!G-^SeF4Lgvj}$4X0k~$J(5ln(`lFf}Z9@+^K%(pA z>3L(-)jTUwpoeXHIQ5Vd)Bj&Fs}dLgt|9IubqL>=1{P=rRH&X3n1R)@e+;Y=u3nO( z;%PuLY2Qn4zQhlMNjR^PW_D8=*+bAj0^EHvpLuu9hOu<1rKFl2;*t?7+!GBf9f2i- z&0x>)up9h&Cdc0Vy*}Woz-Ekady|H7ogpX7l;^DHX2uZ#6USvtsYxREcEuRr$we^$ zgB>0CaDnEO6GrjoD`f4H#cx-|{>Dj0Cu%$J55SRr1fn&08Wjm;X(ds(mj#$Hyf89~M4Q^Fbq$Ald zy*CeAN6UegdFq_}RkMCF@xCa)QmK_&nlHhbs@rA35+Z=>RNK;+sGd5mfJqEoOb875 zBa1f2LA!Lh^?|t4i?r^XDkqz5nre<_xSlKn(x6E2!pgpYYX?H_9?qIGU^$z`9O7?ML zFgfQcD1* zy{<`)`wDkdy1Qtvl2;b@rIcER_Yu`4UDbu*(sGnlF4pwaCsSL_sZt;}v%(;V zaZE(v_f#jU5Vm+PljJ~Fj#JEX&pTS~>0GXiv0Yt8QY89hi+`c}rK`PlN$k9=oHAiM z<$8?QR1%x|0pAA{KOR{}D<{(`@|^D?o}u6)mfpVOW;{+hY>f*1t#hEUZmP1l7z6za zb!UX5XsrggBec1`4*k5zuCjSL&62pRF4pna`f*bIGbx_F{G7ty09JmdpyAy?w2C7QWwq_+4zNLno2Yutuz%;CKP8|pi)V$A+Klic4JHfB zsH=Rn%NRLDRAH~m)s__}iXx?7P=JY|D_kbLBXO5vR&5Y`*BRK;Mp^}%q9*I;R{dh8 zGn})mi0p^MwvYzqIa5#mZ<`)Cw0)gpKa4Kjrgx{QMp=<45?t+P7q1^jr`0 zTo0n7oqwG_(RrR5X3rh|+&d3`#~ww~6d%w{nNbPl`a#|J;%v?<(r+f#~Yi#+&r zn@1igD|HKjar>0{v6n{!n&s(H7BNYcPLvV7y?=Ltd&GMuL9=;AySr3Q%T7v|cBKiY z8Lu$k2ET@`pK3C3H03{H>zwmO#8hJWT_9a*h5@DtSo;$G{R?UPhE+E3(zn;+-~mh^ zZC8L(>i4jtm4qinXr8heh->IWf#Jy+rEan!v2{qcDBJ;qZWVRnEphSKrHFLa9!em(o;rTR{P0V4laWzOJl&$I|*XiEpI zE(FswlE33N&K&P;#ckc8+>9sJLRyKrH8;`I_VSRWf&P55+IOubu}_;UuQy=HJcQ$J zoOALPjk`bl?c5CQQiW+RcRm7~} zP(f@T3So4){X&MwIb6eo(+%TBYswkZ_6ShaG7Ygq_ntt6xfO(1s2Jksp-%sZ^Fz#i)f$Dz5r2=l z*fPIZdTq-oTQaP64_fB>pKOkoE_fsY^r2(a_hhN(8+W!8SUSH;4#w`vs;=o%asEB( ztRZ3SqX%6>w(Sdy2A#H~SkZ2Wx>CwNTN9|aBB4fqbSvq=A?5ExT=I z=?r(SIO(t%wZ))xQ<(W``)Frx5A(($RNVetL$6JqCVbx@Zn(}c);4P#D427XRDcB| zEcY@!W1V0akQJd~M#$rL5)LDKxdt`YHU&Hc?*A~(vW;|EPoR@|G9)$w*u1KW#!&bAZLaQi>4ii*vPFrVh<`tMgv*# zyW0sGMxT#B)W9yXb`O0n16Ai6D$f_(V5s!%#M6ws5Ppj{ljLRVjUzzQY^FHZQaf(+YmF?eM~A5solI$)3ln14#T-rJXaQl0aK zSs6fTjg9X98^2TPFKY1e@8Fss4gsTUYHQ z(4r9df`2Xu^09!&!f33;#*g{Kdu>8J&&#B_f6j36bz?fQjlIBIQ|? zV}Ii3HIlbF)2$iw?*-4(v~$;}kKjGr4&vk?lcG=Fc)Legl8TI+`=n2G4;eGuzoICs{KluH$1@La~=-u`) zG{kD2^wY1W&rW{-?c`T#xlOo`T>$RnP#)vA*0|6|rSb8Z>4;Yu&*wNF>fqTk z5`V+Lmhxm|SJ+x&*XYbN33A+_VhtpEb9{Vo^oH{~ zpcCfrNu8R{(B22}GrlZq@~|(-`G39x6PXkIvh)#MV3q$32{cpzWMk z1*C|3`oIEd;O;LVz#=C;2e&TLTe2zYJU%*lyTcD6{CRM=f4sl5|8@+|uV23E;HxfX zgR+3`{=xYDJ{R?0tww<3l7C{!YE0^bZ5XxgAqO%5?QVsfxO_<)n7D%&W<0q})LCd< zUq;HY&JKX(xInq*DWks!{NrE_T zS#5WPoJlPjj5pgyU`OW3CE>?j140UFjaiSD!0%x*J`$!!Yrn|~^qk9pce_^0m# zmP^CdMPSMRj~(dSp1_YSN+XRM?s|cJRADQa;zRePFVM|AdiAEnL}M&Qr;?s!2>{vh zaE0~#JK!CP#(3_1THX|qh@hi4AAoPZ{^8J%c3aWCirM^-4%-p+vKmG1@}#0(@Z<4X ztC1U8QrkU&jvp6rY=55(>++Pbo~W;$LiSo={&et0xjn|L7)pNh9}vZlySt|L)?{0) z&7PbD?TXOmBLRU_VHojUa91uu+b2WEE3Ilg4!Dn7j&bjvcmKd{#vJvSEw9$&&u9$pMZbnnLY+3N2-ZM1(p6SjrzBg%@=bDm5@S&)a^VC&jsIU!> zR2#w_bd1_o8Gw!?<8&-6QI|5))1g`-lil6Rvc#+{1Qm4`;}EP2NxYOhw0p!=o0c9x zy?+UjpgdzLSbr6I%iFV}GgBPgJKjqC;H06Qaq7OznaWCPi=SmX#p29t0mQ~EWMH;M z!jkjD;oEkH*c+k{8oDe?<~xejNT+-c2W}W+7O=YP%P7Kzyep%lUe(RWnlA0}5@~AH zL4QWgkaK%cD!lY#-@?&*g{C30m9l?Kr(OBX`UR@+O@A=MG$j|rf-o!z*v}K*4<@Dz zqVzVwoz5EbW4=p@wY_5P_%dA+fbX*Dl(#`|?-45nB}8!xK!^RZYkFi%S?)%J@M>9M)1c+Zsx= zvqhFqiIG#3h$NFSm5RVpnBXu|i&;bbZZv4DwfjITwcgsMpwX_o@FHoJPetZwHG8_e zY-Z^rBqcFlK(=CHjza=NIayRbmJZu>YqMo(<$qlL@!i|C!-E}3YQ<(vt&fTyR|!~U zz(vvREU)GhG${46C0EP|AiN2HGQFkvU~9wO!<1sObpzk@Yfp2r(}oG!*fB*}W!ijP zm?m)B`sFZ4;s4v$8~>%mEuMnJQWUxt*%RJ80cpU-S%n4Ze690twBej*-~trLGHw$= zrhk{7LPiT)0b8r4HE$aE-fdqCj~Azg)5BowBUrb6!)raiN142Tm>)3I_HbLxKVZlo zHSEUFYb){I!8aH{5^;2=Cug?JO|&nB!8X$(ZeF%2*VpimxAC`LOfYg6{1(pBm``p zCwClK>JH^98Xfw&Ayb4XBzj{lR)ZRpK8nL6=QDeW9jfml(`kq)Jee=v zn$*{lYZEo6uz8|16czg%2XD$7wA#p|Q@KN@X_E8GEyd_Y3ryj8xVy7|_%*xa<2La! zw8JXNdBM@;oymjadtV$kImVvD@PEq{SoUD5aVkY6H7M%S1wdS*oBYU&RW#34Z@8sX} zO@}YH+;a`gm^fy&zRHjM@_!7ryZEZ+`}1aV5s%c;bsg9}ag4fnJN;FkaepT~bxzJu zr8cj3eJwvC0~%7Dy#>PY)`i8{!}+A3tqQlGIA`?B*v#R*WL!GOP2{Q{wRPOy_S3m^ z&VcM3sNntW`wmhtz)?mi12BbX-{C;-4aL|~x}g}qn6u!AijwQyKYt5QR`n@D9uC0b z;f*`$6ojotoqs%Z)VXwdqpaQX#tCbB$Z%EuECg!!0H}As9z9qDP{xT?f}X7Ngp)dA zi=`dqmO_#%9}Qzo52S>s2bApiJiAy(y2j4!2r}$OA@c8=4-^Yf^0_geFlt!Gr)x&s z^08ZR;H^Fivtq)FA%A>PFZ!e3C)5OIj^1)M^XdgAW;c=G{g*+jLveL~%4zz;X^TNB zPHv`=!DbVI3b@l%3>G@BxhlPgN{0<>mMMopl+$gpbl%<6EE5~>{6k8ly}m(V#EDV)_=7nxU?`vSEsy-$Yh3} z4@u(F>5B}K&v10T+!JmOde1goH1OPvAv-A1#3A)WwTX$w^J^@i-(!4c?EsHGrs0m- z&bthb1xi7p zOXuu8Sv*y@3~?;o;=H46^D8SW>M~X>&~+|gsJ^+rj(?PI%1N2W=1y4W%7Wyqj$G*Y zQ3+A|^3yw+P{||}#g?=UdQWQRP@801YQ;r=#<`;hbZ>Dv?f zrCSt*Bn+2oDTgyml6kXGnN+e0b0^Hjzq9NL3?Ta%*f5-8bf{0Kb=F2Cnv{k9$v6A8 zKH0XV!GD;UAGIz%43t_XaU8vwq7%G71nq2fS)1;3DE(6g2~r@<$dFbWs!5B5f~X{H z5M}4Bvx^c`klxcC&O=oXL*gm2xg;&HZlD7_38js$TcpqhTGE-6Y&owd4^XYGB{C(D z7?Ib->+$Y(W7=8JNeBD9d1iO;@=!HK4_F{o{(n;LoPQBED9J}BVYPaU640q%yB13q zno;LGU(}Zd30*rLpAow*3QSuxOA3`(buycJEfq|f((+||K!O7c_v#?h@Dw#Mr)>3!_cvJz}04b=zsL8 zqL5WKni>bT=>;8=x6_t?z5C7fhqoA>d9Z)9cf5c2kHENB4K!)c9%7^;4H)0dDi?{j zRkz>1!&+FZ-{w6pB*)aE%H4GxbD|Xf({wOrZZbb==G_oyJ-aJ78Se^D}Ss)b7w;W(Dgsrx1bsn@k44pV-WC>*z(biNI zA0{{KCpe(r&YCO+lS3 z(eKxbY;r1C_c=mvVo4A-?x)ZfzjLLnGs3ObN{4G348!$-S9Ad<=->RT1{{!|`vTItp{6)=Fp9!oDU^PG>~BX#aT#Y|yd3C;z$LmcXl3OvR1CV;>wmKU$}V*PYJd@N zy@fC>flS9OwLr_IKMMbQ$%bs+!>L07c^{Lg8EecH9n9ld`TD?+sv9X_A}=VJN?vyw4 z#(UkJR@xy&jbWhrWu?l0NYaSM>Ye900ADGa4k%Vf&Fd)DwnNLqGGQVSF3v6lm&0q0 zoN{hUcMCph{{keH@<7xqq>t5|O^?I9bWxh-(2E zoe3S`v~5ppOU^9w^=z%_XhE`!LbCnP^rAM=_!!8|;R-QKft~s=GXM-!HY0(eIgEd| zl;SO0GQ#tuK@k@X)^(t0`wV(E&(sCXIwwKT z$SebT$k~oRJ&2m?cXrM-P7MCg4_53I1|%l(!DV%JPk83wX^jmE7p?P^s`Tc z=(A7HvVTU`fu3x?`x>7oS4nXD#b@KsJ{<;oMU&?EW%u1@pB@i`v}rd&0o1FPP#oH# zcsfjfs)ihgbVv?4#SO?{s^4Oxe1lt+r2SzsRa_^3)XY;46{i9%=`=vEVLj+Ni`e%0Z7&C)ypoV&p!QQ zuuQB})RaLUIam}NrJQ7WQk9qn5vMJt=;YkA`JE!&%J|?P$8YxEkKgPc9gh#*ZXbWM zfB0@iGBu>dbyk^IgD4KuuSPupx3dJp@V!<_09WXCzT)|keF<0;(fBcb; zvVSCJ7#fWe(UbyT1@DiM#fV`M<~Gimp#k7sGC{clW!`=E#T6DgIM^YMjvqq8V|GQD z0`VH=ASSUr&y#Bq-!!#96q)3mgP!nm!J+b1ny0)2%EODvAuu&fI$)olNG#Qy+;whA zLuj6OVnT5E-lQO04~-1Od`^^tl>z@$fPd9<&~)k`LKav#KoCd`3{S5wS9m@%vV;m{ z^cCiVWfKSiY7#U9<06WOU>olsgA3)8~z4VlIg7;*gTWf2kw)g6b&zzC%Fc9-caCESJ$XxK1%LOY>>wmSU z^$I4NQbVbRjQb+@oLjvCX|Z4mVC52xn#4 zz>=QKaV*aA6!eK7C_8WaJVO_vlzSKFs@VWlc~ zv-j7RFFyVJ(=T3q_UY%FLHOz=ya5LLua_@hz54v+#;ec1c>U?;pTGWM1K-jL7utra z;$4hN7qSWP5l;JAa$9G=7`G#>2IsiKHJ24D5hZ5YET%~{l@VGK`mQO+6@Q+;s4>B$ zRxm71dBvQPWG2^>PB?~VW0j#tOcs2kWs$?7<~uP=td>6GmL_)>uv{38wX7wt48Zxg zN;_H4PNuvG1y3n4dCNyLDvOn33awY{XW2Y6cMVglJBu%Xtn($f1EFo)g)HN;)}+$! zIwzLx%_dbB#+hvlT~>}K?|;#I&go#m>8+>)gO={CXf!d5P2&Jx*e9ekTq?(3$FRE8 zn9!oH2@yhVp20QxFEr(qr_Ph{@%G`jyT{|>eOjQk{~-jO4@6T)YMuCZ!&^`=HccH! zo`dS4pOZ#2eDb?tpt!b)v+`SNnRLKo*H95sS;owZG@-`4tIeyXC`cZ>dGh_y&bzP2 z(7W;Rx5s0=f+p0-)Ox{4HZg}P4@OTVgtM!U`kFg^^8W!)O9KQH000080A{lMSjg~e zPpBCH0K;4W03MeCY6usXo`D+*w_$+{N)~@u+Q_3DjsK08ge8`ZA2FDx-22eBVhu{) zFWJ`MilC~IFB^n@u0UwF#GOjZF=o?FaZ}dwX$4eyA~H*P?XG3*R7!F4_a$gNJTXEQ zu8I$l6iBNSb~EXwmhiGNp^FdFxz}EbU-PrAx8;RoP+j_$b3|m1QrIQVkF(t$@DP92 zvzC1FII|i_<1|g!SX^e(y`DK5(%~d+j(YxS=BCB*Pc`LqccKMuV4>sB>mNN7TKxRv z8_+^cS98fgnYj9&c0z&5@1f4`_g)m2V&S@)M;x~!(%9NCWL6#1^YGQac+OE>$Z;t6{%ICQ+HP~Rq^3K40|-4wMh(IzLqe=|NYV@3?=)yz+j; zk5hn;jepj(`5O*QeoNd>Dzy#cYXsDrqz`&Y)2&dlPHv z$*eA5(&@2$?&y@G+xW&uMoeB9o?ms?_WK0Jb~UuleZ52jLgTkns)?P&Y$SFMoz33{ z|5Az1@MB61^Uyyv^j#)D{H1xycus+*f*yY7gDpZH1zBTf@wt#N%Qk1PvsDx3v^&4}o zr)kB{j`Rb07H0_usy=^u=~#75*P*Mv#V9E}0wPOTN+8#v>7u$@6{dI1qcs!Fp}2^m z>e!MWpCp%~EY)Z{l~z{yfoK~Sl3XOH&2d@DJ8KF;Rk`cm*xSS(;^aD3U-_o556O=l z5@6HllNe#mwDW&ZO9KQH000080A{lMSe2A+U?#Kx003qI02`OGeGVA6IhzbNv=L^q z{8%kS3)=N90RRA!0stSEA)5>shnPse`sZ8Y%Xwl?R{%^+c=WwcmE1@dvZx75@S2*$NG$Wuah|4-p%X8 zN%zb+9)^-2o3TXlkdzf=cmMm=0{{UMq-3Z2-oEE9Pj@U)K%r0o3iU!^*Bjj*d6R6K zq?d=@vYL&4#5X&;JG52Em5XfLgbWW200_;y(cu@Gs6 zu+D_=UYtHXd3AOYR-Y>`PN&|}*RS5aJ$>^2-Rrk!_!7$P%!+K`MbT_oEejDvUb4uu zq5}8{bVWq-f20ycJTG^4}T2RNnwMQ6&q(K*b_X<_g|Ilx2J=BK#$q zC3B(bT*c+pJQ=I6pUNy%pXb@-B@k16&2&3?RwkeGxVlm=3JtEjT2{$ie_QK1m009v zyI&Ii%D9FK&nUVPMF|v+qM`RJDJ#J3rcqvG0I*sQy-QI=^fJQU4q>F? zX(T>l%jJ*;WttRDP*^jc$JHz=76Xa592t>y-&+!jz8*mCq+#l@h5UNndZR#zhRQX!^d3M*lT zvo@}(B7ngkdVapn*LvP0=`8aHhgdr=;>$&R=%pEu6@I5~p1yn&y?J}`{Pb_oj-TiL zj+g^PfE23khAr~LzgONXeeWK1%%t`C;lahu zo4-S0qUYN89vC%u0D1n&=!I2a=lJc@U*LChz6FD@5b$Lp0)OPg2uHs0CX{cW>XXyn z{-3>u65MGliaZ$pTEjcZiOG*(h^f;Ie-caS_H>aUQGXDYIk08KY;eAJAqR>oPsPlO zV6`SUB8taJAYtN@W0L^+96<~X0$k`KJZn0-@@5@9J9+Z{XWxLS;f8UZi*y?JQ|PP# z&YU?sb?nMF36rlqpk^rm1O@-<1&@Z_kB#Q>S>=n~QTXlj-7nFbzrQ?w@#6JUe*-}S zK7vR}TJ~-DbsO)BrRb>cu4Yqb;ci@a?vbN&_$|2evNFxVO4*PHaG z57P(u=Kf$OpjTt#)!<*bSib)0!|DON4nBnZVQ^nU7$^9X{1O-Vg$rSc58>)+{+AtS z9QU&D^ygQv-<~`@K0ERL6TicAzVi6A|hp4iMq&(cPPEh!;xn#~u#0)ke4;Cm%I-y;gd1H;HQP+W6c=ES*pab9} zWOjdYrjVLvAc{@bAp0bf041QIheDr3Z`Zr3s=PdW_;8v{K$M{Ie+skW^5LV0&_WRx z>xXrFSJh&^%U?mZ8pWeBTNV>BLJ@le5}ue;^L1ahv7cQCIXYbCVgiF>Q#-`BwT1y5 zoRG5Af`!i_n=a>~4TIk^6oePxKXoH9S?-;e;^xw0-_DfjUDD~Qbkcviuugo%J?;eUZDO4aw-rCaQct`vVVvE z$=4cQJe?9KrVP^vv=z`mfo5fI1Lvv+cE7IEZZcfvs1ybU+!}#GEI`(4;9#^{SHkVD zg+!QVd7!K5TDWwshu#gYo*II^iNPei#@{ub?L7{+BmMLFfAvL;_AE)KYD^jT*>XOw z=hzXl45J&+m@3fIW+BSuypkQQNnp)-tDI&`#$4hbYgB1PIZ^Gwub$yu7#eFp)x79I z*=zu1|9-+3aRO@bDXmJ<-9WNP<1wt&DkIw0qe^4VT7mAD8)ci>)#Fu!-+RWq0H5yH@QL2FAe#emSL|0jM z4Fweg8EBUB8!nfcp zcrsh_H)H0iUf^>Ipptlwb0!J1c!hJ`9NH-$+wEEt|6i}UN$jBhI zRFkVfRc@^thmM7k9PYs3Ws$*FlT2XIIrJa1nB7Hg72EaR3Ys~UAp0u{5OYs; zALPlHjAU-`HI=x9o7BIkb@&LC_fZ#W#;oDWf8*4F-j=j>(;FW-S?Z<6(*`#!X;X01 z9v>VpLnG@QD;gQL^K|IN%;cN))xbYZX`On6958p^S-a!$6*h<7y@ED{KI|kh-1xQE z77-~Pm-si3zjTbIIYiFUAq;-kncF!7zk0qYuEphYU_hbX+&a)@UM2x2+f-l%$2H=@ zf387Q{b0$=EsM~-a)g%Lk@^qqe<`?D13lA+3iwbHXUGI``U%_*&|&CtS34$< z_zc{qY)&6s-o}Ej$)~)M~O{_yb%g zUuF(*3e7bv6KfKEnc19g*IV5{`*x{wpQP=P^jn!+u%$)p5+gH&?rNC>hUZCI1<8QM zo~Fm@=Av0q%7nV^U!}l)*ld8*f9~^67i0TX13;G#@}g0$%@gm?xD9ku_3cn|tJ(w~ z=sQC#16yjZsj67J7n&MsQ$o^nWQ~XBac=+wUU{v*o^@`V_0$5trCJHLGRzinl%3|f1gTOT0clp zv#VK_HGhrgOCjwEO3I}52fK`z#7ihr``OEF1W$jFlqCgw%t7-Os5whx1nzx2nlL>E ztv#Y?rExF})`-8DeD(hXYrNI`^l5oDndR*!CQZgpHr$$?GKvJ{=4L!eU`|R&Xp*XB z$-##04Ey!FZe_RJIxPF&f2KX8VbEEZVE*dup;-Vv{RDL!JFmcK7mw<1K^Zgy;H;b!hXC?{ zcW{ADNW>57yLX?ue}$siMt3`nioT1s4JYaYuE3OTF*bZ>?H_1qHP-vEHoZFB5w%&? zX%sZAw#1F}3tg-w;b0Nkn*bS#jFNGE1|s!%%5DC*;z&2)*zCIuL`HlULH6SLWmW*I zT}Y$PHY%b6CCz@hBg_zi$6t!F3LM2a3^H66^D>@^X#D+Sf05!o7I29{SYQ}m4m0`4 z2RIZB82E8HNs_iwtnnGPSo#V>?xF(f7s^}5UrO~sM(Ux)hM^P2OJmSw9dwohNdx(H zfZTTxUkeH(&DTTRm=;U4e@>fKRhUQMXb5&S6nXqXWB1b`gMo<^HbUKUXthPQLOw3r zbcFg)y^#N8f2#ogAGnECN^0T0K7?M6NoG;VBO;}fKIb(@MOlff8$uL*G^{7x^WI)L zwtq6thhB~yweV?(Mo2*tr7(=47_->m;CYgN%tDl&8oM)3V3z%kWc-|xFNkH>8$Ui} z^2HQ*pd`Kt@}^&`F)IK`Zfr;p269;N$(!<1O<4c!f63F=Z=V@UBF54xxFfKt8KS4E zW;o`tTh?mP>jY4X4fHLVTOG&;#$+PfQ}hMaaMMZNHHo?;z7;GE8Ezc@OqdlWoE5~SZEfO@`2f(7z-yX!``F;X|a zETv=4fAaS=cNd5Gl`F8pl|6|lspVpl%o5<@vH`TxE=!gH>mRY%n!Xr>J4GpQk)-I> zL)nl$1vC0g4Ru;?5te(yqhIRa2qJAVz7}i6FFABOFanLR4-)gh$DjnLfIJW*Nb%!f zFfg69==GHlkLp4VH+dN$AY807baXbP1Tl+Ye`koa7m)*7FPw#>H}Uw%X~*xcbbRg7pZ@O@jUQT;qt2S_?kR@SgF}wkaTUa#M0k>M$f$v5Q%T#Xq zA&d&}!mLr-ljR)YJ%J{|IS;Ub`5iTO60Hr59lko1^{q5}H`n8R&e+DDCk1itYB_^# zfAlIrvvH||HGoHqWGq6x8Y#{OCNvZXd_oeY{}n0lC4MX?ApcZlGhP@&Z=jY0-ND%K zR7;XJVr`{qo{3GAZosW=??9EHApx0a7g<)}c2$(anj=B&RXo4$s;BBovtQB*kFik2 z{aBs>vG*`;dX^4)>~7|M35`-~N27rZcE@S7Z^1Q{3*1>Pnzu5cLiymkcwimR#GkY~N*Rpt%>S;Lzi*Z4&H)nn#;+f0gCl zT-<;pkfn2&hGl#sYE-ao*cC-PxMG*%)2|bdh$@H~@1M}65b z-=?X#NT(gfcC-+r<_xacY|lJ4DC}7MU2RcKpbsK_p0xe(>T%o7{*FfM73OjcPd|HZz=C-Y(g_B)2xox|in?Dw&^1vj56|Ki2 z^p49CrX(Hg91Cv-GaZ!5P=tssN|?Bf!;%+iw!FNm*DsUs$S9?CCtIQHe;hBQb*gsT zKpyVM9E-+T<@-fei+Ew1d92}jNqc^N1 z9Lfq(w5|(ZSOeO0#%nJ=e+s7!LxI(QeH`aqe2Hz1YRR(+(;#`Ditcpt)VrT^^HUog z+F==>jpg9B3cp0+R$4qhyIx7Be_;a>cv2dc(|D~a(;^RHsaEoAC;cWBP>eo-RZ|(D zu7$`;f~5e=iA zic9u_0LQ>)O%5*mQDvZ`T$xcZyj4xz1M)u#pdQHOZ z5qG66T0z(6F={wGe@LP6`x^oZhyp2bV1+e1N^gYR^9lzU^j8A0{ zw5^AOE<0osTNGf|byPME+7}Ri=)Afr`c=NkIyj9CTwSKErkz@qt1U$+mPI%r*V0@B z02;>&Oe@SYX}lp*4GFWcHgeIthNVYA*0@U|>|C9_=2+{*e{eIQz{jELc$(YU+^+Wy zNQZ&kF=jU)0>W}k{R-j3kn?P4E})fw&QYn!vM2zp`C5LTX2>3RHV_-M*u!ryb>?Np zvZ`9I2@39UsaQd?ZKJwKkRl;wZm6lZYZOoivM!u=x00zYhPvz0Yy|v@tq1JHq@U|e zOiOqse=wA6k?~d{wZ?h8u1HiRFNc^$35Wd9c|a3ySy#hT^?0a}ApvF3E=?yH$>WHw zKMeM%0n*W!(Rbe+41f;xw9<}BBYr`TkQY3a>0J)OA07hQ6``j z8_MsstVqiZe&%(|R#kp!NDd0MMmhBwxCfv7sYhwz8rMekzA50}B9s^&+5GRb`TnWQ z!a7WAQ?yrC$fThcSQ7q5fGxd3`iM8y(=Nlhy!p5x*6EMzKQ&-JdVoc=DXj~N^!P%9966fpUXgs3| zJa@Z&5IRbtyBI#Gzd_Na2IuI|d49oVf9>wlB+qA`+smC)-bh*e*(~+ zWlKC6or+7?)@}sIx-YUDF<*aUgUWylN>k$b1;y9HERyyL1d4i9gB+BBf(TB8gAxx_ z^q+K?n93MFtE{-@MWk}Ag^TMcer`BpfC!@UD%hz7+N)iL|6L@Kbh~1rf@mHu(+LbF zNviTxFl$wfdQzx*A&jom2M3QH;@+#aaggoRR-X$}c3s z!lDHxfgdTXpu@f`H7I~jwcIB>)r0voN;TsHS=Gop*W;yFMs-W_le27hOk)QW9q-mW zPSZe~PgU0@1P!VO=}zN6KFAo4G*i>y-29JRSF7`b3wAJ7MNpGFq8KLJfAqLWH$Gi~ ziWEk*NJFdUR4~`sK$TIzrEUiR_qqfLkKGw#j@Yr<31RrUO zA8&NWM#DNvtsR>-^z7(4NAG2A7%S>wKU!VGC4qe1fZ-ztO%GYum}#Ad0-w|no2lUf zwUrL_M%?8Q!^aK=Q{WHTe~gBvh&?91iB69S5~v|H zg9W-N=xkk6s+&*|g|HQvj1sqW0}QVSv{oH-<1Y8ors~InBLTj!vuFmL0T=y@6=WI_ zr|j~!0s+Fl)ljozboe4OT)hYzhsLFDjIAxfM>LDzV zWHJ{IzkBrKqsNaAe-6SDlXmq0Jj=W=3_4JmFi!+|D|^c!YA1(zgY3n&4+`u zSXjU~NEG!u&RNuWd|CoyCW#TQX0TyLq!_TBaH6$ ze)`GV|9;^8FU*ek{P{*Ep&gT3%rGg+gU|{p0lV*LOH#3|e{86hWQQlG=86 zi9&;7$ztPF!c%b*8hcsP+*9-Qrue(g!^`G_@jXY1ouWb-S*a~IJ-1qk4)g7i+W@?I zzBJb*^rOF$e>|$Br^jZ};7Z2r09m59%xO4)KqDp)8>(k#GL*iaz<=UGM@(;N6ZeQXluPpnjT?6nab~F@IiO#KGP+n& zL%+;`4{nn#W4zSiyARS+<=)K&|n6`uKO#Bd$Qu}7` z-=L2cvP)cjWmHwq_cx7n-IA0>y1P@lm68tWZt&7br(C*Q8sU;s(hbs5f=H)?ln-Cw z_u~J&xNFTiv*)w(oH=*ToIN!&vZ-(v-Ml@bX>`o2UuEA-hYNoDY1S)zip~4mRQA1S zq{bJs;W>A^n+|B==Q`)u+O!CTr^xf~w8`Kpk&xCEp$p9Xb4DXGGs+b`W1R~%z;u<~ zw7zwZ#2Y4AjqV}V=P%%@u~4N`0-~H_Bm%=QMnws9=6FI=M^@I2!E`bXh+U$|mQeG) zZX~xrl77s@Gk1LaoG{fuO;?T6;xMCp#W6Ty08JCc;rT#dD}a{PcaF)}6tqs>%9WOu zPrCA&)-Yg_Bk*{k2}dteYIFI0E^n-cA?J~#lhB8een@KAS>7KVdMH;wTFQb!?&PgY zn%&~r5G2$l z5zb#cQyO=a{Fn{b7l5{8*-a-?t?U(6@W;*UCimqn{XEd$#!?OlM?#h%ljZ2q51J~8 zJ$w=K-C@vi2i2i#ejDf@+SlFhAEu#L;G0@Nx(nzR8Br2Rm6nifT}Gmv6NGB@A2Cog z`7BId90?BJAMBqqji5 zD8)uv=dm0LBX$b}N+cc-FeXb|9>Gxe|Oet=4 z5r3V=0&pmg4u7!WzPEBzBL%$c^k(Z7C*YGRxpI{Su+K#kSnaY6{JkhfTxYys^LhD6`*3v~n%8 z|5B3bvT;y=CI~~~#7{QTz^mqi$!3ivT>W0_?w(pec~BFt z^}QhS*-^hC zEz_)qsv14OKYKGhz9PXVb56SU8vlIdNx+j`?G|o2NV)Swatm*Q0&LKO>M;QC~m1ymu-V;*e6(V|LIzBIOYpg+KJ>}KyL z_nn^r<<)HJh$>=t(kSFpUR=awUWw^Pi#i-ft(^AsZuH;sZN8@!eW`lt=;GE0`ftwp zJsjNA`euciKjx+&6%3vjPu6w$gJ`$-EZ`_~GCg_+waEFk%)lSTRjSQ;)6-9??kCyAZ;>oKB&487^tKTo7> z%I1|vm3|?N=bbK?qcrS%6J;~Tbedw_-Uz)Pm?=agzI!PA3(;+B%%t}b|M?SoIB7Rx zKnHXGW8{n7G(lJM;pFw&Z|tVwZAIqI?Y+rOseLF~k>w?xpi0w?7&_)(6m(4ASc1cq zKQGqb7zmsmp4ZP^iwm&?g;c)#Ym`&fI$E1s8Hcib!au<)7ucd**Gh`G*eeX?BX?C6NV&ivywr6vNlSvF7%ou?_1Cvt)55%P*y!KgI92 zQna^>)_xNEJ@E0Y2`E42cf!q76lE7_xEjQQ*>o^$FiS!>(nkMcgwxnmQ?hDAt?@*M zqnD6|$PrjAHME>ZoMa`y&CXO1J<{5>01-$VMj0dop92}f3u}#{8nVP>wiJn>C%Z5Q zhL&cqg6nspl3HTef}bwO8}kAb7%yr?{-n^o1|WZ=-c6xwH2Swvs)y26V%|u?HxnEc zPf}_ymV`RV)ZAQQuIX%%T$k5Z$8fI+ILNM7fp3tHKmV2eJ~Gl)R4KBg)R-G`{x@yj zqJ5SI5S6_lCkAQFF^E+Vs-^@DhjF#7tZ07uLK%~e_U5@+h30%=GEEraT8%zeuGc{Y zbJB@lr3a8YLr&!T1Y-{V+Vo!LS5hf)vG-c_A6*+E-jUl3(K>0=gMAe4N+D*das`4% zq;G~IHwoVwy5s!rD#I4^AWZL*Zmf`Eu+loWEPKVjNn>q(XTA9%k-A45>XrYoNt;WI zJEDBZ?Pq4fo9tJCiP8@9KV5s~?70f`%s>8d(hsc6+$HlGlqwTnmR0U5$(kYNusQqM zlE?S={%rgA%-LKrmPX?i(gK#sg&gpmRc~W!HJeKhrkuIg>v9t*{9iQUjQZ#)pLfQQ ztHV1P7jnO(Q!izwKxOFLjG>>h?`@&7h|wIH@)2EmsvLA>n;Ailv{PiGp;Rk!?eg=dkKA`kavD{vXU!> zc09xVC0zG!mL`?@*_26$fztBXs`^BeoNkcq<;UQ+-alvhKQ(Ru>wwAq5x_-%Vw3fFUh7z7o2nzA6(D+|%-|5QCZFu8SX{6m)_#MgH8ozP;etUjq_9YTK zz-W9=i`mp)`Yt*2AF|LHYFC}3)RmrzRG!VHJu;&bk&WpxiB?SbxX+&PL#?IeqE^+q zzy@sh-Ru|K0EU&yt%A z`lV#YogebLo$q999Y$HiwOxN!iVdq_%2bA(C%&}21ah*qwpBt)Q@ZpK%mwEM2t6I- z(IYI70c8XdXSB@ebn8N|Z>l_nvrG%&-}MjnmhwDPQTWm-EjolWUU3%+T6w-rX|u|O zDTU^#*bw``(mru;Q@on#Bf~7oUPxAnR1_rfE>aj`>P2^r^=py7pFN4baxxz$sNNusjq! zT%3)P4XgSwX?}{hgSKd>Ubgda<6P$*dALTl^U_RATAXPm^e#XChcW{ZqL< zz;c_Zt$FO%N&`4PU;4{P%Lq0$OUn44&ra5OJ50uc67|68pRBzNB(o6psV+!OlJ=Vt z!S06;b2Sr5=@3CBnrtnUnD$6=6Rs->~+N{+?N80L2eWa;2NS+%$<}50M!2NdG$B zq(i&q--UJRHsG6$C3aQg!~P)%Pjkb_>kPaNL25+eAcl+8SFp*2%1RreyOKAlk*C)O$lk7Q7DmaSb>GAbV-svqw#J7KLH!deH zW+1 ztn(xoe_@2RM$_eCM|q0fbRGBEZ_b>CA?w;GVvq0_f7Y#9f`xg;!ki7&bD0TYtvVN|hu|Nzw(i1%-j7^NG;;gWA!{0?o{?FUUuQ zf~R4Q`FxJ50dlryiWuJ?PK!2b?kQ#a;U5)HM%UgQH2)h)X79G{+ zqUq>kvpM5k>E@gBY8eB~cv>#DtU!_fzmtJ)?eX*x|7~W4={w@N{oBi`AZTyP%7TCZ z>iTalE27OaBKVmjo;1rNnNSuU-{bCAk1RZm$FcB{Y&=io$9}ndyx))gVg-0pSpQBP zE+)M4J{CYgIOafv1!m!aV0xh>Sa6AIyla6c(j7dVr|}v4cJ zo2L=@m`MMcjnVkqPwe+&@DtD;`va5lvq}D`fev4$XM5Pad;IXDgmDbw6T=m1@TH!H zB-G-Qqy9VNxb5g|Co2vDf(tEd9hZ;@KGBG;g7dFuXP&FL3k3lo4D(+P?4bt>uJ9Fq z7Uhu_%^EgF#?Cc?g{G; z0f8dbKeg!s#O=i@n?HsN zuL-jR8&zoN`Lx|%#OrA61t0{4o6F8Xk4E;9TfJVnR8426&~r!_dw6)PaJ5c=gjxw{ z^&>;Lu44BXOd@oSKW}n;nob2*wEU>}(c5CakocZ-YZB?pihK>mO$4}TU}_SygyliU z3{@~g^7X%d-Gj29?Ar3@@M?7OW}2GEMBy@$`4f#pZtj}*>qtf>-T=ompj|@cS;(8r z-nK|-4YY#Ex`A0{Y?!+?SQtA^=a;T}*H`P;GQfC^SlL~}#LCxHZ+CLM3AF>a20vVX}mu31=3!XnJgpKuzxryTth;U83k_zsmnjy$U1-38q&+Zq|I*GZE)wKNlu z9`|Gi!i3ozdZ)KAjJI*J132;9ftHFjKW2nleubM5R|cj9?3ru6wJ1wuMA%yxm(hd1 zjeh?DG)hf{dZY>wAtmb&6S_Xx?5Dos6TZ9eiL+Y2>7j^KxzuF_%?Pl22W5T<$b)}I zU@IAH^KUz)i?**l;;&I-M*8D7iNNgQn2(Ogj?3XGolt~R$bKo7=9`0Xx9z}999rLg ze14+)E&+4%)gSaI2HoTzo`=5lE2(5q(wY)>(*$XY!Lj_AFVef-zx_W9=^ZDQXzYFV z*P4vf4n-J?N0Qs$iabO4or02)qKz`btgGducK8`!Y;J64+Fr$^g=$LEUPeHXs+64k{GDBF{x4_acZE^x%t;lDT?)AK&ETM9FGT4cXh)|# zTl7}Ymj;swyv*s=_zkl!D5O+CZOApPY6^{Yj9E^?E5xXlDX;BcsmeyRm%2l=tH;3* zqzud#!`DEX1ssNrCRDsDMBIBauPQ6l7yb)@-;q@NOz2J0l} zaOz}=eKEJi^?|6015u)lz*Z`ppjKGVxsVL| zdM@!BR^~~y6Op)#duYoWq-#vF$;fB*YP%Mw`-N+oZWZpdUHm<$L5+cooX}%G=NMwW zNNvUxBun%Xm@E05z^HXzbUlgMc95Ly)K4FrD#~~A=mNNjU2LmI44s7uCxx_QCpx=p zs{#3$;F-R6g*@5piN%OyG5KBfEw5Uyc|!<_q1bc?B;C1X@3D!A!kI}nI-X+(`^^@;iU2$+*Anm@ka`dZK!83u0G#bauJIE zHjrbcQNZNHJsrvbYABsw-&v2)k~U;UG|~zpN5K_!d-r}x1ILj1AdkcR4vq9Zj-=rG z-sqY`Uh00{y&~0jDCpE^DB}ine3kx$A-=;aPSQxGq>66 z6jN4)`2@!qbzpGTh;JvzDIQsZT$(Z-me6B^I{g26R)^0GLa zG)T~%2_TI|5`~5*Jp6-UCAh^D5Gded!kw8dD*aAscn!b3;zgtxMe3fAXmVRg7k>)V z#A04=&=0nLg_n{8camxSVRP#1X4fS_SISz2H=8a2T&t2kT@BjMn!l3IuHX^70R9Uh zyo4J;Ra~E94O89nPUOTQ^>-uO?YgZay1}XAdms0T+xS8V*#SX>B?nMeIDuwNR2F+$ ze_hX9e<2OycBESZ5cNv?m3ut?Ftv^_bvbv5#@fq5!wy$&&j~}L_dF@)b{aRBKI8#A zbOiiX_;khED^1N%u7mF;yRq3?$tLcohX7_aE%}-_!3MnAjzX>LeBkrXryVjMb)+bR z_KSjW_{F1G&zM7ngQK;nt?qxiVU}UKIJFRBq@J~b2ZF zb~z^$E2N4iWd`fwRKce9O3Po6t%$!nLy&ry#qtrgl$K1zg4Zqi7;FrSmw=%rG32L5 zBU-Spph^t<@d$oKViW*Np9<3l=7B_>Z`c^-*phTOuDF6}KXpLpm>1>cr!Id7lYq<( z+2{c~P>gRUlSu}fs?xf%ZGM~??Wy*YJi;;-*@vPz2O_@r`$m@|i6#o>`VjO#baX@q zJ9P~7#?C;{hQ8Q=*M?9gia5{cd}OB{x1rtlv?3FQR!=L9_M6Si<&)OqgOi>0Jx@8u z1_;UT#;SmgowOgcqsQIEQlDx8n!|aK6~x`F#$E@%kZ7FEt-d zE#2?g?3hZa{fduW-^z5%IZ)I;6e8>0pWK~f;5o*1PjCF?-&L#Fa-Mvcz-X3Ksx|`_ zexM--K0DPFDbe}u?fFapil5(Sip{>R!GKRSgfj}w4ePsrO{7byn&9w-QlbpUnK{+E zJ9Lz;2LE;>!$duTmgh?G;$(Cu`Lzv7E~gcDk|n=I7JjxD@r~lMc(?+MCtSAoFq!Rz z+Q3BKhjULlyJy!auf04!{c3NO;eAKL_~HfY&;ACIeuL>ZRae;z3x!cvJRHdf8DR_p z=c4RMTw+KUB!6aN$dU+T@Rl79UWUFWAeDiZ*~Dv{|6VD5X3JMy5siUZ)*SBO^pZd& zkdN)HWn21}klyYNAC~GcN)BQ%%y`UtQE@eH19XNtNyVIjAN7{)O>eOxBw257KmCT6*p?#)EL=UWs&h#OlzIKRo80S1!k+u@ zsdN$#b=5=1?{0(@RRK$^+m6Uh^+{jq!Xxk1=?_Smsc7FCt7?tE*c5S((H=;Jo+{wk zZzpq4iE6<`@@Qx9ea9Dn(p+v(M~D%xvd{2i+EiDt(_vCaF}!V1Og&r1a>{Qdx^-;1 z+4fN3i*OqKj{9&#x>SC7#_*b)1leal$&8V#sEF3wlcwM*Un}vh7E{}FxzF<9-+$i> zu^o_G^pCqyPGpQz+bvln!#_nYL*2_)uCYlGV*OCChQY(E1tZvzprBbsTG41ChgdZ>0j`oK4|hdx=W&x0l&xBY zrpMW`L&vjKc6Qu8&Pt@&*9X7(Ha^c0JU3~YW4?wPTYv;ol}cB&Vh`XBhB`yOKPzy* z-GBL7?qcEw-7{c`Lf}f^%+?O8Ro(PqkDRlahf%=@Up;tQ7%ED#84`j)(3h(NR8M}9 zav=Uz1yPw;`$xL1bfmxu9l@DB&*`uzdviBA5+Y%%8&P~lxJA1`A{7a;)<*E?)amK-+~I#!rxVv9X>3Q ze`+(>Jr3l_n9*q@aY5k^i?p`{b1=4Mp^gtqdufRqFhDR?lS26*HM_LjGvVcE2It$u zUNjgMbbKBWXihchtSuA>c!xAY`6&6s!4Z(Wo>qTwQ-%lU&^cG;+VQehh+4&RLun4?f*0&?l z94VYHbk)_xApY?mmG+=ipWt@2-Yd!2Zb@Uv%_UshDei@mh`hZqIG+x$H)-@M>G@DK ze0&{7g{;>duCMR3W2+1^KNrer-Y6!D#ZMvADgY8=D!;Bc^px&yLK_L=b%~|<^l1dE z$=>xg?XHaeOm2beBZ=FM45KmwlNu^M#C)M(WpaOyPsA|c`6(B=R>#C%WEnV{``X`O zjIem>YMruTQ_tLeK}j{dDp%Gs9lPCE)#LAK8C1Ir@k@)oGyKy5s13Eo)%mh%M`_e# zjyOxuZ1K%IY|UMv9y~6b zaoYTBZ%@o8Er9}Fy9Em`7njp& zO72Q#wPdYFK@+p``#HW0Gblgnq8U3`*(`lmLTxV9>h>li?5>E)0tSEfif{)wk-oO8 zt;cf|+poVgfbv2>+u!oNU%#R#TR;@Kg86W49(L! z!>d*;VNHZzRu<>vMMhc~jZGMeE}jLcullIhrf|M#afgdFg$M#)X(#V{OU(lkBQz2s zKuK-6H#A%QBl+}`R@F3_oT8saHmEJ09oDwL&Nc7Dgf62O$Zgqrj1N_52BhMZK$IE3 zG)it1iv4BC(!k;bpcy70+<(zq<96BoT&vT}A;BSkpQ7&ch20f3IzjTw zG_rY`g=L4O5e&7?XZ|C^(&5mMS8SBxs^WhHA)9ytw8 zR3P%+>-O*Sg;##et+$ z^^VZ)ND3XiY&w_>gpFo{H}id?a3|GlYi^=T=Fs6QJ8UIZa$+H3WOuLV->mput#2M< zuWBxw9JZJZF^0uE89N_tUTv0Qy$Pf^Nb?|lh2k&yMw#(xe+S0u+W7(D( z{+UnK+5A4>6*sxIUUnK&<(zA2KoOU_=a*U`B3i!5Vrkdi+2Cc7(qaZuhG!OcX4>cj z&(PD559!{g|EzSI=zX~n*J=?05DJbr1w$1*Hx##K$(2$+=Je>Bj*|IN*B&2WLyg&@ z`^Zy^$oa9do03WSI|R4WGdo2`W@b_}mD)Jw)zkh24CUr0D<=gv$kfifi_jw4MN3UV z+X}GupeFbLNvIc$s|}x;BK|5`!84C8PA5{qgG;4z{nx0-x>9R*d@QGcOIFdTUK&c5 zwtcfGf|ZV~Iy7Mk-*I|r;W8FRX1ZdlVv#R~c*$%umrumj+IofN5Yg{N;DOK(s;1g~ zWmS4uQO2BlR8Qikt>SU8@2AJ1Oxx^cbHLcLwM&L0Fh7cQE=x!zDQWr>%i)903HoJ_ zn`GX^9`Xd}_3mFC9P;#{JVW&Km0hUj-j9IcWq)3idh(2}aj=j6tsm{iq%^b^as3w) zF~R#n%4Y6@>gVL*va`fBzT*MD(Y4SC|M0|nn7J~#42k}qGm#}~)Ki)6D=LZ9Ey%%{ ze&}HViJ3?*^LjVsGR!IS>yp=lnu+$-2s%65-t9PN>TT<0lF7mp+2ugF2ynCiMH7 zIPQtbfb+cbWF-o^6pQztpbV>xbC=Bcc8(rC{-$MM3}WiCy>s1kB1~D~p5pBHuiDdi z)1Tc9NO|R(`rlC{q;^M)RRLGc%PAx!at&j<`nP*aQG+)w7Mj0&C4XrkqS_WEl#FpM zOTV8f7@Q^bc662D+4os)jLRB z+LqFlPPdFBJZ=Y-&Dk!sy|8-~v@8z!#8U5`?nPDhqEGu)SHJr{UZ$vw&W9_T#|<4Q z=*pp9Pqs;cJZjx4zEpcm*NBk5$=r3|6C!iHG)UXTx7XSumu6W_K63a1&9OwVSV>HT z(n%LFVgLOg-K!R z6FuI@aQMRqwot)f=7^Qlud%E+yDkcjjZm&DpKtA?pE=7U=oZZ~a_0qJ%-Bf{#mM_B zl?xzF6ps4eA?0e$v0d_kpn5ksJtfh?&xQRaRY?`hXw zMd5qK6zq@J`dsQZ?LPJ~zH3(>7Pd8=PDfrJS)2X}O-x~J9x}mkiIoNOoa_0Heb3eu zHFxv1Z2#fPmN)vsduyLS&e$Ly2WfW)KDV;i0I#7K*gM(oK>WXn_6o2=_Z}JoLJ7>| z1qKmJd=MWKh8s=t4E|Gz;QT3dy`o7#OYoQ&Mza<51?kS7lMSO@DFx`aZi{ej)dV)Df41i!e*3zDf3kp66?`t2nZ|` z|E2nCKM+D-BuOOL@MJH-xo3|8I0h1OJPA+qMj!qlu3z9(f%!RG_m!+V zWKr=CM5j;k@}>}-Jh_x9m*{^hA)UoUJWoFVQ%CgSi8i#8h*tEGUDZ8NHPd6$gb`5m zgp0`vFnDU(Z~&Me{S8a!1Qh-ASr}+$zzGupA)XbMH-%3GH|7E)U_OGJivUdi7oK7O z7p%vt6AFO5Cso}w0OC&s&6-Nb+bW7`0MVG6}01aR#&;M$W= z0zZJRkB)`a`~*%v;)ibm+n)O6wt?kO7C%1&9zVsv&LvRii9qHLaOu%j;Ji01p%2dp z7zqsj83+O^FMY6;`VBA+<)0)w$A?Pm4@GrK5Md^IAn-?2(Aht34Ywe)r%*t921>zw z6d@M`!heJ{UL-X5`7kaI5ylY?0mEj%psPokNeGAx@DE!FXxTU}_bwLmiUdRv1eB^58WHwm( zaGwP*{F4@LAqZ-D_K%mIDpo=bIs(EqF)U#M42F?ckf6gAr9drDN?<60qMza*O%>Gg zxI`&U$*(+MJwyG^8DQAk zs)r{Z-Zbcci=#K+KzC2K!|fu7_9>cZH$aN$|Hcc*RcC*B7(asipGCp2nCb^-=I(+9 zp4@J?4`O=KGUFIT`E<4O1l0Rv@`&FcuO|Z3KOkSMf2eE9ZDEY44>3UW&vx)46tK|a zdMkgI&Op?I-k-4_HH)i#&ZJOoku#}2TS!H4mnLlPoveF(w;eH%Y&)5DG*a^w?f;w#_D<1r;gw5Tbc zHz7qiq4-RaX3s5(g_eo-o1@XfZ}j|N8cR=0v6K%hxMP#ydxfRBH-=Nwe3^&Hi;o@1FoV78*{R$y_qhO5Z$HWG?cwm zfbwjI^zCyH50%t!5_s)R%N?h}+(UEUaivauOO=%hpK2*s3pZ|m?~f^Z@`f><&Art; zoR!2(U;LKdO2Q8_7~y+oc#^)U`e; z&XH9E$81Yu11N5OLJS$0`zbBh4M%oqpZf3H>39k4W(Hf%)&Kj>svWfBvW$U%u*?86 z9)*}dPmc}0xUMS;vzeDY{EKve0-w*=>TaF4CJs_hk7f*|=d@ZzuK*3~=+14iwL;pu zEgN6>QJW7Q+O3wwU%Rm|C9J5@?P+Y$Nbd3{NLn*~ri$%%lU!pws2VNn%b_V^HrWNIgs|@FzG* zOSCL0bbU+7r!J;}(N^V|+{3WQ7(br!cMwx&2PR|_4Z-T%C(P&CT1Brc|1Sl>}zAUG2 zp$&%hE}Y&nqH7Rr@&=0?+oI{{YQta`_1cZ3D_Pi^pRHMzyXm_J`3X3`9l2D1x`aiF zr$%*>#9j(O%Aa!g1Bbc4zD%1B9MYfgG+oQ5>{Lc91{;ud35;^K49zbu-+eC$ILN=Z=%m8s=8*<(_HDM4HUZ{A^C|I`w4)dxuHm<8!a|XgiA( zMk-W9W&U?8IVc0D{TvDGRC6?d9a$#%YzRSg5&zikU?*XDdzYTVr(9M~5ZUdgVQ3;) zQUz8zB^*`bgMS=nD;-Ziy0$1?eHLG~FV~H!%o;NvRhMk>Nf9wuumj0ozN;3pN*#OG z9gQwE?jbCxq)S zYHTy=MupxJty(*Kq#iZCurE|c0(A>hn*{zHCg~G|Rx;f{J}#5)7x{@SisT>puz&B+ zK*|R5VuMx;92!^+NjhD&V*D045$VjF@ij8DjgEeUuzNAbCWN%iSYp!&Xra^EFJseW zDQ!fol!XCJYm0&G^Z{8Z5hEa--WNXX+KZV25I$alI(Gp5b^ohlW!vZVygrqY@ED-D ze?xbWkURe(Tz8K&YVIH<*rFKEG^OWVohe3WE~P3h%MT!IEmw<^_P$ui%sab2S&G?0 zD3^Vwqlijd|9U{Mi2Y+$E!2>PkZbjaDwpz9gC&~vC+Fm$rggz{=Bg9C>kM%_rsTiq zD}5NaL^gtZE{BAdvxs9oksl|B9E+xcw(P9G&sgufx(1@f$*)f8q*V|ZRIr2_Ec9hM z$GojoL3TL1!0`b)Xp=02&sjHU3x~ckkWUNj!~@JKuteagFr%`@p+L7M;)CCl^N8zp z0&;&OI`A^bq#1nTn8Gg$)mP5#F=)B^j5((g)dKlGe@W@Gah^Vsx;K|sjX$2;L`yzc zmP_)ij%8#(xk>8E2bo`I@0V}Qi+FA85?05~@}R|(m)0)5n@CC-z!7JhNc&0eA3j1g zzcYDHCge3U;S-^dR%Pe_c3;5Jop(or`_0sih{ATL*YOGkJ0Z((!UbXkGU8`a8&Zu~ zl-?;3sUTj0;Ezc6IGgYuZ-#!ksin~~@K#`+;C2(kVBzj;YNE@GZ2Tv@^A9RjX3xpx z-B=0Jdi<=CHi?EUnteUJX9x!r95+RSsLQ)lU+L*@fS@nsbHkt)t~8xhQF>yTHcf^v zn6eWbcHs^FkN$zc36Z+br&@c3`q& zG$X37|f_IRU8L@mMM=ks^EweP>PKYPB*Yx~0-kK?*^h7wXuX7M$E^~X;wjD;wXNSd|)D5_SaW4sOR-8)CC=wHb*(Du7-5W#n#3zr&*KPS^{M3vtMj_ zvJ_upP2XYTKyk79L;{kQB0p<(D!y|4l#<>GreHa(|ZaE8^v`gUC)Jog4)=E%Vtso#ia!l)Bj# z>*VAr`{a>T2jKug@(&`JxOqrKrgVH9Dv^|gYJdxbN$B&fCQrRqG`vBZXws_T0z$ln zkJA*w)h-4qCd$9}gUbF~Y>e_Ped+&-i%wrUy~yMI%FfpGg-_?=2~$HAuR7@lHc<7Z z-$M^pl8Xsuw#Pd}V$PCW296_qFu2vmm%`=wU}B5+6|t5L*;MXO6;U1JK}HeaX+d)N z;#ZhJ)giPpDtTN;eBcC1F5D2GgM`Iw+2DyJ#ndj+f{hnl+56kKB{c>^^vic#Cw{Hi z-H$d9)Mxkf#cYFiP#G2ft*4Mw~Vop3VCf#NEvJmbqeMF7> z$zR@k(f2s+wiMH|ey~>EJo_Pt|3@=uNq)p)D9jjogK`kA-ZM%0Zk{tpm)Zt_6L49A z&vj}DsTpMY;qYZF-)QU}murh!)AC#&!*{t`174OxfJw5i-IsJRrWR@U3tw~dt`}_F zFOB3=-1Trc#l0~?T;Fg(2}>b_=}s}{s{tGFY^^R^Fmu}i^2dAUYqa~nL^I3_FL$@U zcD-_iY6Z^uM;CidtDu^A=XqML+lzgM@S=1~s0jg^Bj~qGSq3v`)3Qeg*{teAJ+1h@ zcJ92Ze)w#%*4%M?Dq#7i={RkG6n79W0>a-o*LtkQ)|DJZj?Cs)cbw??z|9PGWp7G#%(~db*RD-?~GF}No0Z<1jVe& zz^=80a!W9Cp3N0$Cshu>Fa z6^Bc4+dG|Le2#g+)r_NP9n+L>D0A3g%YfHW+64nhZQ5Vm(+YMNXJV%7q85(_d^9}oN)X>StvzZ*NUGvP1=Fy)bQwygu%eZb z6SU$mjaDnm_SUW0p0zS_odLIs{%jZzMm3;OT@St!U*m#$`ims2ihhS0E(<>t8K!Xl z%n&)^7xyMvj^vDKv^P#Hl|nZ`Ed5CTXmHm5w^kvBDWo-72~IVNE%5M8Lp4uX;>V{} zUrb;}6Eq47+Uh1G|EQEoKajE=apKk-H7nDN7W_`xjP&I+6}?pokEVz)F*M!(g0Aa* z3hD@8Nx4tfw&i&jFWpyY7AFgzE~9MEj*~Ds-V`my`d?UdtQC<9r|3taEpC?Q&50;R zPKHjs_P5O8aPLNH_~qu`ffG9)$#>cDnIc>&3|58T9q>QU5+#CzZV8!De?zl)u_>VU zg2_?Q=)n@*0Rt7e@&I~W-l3iJtVE4t48*2<;cS_H$9xoPC6G4(HKqe!J)X_`B$G&5Dqx`|ZG*1^)i^@>c)4u(jOmoupXO3u zWh(I*sQQ!}%|OV-PYI+ik^OuDse=YB(e61Fn8`AiR2Ykn;Z}l?v&Hzi;xiWD+BcPY z66(Knd@^JE!LpOkyLF|(hkd+F`SZDmy`|10Dki#v#E1skDHRm0D7jj+f#jtcsY||P z%uSO5%c4~wTDW7In$6R5u`2D25Pl;4Y(?uB{7aOD=Nw~aKlTG1c@Xz$ElEs3WwUr6 z-39y*4)<8%jhyg|B(7i9kw^ze@{kf)7bbQDcZo#7g@HoQ`7Z^Y3Rxrd>mk@39jhOM zDmyw}H+6Ju2HZ5S84Vqce%_giUdW^5kM-_au&5m+VobJ1-@D=p8@hC>+(zc#Lrd)} zUI~&i2Y_FXIkR`P4G~>Xp-;?pb+)hNrh&a5ibCgE&A#le_XKcXj$BI$El~*l$~?Ty zyVREeKezRQ<|p@(_#pHX#~QS2etyU4yp1dTHO={&nuk{wsjlKf8ADULF#(Rl(GeKw=O1Jim_=i3C_qh5&##9#}ZG zJ$7ZuN4mOGa>buV^jAtRzuL(4T#vppAgp*DF8xYA>oI=z!fGz6-mUHNE^zm(+H`vzv*+XnE@eGGEC%>HdADDl;5P0v3p{as%Cxa_8M$ z1H5kK>gz`rKEw}sM?;CeAN?E3!($d_V^mwhqKBQO`CqH*qs&W6I>uac5yi^c(k;)F z;3~u@?$b3j8b5p$uIK@km<`P>q46J~M7)v?g|5lP%a;f!>VYnAJ$lv<8M3ZyIy{0E zuh;@|HGap@3XOaIQjIS+dFD6%wfq&rMjVdo(YzS^r4>aaCvbE4Xv&{F*yW`I4qIrU0m$wV(X_0{Yb` z9No%bKLTGx4mX?lgYfcXX^;w>)4rp9A{-VzrEeRi5>i5^f`uzU4Q+om2<$q@_FT zLND)IL+IIP*v~;#?xk6tjLa%rOlm|w;CG9%`+RsnPE8{=v_GLKTii*&Eq>^_%ihU;x;cnrPa zR^=+MZEel7szSp>(Is6EKM^EwXc1 zV*ZQakLJ;LMkRI)-st^?9KK`C%B^hX;vs9Pa`f8$eFAxrZ1q4EY2vc)w5gwA(Ftbj zzJMl~L6^1R5H&R_G8O>g_+ff4V;1A0(GFsH zxi%bC0C`3^?k1Pg)a^FTO0_{aAyn#G0m0Ws7e2@(LN+D|Bwo2d#_>B>-Vj&ZNhrO{ z>vljeTO?JTo4ENC!NbrUAr<|wqrSr)E3iTaO)_L0B0oznzf$CVoO$1oc9 z`qsvILoJ#BZAlukUeK?;O0Y{9$XTOCBRemEh$VX+F`EHs-bE^I0m$94YgXDw{zBY2zZYKPUF5y5-Hx>mxyHXM;rO}H-i{;qhLd-8teP@s&W`!@W zLUKE(K{is09*ufx-agM>r7~B6s!oz@*Ct5Y!O-=d3^#LIKV{+f{k`6xRyC)G3fiSR zB(U-%ve)R?yc!hOGm^`^zC?_Du$0A`v$`aIjyfuB32i{T>U1Oy~A`rnbqr6VpTJB`9e*z{I6RnloR3efw@%b z0fVwN?!?ZGr1B-sP=!q{cbx9eLMq5B-QT$dx+y=-rfXha#H#ABinmCdEhiw^e*&lP z7DOlz(bJeU=lj@|hdcQW(el`>ESctxqa0!R3T@tm;k86l_{j~=GQ`b*-P zaz}iw)85T_R)WsU1ceM}c)~si(`Axf%!HI>ygQ5q+#MitK(|w4aZL)Q9lqYMtBhMX zR*(b<#lGoLqdvo#sxH_lsO1iac$F{R#+qP}nNyj!< zY_nr^*s*Op>DW#>b~^rg@Ap0U_kUK`tX0pfxen%4RbxE&ur1oFF-0u>B)Z1{55<;zITFy*i zw&9XJx=?Fu+LD~f`x*?)kVQVk)}uFM4+{9@0>Lq`B`j1xeRRvN&Ne<%Y6j-g zRRv!4ms+Id1&ORZNE(f4tCL6x=m6Vk&Rf%5T4xFY4$BU8eyfF{I)-<92bW_nc?;qg z0;n0mkH*^}E|EoSG1rgRHQyG-KZ>>&qxi7tkj132LB0JaJBaQ@K{c|ppy4#sgi)&C zD@x>N18JV1b>{WJ1p>xwrERv_ki7$|8G?(~025Wc;uVdox~B6KR4Y?nxcctgm!zc8 zq2kT~v{Y5ybf3$*HrwiKc5!8zj&s7m8LC#StTdgAM+RV}gQn8s3deMz(;lw__6w_j zC}4b(gK&8NXfc)7mE-v7vj##_8S*PM>rv-Y^7v{VPE{Y+B}D^1J-JAfdY@xE<&*4N zuVV@aV)EDeE&JGZZpw}(m=rq8bVXQ{y^$T`yxO`^LNAv^cRJFH=P{pvV%bMo=VLec zH?&GHtV;wpJmlw$zyBaUCaCH-`!i{fk2Wh(8$;&RwDBc4B{p72vb5>?8#1kqQ~Ra&PY_Q&eKl)kGPBBo6)WjUZiR>g=Rhn8 z;>^0AFHP%VWvJU+d5aY{w;Zn+)A;~nZfGF`!BX$Fn}(+#yvpWc0{?OpZ`X*Q6uyzK5%NAWOl&7h;QMT0(6pWJK8zJI5}vLu2(94tN>1fDs!P*o zZZ*j9&WD5Jhb%q3$9jq@ffM~3)X%ma?q0h}dj~k@FbxXM4(+1G5lO4x2ES}-rtnwT zd%B5$yvO)|eBVQ13-g+5@l}$>L=y6~0^fC(#%E)zs8_4?=*BW=>-MtDel5|eAr3d5 zQQJc6x?9ShC}ThEEDeN4t0`y^OZ6E0a@vZerEh*ulnvo`&A^DY?O4k-?MW7(9Cr3& zqJ=)Lcn;7^EH;iAziOvvH?o0gqbYodQiRe6YBG2k>u4xsB0~}NMl$C=IfdMJAJo(7 zY&la}m&zoQLQFRyl-uEF91~o*Io|3-XJ#5K9VL;8Sy9rh7p}C0|%s-Y{oQH>7MUe!$ey&nzE> zwVrjRe6>3{vgw`MS~15YsG}%TjggtNj|m%3k@f>nk8L+sjN;?_v@Z^9du7bUncJgb z220A%p5rPgs|CBr%3DIF*ABEF&iYOl``=8Otfj-vfwx~< zIvV9W2Je>#OIyQynb_>12Kly@Eiw8+i15Vqz-2^y!nPMtM<-x4R3MQx#2%J1jlN>j z1I85eOI^gxtM&e#`5UM~B|MEpmPN8I8!iTeRDDM9zQm7#pk&KRKfH5nuacx9!y-md zJ)o#{8LdjXkAT3cd}TcqXjo-{W9nHtUMwJkj=PhOMceevpu+GyQGmZ z_O+_0>n=dDi1FS;ShD1aB?){FTt+77*7T=lfcXf%X9ls`ydS*m+)%3$la30YFwh*w zpWyMsgke+4x(-x&F&hKHJ%oQx05wGrLn%wNm9AZcB3aXNIKcT6Qaq9mWH^dFu@0=e z+nlhLTGF$!uVs$&F_-^b3jK!5A#o?^GVln{Mhw=lK5Lrxl61d>y&W(+H?0m|>hCCU zQ@`U^Vm4aik>~!gp`o=o$Y)r1#?|U%BA}N4*mY)J>Z{X(VWtGpar5^7MgDL}f^I$h z^#NeOwwj&eQ2wh%#$Dq;gZ)^c&(h}l)-r(mhJGeB}9*z7ASGzWvyQ+C2=w37N za|6ZAQ_vl3JYhq*3W&5x+Oi+6s2wxuK|DZ|OwSn4hd^mT1yYxma=p_Fs9L}nIASUv zQ-1xRgB9-DN6DRmtL}C}xA=(>Cd@2Ou6HhqTCa3l@|<{Ly#voAbKh|-F>EG7 z)&c5tq*Qg&j}nenvxs1_|`Ltem;0(H&Qy;u;E@?IDQQp$(? zsP(+LyzAp`2-I2U$tojN0ZS~Bty0i%5aU{sYv55<=b?FY($?V9>>n3@8T7bc`6t0+4RY`an`Xc?qhv6_8*xTV(PllUi>)&m4LJ&eX=3kPQ38zj zaU-CYq|yTu$zu@RV>p?tCi*Dj(;J)JE6Op!&8$r2$^jU&KpuMMDEXbvx z0sXj5)dTih8LXt(Gt^&kD8LLMGJIcg=uu(C3s~b_17AqN+9w1m(E_kC+WrpXgpV#s z?l_JdI=RJY!tSt0SgGEX)O3F>WW2xP5P*3&W#GNxP>>t>KhX=&q1S)g#+OrBt{(NJ z6c&@3g!l$85-`XxYbP9v&ef4&{)Qs}BtIYt7=X7mWZWw+px$^7jwc}?alys5@p1SQ z6c7{6iMh4g^2Sc?5p%Kf_S19zoHr{OK$L1a7TUw=IM-GP{j9GZ(hCSH=RBVGyitv|=*K_L_c133a`!|CZ?0*%#I~%aD4>Ha|JMOLRN;DJraE7hHj@DYfe3Y3Z66 zTcn!LEXnTi#RoC`qYGTWpM0Xrv;l55;z^exX?w9F9a%JP8my6`q9P{l#3UU|aH=WM zsl;oQO$LStJuj_V=;oviYZ&gBk}V4i7m8cO|I9{p08A$ZuH%B6o7n6KuVVpdZg^)-i;qssbl%$-5L=M{qw@5O6j3s*83$PW3UW5;%ej~U$Q0^&w%sh9wzQm&s>UBSrKFO zlq&Eue3`D4!j2u7cx9|vW%sl}9dbihT@-=gt+)JE%0MyS_XvZ@;cC9f!~F6>tW(~y zgWNKV`K-p54t>X#IWm z;TlsO@LBM)vFzpzMv+?1jSwLOk=S>;7_@z*^V}vJ)L1j1Q;7+~XT-F;h4Ag2 z(TNbo#-17&*u@LJz4$s+-YJb)5?cSn8~tq~zNw~+AejT>>(bH!F)l$!i-tmrZXyY@ zo$dpgP~&%XLoHvK%^8aSut*x|eg0iwv_qCid#3D8pPFwgvOy>J%vsDm9?SyzMZjnj|!E7xu^E4kU<71bJtYahI%s9@Uoh zxvcToC2n`;oEwEQodk}SZg?3x5q(L-O#1tlE^!JH+*;7fnmBoH!ZW^?KIkdvLWP{3y$bMTao%E6Vio@O&gYCoZPV)U4giF+e8 z)@))U$FzzyzUgj6TzKmcoBvpyc!hCst%opDZ~{a5M;Z*F7qvh$mKz|Lc#A7tbye{_C(3&e#LRMbDUX1;AviVB$&=gk#C- zgZyvR>kt(_No)`hd8LdVR{$X6vlkDs)dUOI5*+kb>o_iM3^EXZAtk|UYY)=sM1vH$ z8Ldt#EAGr>!xiSXa?M^so;UBnf>K1T5cdyMQDS3LmdPe1nM}(N1dRdhf)@$^`cVKq@&sSy`9lmOpiKxTU*z6 zPYCqxz-+i%jR%`i+FUakN3QQew)J4|UeFRa60W?fL8zwS z-{V6IhD@Zd0>vJoy|pa4p3TyD#u{nh_LJk6o$?J%taO1^$zCYf=ho1DxpGbjcc62y zftf*HrEJ}ZELr>}RIqJR$AuEMFMMy3nQ$meVKC>av~@%f9oaAeL&Upfy_iB`2Vhid zN|C+urF#U?cz9pa->duLe~FZ#aECu%N4j&<^P}XW$K$oX$qSczLg5y~-fE#}?SBY# zOobIZN*Dt-?O=eRJ#x(N!Hl04{^^w>rmJi?zK?gI0{*v$VU=#Gh^}|1^MF|C?xBse zgw!z(AUb_`wA5_xXYE@Z@gvKrOfnpheX%0c@Xf&{E}xJrG~c%dU$3|t>jY2}F~EAY zB2=(=`|N;kv4i#6hM5V_6xYJ47710$!bY1>ie4TaoO;;+Uch2;-;*XaDj@PEO2&yyR}==hqAFo+@LdC z5WdcGy`y4VK=$@r39{IVOnThLxNFCeTfm?7T?c=V2z)ED&2j{WGL%fl^Syy9Mp&yI zyZ{U2t`y|>grfI;fGN_SjEsE?UU{zJGVT?U?xkY%95iQcIwbQbWUeXWXqEM0EKh<> zqvsUXDN*z$8K-rdxErdS%=-K?XC_iI@F^LCJ$vOs)D zCX8C4w1YeUkW79=hL?{2-Geu*)c4H(Vp$HJ{TBrZO;!-&o-5X$m;riYy7zby+%eY- z=FOmLCS~?&nTW7w~MT!F32Fa(~Wd2pwD4Mc~xN>HuudS_TT# zC>`=VYI!Da?vyw)QkJ}|Yf%nyqNiI&qFaAb;2jC*p-{!4Fp>Oi?C(f3hj)#baHyVt zP<^e~>i*%YJna}($V1)EpNRakB0v*ic1Xp7V*F;5pB5ZpX^pz8FQzn!bA5GFnBb?{ z?`b88rX@%r;YI>#vv{EHr7&reCO(i?nU<#p!L#E0Wru00ipQ2e>qt@F&TMeOt=D&{ zd26djjA#UWC&-Gt8|?US^ZJ=3n-<~pMlJ3~!Ac8huRgv*%I=Or(*iQ4(2c=|_kH}0 z;j?EDaJA2yX$Kvmd7GFJqIUJ2q|p!fZ7o>tX-m&RiHS4&uu2yb?Jz=RtehSskIare z&LnnsiqoJWl1D`emPY}x%q8wf(u-)Dh*9aE@QuT`3zQpS;-FN=!JbO| zg_9$iBrXnNtHVtbA`m2JsK(kb3G#M*L?*NnqjhLk8{hNPXx*||T}U`2)O8hD8>Qc8 z=PCS+j6#tLg6b~Z16i3+^-gPP5ejd}9K0!?o+^G0tEeLZEkyHW6z=KIi|O9>U?+<1 zq56#^Jh_?F+&WTEuy%TKtynC4tUDe0PNAbbls0B|5v^?5M6x#a+FA_XaTG8z>TL1} zW{-+wo3&)?ou{KuSOMXaOAQGqbD1GWoEZoin2$C;W6(VRE4_chnEMkNTiOVK=T&Bv zgt#2VGqL26NacOtOSlEdnKhHb>@;D|fY`g9BJ7SFT?^`_y2oVb&J5f_^!3B$DnM^T zTfKQtW{Ds_m{98p7Ff1${+T?)0$ZCEEZEpd-2siDll=RI-k;|Qz)2+-F?O#xqKoASPz(}~`I%^8`=Wlesp z{*{RCJaA{AXQE3`_XD+L@;$(Ow2o}~XctzN0Ao5>#}oELXkQ2#T!og`Qn2GzSUm=) zGh}!TsQKa9pKI?oD!>G!p#t+SRjH`d@MOc$M9-b-XTNZUaM`LcwZkh1F`mUMT%DE_ zpv_p@YTb%2xNa;mFG)o+lLza=xkdMy6GO_&q>%@WyA6`rM3Lkp-u2g=inh@~k3;wn zif5M;RcLdFSC~A{*Ey#=ci4xRM)netmC6k-R5bBI7vZBwq&Wg6o#%b&M5R(@E#6Wg zy>Q`I-pGPj#z0C^^9r*5DrdaM<(n?*Gw-?JJLx4BoSXS;8`#l)zWh!Z;}KrUs7SyG zwontcgi3@#D>E|41E}G~qy^tEV5^UXRmeaVo?d7K;Th@8okT8Ji(WL%8ufBx2Y4_i zeJyc7cKbeFK{Ntc$=5mQ6jZqz_~>-^PHS4#?Sh6hWY6cF^!Y6=%qOO8!+svdr%~!U z6{*D1Bup%&v)fye66spF0Og76Gog8UvWg_$u&At^m2>j>%;(#Fled(^v#Q)49C~k@ z(PE5amL~jERfY1aL)ayO2_ZllDT+O4F00F_ml76C;jRImBY7<=QdyAiniFCVj%^bM z9muIl0fu3vK;tmC&5`t-_*mbv`P`DXlx17#Wb=z%2{-R@p|&^cieyp?ilW{vb6`!n zN-@QGvH;E^Z+Ns!DoZ|2222=|P5r;AmvU)&LQsQvXPUAdR&TgOG*eSLKHMqy{tW(J+#$gm)i=QNY+~5&C)fp4F#Wz;ZI&v{d`T7> zBsB(oM|;jXBU0`%oyq}WhgeKQJ+4LuE&UpZ^1AZaqIS8NF*0YpSP>2ZN0ov{#1EHe z_V>f|rA)++FmLZ)Gi}BCvVe~1QgJqw9gk+~iZXK*Y;blYqpCU1BM}}WLQ(-9zmRW~ zhQ4k*=y4_kyQQrzgq)izQq+;(%E-w+0zII?d(QcBYx-3(BtMtq4tS(^8vc0G;5N{M zYf^^jpCp+8P;}3l>t#(F!Ig@(!@dY%W}j?;{?i26Ir&MhL8#gHJdEhlD(c@L;=*#6 zTJjtjdRNn-g4uz*;D2^II+(9VH-IuB2PWqB~PnDJ? zttk)c;E*JDtx7o@NLibOKL$7dRHo-x{c1!qE0|f$)0Q&_%(^fMk5U&IT~~){?YHj( ziKU)!Z1CVDpVcbhcY0c(?lcoeDHIG-?d=z`t##Oa{!nbsEmTVcaSk}SP6AflAdZ!N zKx;xpj-@W1)v+n&K2Jb>3Z$ISY6w=4``S)9l&z4O+Q*r1Clp~|n6z$%*_5*Rfj<0U zz`U1xzt0CA!w;yh7ms90OKrfQyq^jXyeNh)b(E17gRykx(5-(S%=%Fwq`#`0H&371 zULs|&0$B|z<{f!VpNxadDMH$=HWx-WoBcWg{->dIB>z#!)`n9}onL{vx>Fr`Lc1O+ zgpj}Ok{aAHicB<{@>)jUCgg9m`bw)R@Iy!DcrbX0(*adA-V24KpUY;KJ@-&RMTu1Z zy+3X%Ys(DgtSw2nDM0d1lDv32*ilziP6#pEOI4JRkVM>SiYHt$37z1+tveYMiKqRk z6W3NL;(6X(am$O)DB>Jk9MTAMN(b~LWmz;8(7oaZl|^tGiJBYiX&t2Ys1vN}QJUfY zml6n4C*(KR_CH+vUe_`D>|d>U{aIqdRL zA7xUK)cA`b8#F_iHt^GsMZEy(2ak3G!X_VfLHjRsgzOB3)L02D;!(ftA6MzIRDoFT zM@ldN+yF(EI-`c0_(OFk94sP*criF6ln3)iPSsZAU=F_TXaf0D&@;b)#yDXnATHGI zR;l<5vc8v5d*cT(E9M%V2%@$XH-uU0Rjs?I#3dc-X(90_gk(9>BzhsYZZ$y$%*{3m zsCiz|bxo2dk0;(F76LjG8m;WD0 zo5(-EH9_{gtn1Nc1zdwT43(E~*wKsqWmX^`5v_PIQ3|xHGilUJi7-+UutBSvLqsO4 z7zX1Mqg(b9s*n_G`23X(9hON=^ z9#otbq+AvR-dJ$$E3AJ0a;L_}og|K1ZiIV#G6pOa7_)i2pWnLGgM={Y7tZ+CBIVFa zDgNkF6<6&bg|+b%oS>eH3@4P75GYc!zsTq=;SorUWQG~+ml8Fc%_GQUWg$hS;ltNn zFh92I>@VIpOZHI(zREVqpQs!I>WHO^bn!W~?K>!JUQJTi6av#aCbNP=VTY{3458-^ z$_%%eMq&z+sF_I%$JUd6pOjX-VXoV9)0WiAS9Bo6d@Izj`z`E>N5M~DnX?CxfTze| z)!yM@N!vs<8tFa=VbS2JuM&GuA6>IVK4&F!Nm*`FcP%^uYyyuKQF;QtW&cGKALPpv zv4!^+TGq`2PpueX4|EsL$w-2anfmP$K(0KKD6pP~3AOorV*rZ&HU>?+8Dl}vvN~@_ z;(C5&XMP!?_`g7dZStOwj_@E)`b%R0Y;amiQws9v$xz3$`o(u!8?Ba1wahYoppq@p z7@23&>5o)El0ogdNi2SuxnRL28-p2 zHos+uFZx?$de*}VI6`2E+nwqO5Og~0UyK?lS8Bre7t+TcbOc z7!q&Qa7gj!u9UuyLXmPuo8!g){Rys6M@MsZ_c+OWe^0lFZ-V1*%c9{0yBw5*>wV!< zY)^rlivtD;H+SkPC?ORq#DHK?KwFwK*_|;O5FpjsM5Tr;A3I7NFZR;OCLOwh+9ZK> zpxG?#G`I}KpMBr19EEP5ju_}I+k{lT(1EV8X3BaR;=w)zlE)Y&DFa>J%rlshBT^Gf zvZv?01ui0(N(oxW5ve7Jh7+WZ1S5ARw{kqt8rk*sBhw^VK<`4Cx#gL18O*PoE&UPT)5W3yo_qR;qa_=74s1 zSe|Ln7g)HLI-T@BEXb-~8j5iZaQPB5cJWNEA_&e-|J$Ip8PXOG{@f)j648cXYQ-!0@Rb7S2D!IvZxV>Z9dJ_%B_l>0IzCKh*h}P?m z^`Jc9z#wV7i17w)guSvKO4fH+%%(_1E0Cf;%;a~=#WQgarnddEUaDG22Hqh$KNDhM zfDnMd_?Ms-T^tzTui4}+6NXuj*=ZV$>Vafp79U?3U`x~O_imO(7gN>{ zTzZH3u0q8P`|PvRr`y9aOCg~G^Rt-^)rx6u($Svzp>&7(%vdjV@`fEMExRyhoQ`EL z(IQD-3y2_-r`%2AdMHlWsq}s0*hmUcTppKv-Je3GcB3HNrb8Vw5!vK^`Sy?5Fc;ZS z1??E~*Js>OQllkrz=G1!fM6DTk+O^NLv{n$dYnLLdQ-A!0$j`?2Al5rQg&CGq)-X$ zgvq5rgj<42urX%Zd zTb76-gK&>_W0KtLNL2BgAGcx0B;A}CCN;h9$Z0nphf+ev7thD(J*;#`N8UrqR{)G(IAko4iz)!?`w%^zm!XIHJ!?-u(p)+0he6Q0i>fm|a*T5ve>5079h2YHuzG~~!> zISyj;i5^wKU_2w!XZz(L42^S0G_De8hn5Qhgl$xHb1o?etjkKT)!lF%{B%PhRQ4`^ z2-K8xmO*ZTB9*xPpCwkO4wHuXp|?6kKCeu)osRdfR|*_DSWfOEg~LksiweuEP&424 z@&~-mfC~l1NbP&fn7mOD56Nxw;4>K$Hj0Ob42{sxdsg;i43-yN zzqp{_@YABMrSa=U`bCFTSCDcD#LHOTzf`ZQ?t=1)o!mnrufMI8_cqM{?@PGh48LTPUex;d3TNQ!g9FJ$X0ExQKor5zs6KhN;V|Ex@&zGnkk2YwjG znTxHUZb0=}VZafg#v*9VnwmIKaWf8j=Bn{+679d87}i)`4Y^Uo&4)-5wC6?`^`WcF z;CeaF5NlxPz_4ze4pRq|iRs%XrraJQ0}Zkf!ERSzg!bsbI7a%`7K}cipgiXWN_&yn zMc5YCs=`a-o+x^*|A5#h5Y=k+WwLdB@4Y zwXmGR760MpTKt%LWGNqof*5*)vh#@=WA$cGs>w|{m+CtHmm35$CB~N>@x7T*^6vga z=7>n8E;aY*7&Kb+)hLc0dsj3eun*e+_j}-hj+g}M583t#l~S?S_l#2v+Z4+{2ly#V zZd5S497||>mjLJBP~hl1GYq51I`4bpX`NB_5d31iSwwfM)=15Mkn2(!!%YM3r}My= zKQa`busrIwPWP`q`1eiGzC!BH-cQcnN#NP}d(Y?V%gx*SCV;!gymF@ycxVJE@4r;L zqUY;QT{gmi)f|hm zi;2Q_3J$UO>WWqxe2clkH}~=^QCGE<&_(E}BwF#LUfuJ|jhlM<@LkbiJw}WOK~8=uDBZ} ze+bc6zkp8<*qfsQm3-E}?x~rTlfT!$eqH(-@4N>^lYOJaP5)6o0bmAKE7KzgK_fs# zjw?~_b#Lefy=qEK2I7kf4^>Y+in*JH8VM+2pO9IhO^3czi$+Ba-A!%?2?^Rq5>S^s zik|&V6bklB7$~ftLH;)6+;4J(z@)&K*jZey5zYSE$C9z7U@|HWE5W2eG-jHCV>V%J z@8jp(npdGVIUTspF^%z4&x8ZE5^a3=$O{mutiHS2$6PD;3z$w|)={{w#h=!wgqvRD z^0VFldCSuwQ8iMTcfmtrg)VWG`T4L z%TK=p_LnCbp`e9rhfDD~|1pN$MQ;oLNJS)p-(|*p%!JUvN~dB+93kMZnyrT&JR$-L zjMt)%x6DP+f4_fnGONbmyea2zI_;e1^A<)$%NeU?^ycj(7z)KkE&7 z%yqd5JbWB3c;Bz7^S8_oo^lrYQN8hMZ|bMmTZ}eZHS}Vop2X-VG4w=bA)OleH1XpG zCgViFH5hx-29o#qp7j`yE~^y9c;1N$=^fEGhAcP*Q~|H+_ZEGd^Nom6V#eo|ozPgS ziUJby`u;@65gDN_$)Hbs1)MIK&-Q#w-|tw5QU;%=WB!pbgp=Y@Pn$f--?9M@2K{qV zc91e?hQz&8lA@A|(zU6Q;Ca9^V#twmJV{fm9y6%~c=ywHRXI%IFC5+sd;nMz#%OG^XZT%OHv*wJu@h>i;EfV+qUn)#l5^fIh zKfDa723)cK9+ky6;DY^k2Dqz~D076rp&)-19O{2&Fz&~NY3*sk_4$`f#NUsb@(;pF zQTLp09s>lVUG#r{{56Vu@-J=VY!erf^1r6LN!2kSoxcp8cDxK@-MM7+IOzk{{{`_VngcTb9X3z@6<4YugR-4|C#^noFr1#&^nTt?wxgKk#?w4Q97~DV5ITLcvK{( zdRsJcukKmCpWxRkQ()i*BNotm$OkPwXLiFg+lWvwYy8?(Q!0^!EoRdOoVwa)*XiEB zjh@tU+<&t*#6;GtZIv)QsuO))>fSm65VQ9i-Mc`??Ad@TDyD@6S`oEU}#V!LJVu4V-1EhrY0)OGsbXQX5b61Yj!DSX>mp?5iRu$hv_ad zY71|BZ4EUk*oVfV8x-jT(#`3^E&Et63}TRKlN%;tptXYvJBbZD|rfTRfQ3zuCrC|s^Qcq{Fux6OM12}*=NWSxsMx&9-p+_;j1F@irWKa@A zlUyL=<%Ax7%#^90U37N@(!ap{^=>&jzxVL_KKo#GXU-TJ7>M`wbmS-)8EsDmTj)-R z{816b+ZQV^LeJbzll~&hV!jCW90C!6hZV2C9$2HYp1{&iHV>QX;6s!m@zru6dS5$V z(dq>%G99vWfe6sip$vb{1$mQDLHK;oGBaO-e?Wk#VB>nd|r>w znA8NOmzaFcs4_OMD)>pRBzxp?klFTOA>M%3c|=Adk)se_RKl-kN0X=1YrS78u&u)U zEWh=~l>RpA*va6X$1$Z5rhOvsKYI#SA<^Q>oPFT>RWKV?lU{N|j6mZ^*9ICw4Co}L zo;|-kVDZn}{TsxVyXZW zmZ5TJT!Wz)*TH!Ngu7V{?;k*oS163e*HDzfW(qIB3*LT>|i$%v<)6O z&!$rVc#?1!!*=*y9AFe~ZCs+i!n8PFW|ZC4H284tu?o7Q8gX(JVXaBOHuh=tbx#up zMSmMXNeL-gN{M-dGL6pW-@kdm6Pg3trdnqcpiINGAVUxX=%wmF{5*;2V3btI=?s(4 zZ4M%KPzQrz_OJ7S;qCWfb|d`3{&6$~A$a zA;}e6O-%b+iWj9U<7Zxxg^2pi-y1v3aSCd zgRHqFVC2Vjd- zP;(Qdz%Br!8u;Pr+Efxc71F?R9#I#MZ&Q6^Y3$%v`?GC8)* zPb^)N&%Q!<_ta3UIq3yx847ERS~^2+^7T>1IC5uUjRP&)-k7V6KcJ`JqB_ANyvOGp zPFKid>&2_^&NhXCHl(zHoRE2rVo$tllxd+b8`q3V${~Sk(zG=ZR>szO*B27`zZt1n zSbRCMyEy>y^th$?%r!u0YXj#eQ#aO7p|U?Gke@GkN8iAEa6Tq~DIYpEVM0#)~nF!=$+mLIbWgVTO!t*YrWu0*BugSZ#Q zq%a5X?e~O_{ZGOPnmx}&Y}F)KtwC-8F(4WY&f%PMi?TI)Vl^K~wfYm{#<5B7*0nvn z&>Yjmi!_yz`^4D(C)uV{t2Yu@$3DvdWBXbG*?NgwgE|1 z$MM2iKchPdvopxb)U^Qd_z=qRE{)6hzLBGk=&@CG&Q?RQOjQQ zv<&*F60zPaF9{t;S0X(_!vH@4o#j{nft!N=y=t#-@)D^E&52_btO;V1BBhahOROrs zpBXhA9|b}NEV*0~F^0FW{8j|U%7qikvKwa>xzt_PL(i@P&t)r@As7$3!aNRM4AXo~ zkThT$>z#4@9IaezYK1zHS*;Y468#}MP(Mu_RLnF8ypj>v&+*cx+6;ad3|kF^{E^_aS(m$ zMIT&@d|22z$8M=IoJhUAWfM&*v0&nyDoT~GT&~$Y3w+?z`S{$uDLzBPa1|njLB1&w z<8qFb!K)c~!mnmxW?j*oJXizb7Y&U_wR$X!wBfjCk%vatN=z%wANmsbq@7S|al%1u zif9QLe+X^|hRXxfPpvlNIYwh7Ez}@$x#p#-9KnMtomz{D9i)8^b%|;7tf+xjyU^ zwb^ZKF%WJc|9Gt$6FKn5x67uvJO+k>61D;Ft|^{#DTkz%xIgOgwiOkO!Mt+Ht?m*ep`DH4FotT#1E|4Bc`dO% zwrIM!obsUY$}tJ(D5e}{Sl_+r(O9{~!`%_Efzh}@`A6=M@EjpGAf1v`o0excZ+dz| z8*Xt}jazjPwIbh2>?AkbQdK-mUd1y>m{*X@o}#1p*3~XhM&4R@MzAw)rsj)`*Ow>T zP>QRuaji4^^!?1#hfV4#q;b(CBlI!ZmOi)JT*pQ@TNTc>y<=elXO8CQ0Z-93OYk?~ zA-x{OcUOr1_*`P?vC^o@e!L;YaySV6`LtTUS~m4@@t}=H7PW|!2^!L`2$PP42mOrp zb3uBri-3bb2@99@>$mTQqhc%hWo+-yPXr%-9})6qO8>Y$dU$w0P9h4<)XdJ5eIT=Y z&1sj~9(^=(J{L$AyF0DxT)sIf5o^EyeesJRd2@K!7jw0VNxG`qLfb<<-_;3ePX6@H z1Ilx&9#lb9VvjrCg|1h@u{$(Wx2{7N*J1EBw!@yL{8CY(Ix2?A&823gr}ggOgp05G zeQ0jJU9Qf+OC%;&xvz1MhTycd63n(O2Y1rtnb)SU_`4l>f_V*mcl{|UOpFZ%n2*Wf zXq^$`u2=GEU$9ZqW>P{wD$IE`GqQ_4n*J)}zAFAr zWI^4?FJd6*bP)K-bPyRGe0zEcF!{^18Z15pS{vf@hD0;O;ZjQL509H)i6HUgWCBfF z#qYyKd-wWh3Dbo{TD^T;h;jq3EMpZa_jDg^H8;oVt2Lz9TgrfoA9>Od;Nmc>s1v{J zlh@G!M=2ZfI{UEJYONx=ol%S44#m>p3WqPjaws8!7I2|r@snzfel}K{Kq-fiTGm47 zxa$|3(_=}&m4P8@`?!?Hze$()KZ|pc6xwgL+qSyr9QN119oHC6Ikw#q=DTkHj&XH3 zD0E(jIbHelcQU}DHvgQ5b`h@9<7a#C+6k4OCB;b;Y7}Akg?JJ|$jAICKJpCeje?R6 z+|-MBJLDrv>Pvbo#S{z&uJP1NsCW6Re>?o+D3nQkkO`9vAMwn*z#$;VMu~l^kTga2_y=@RW0$ zVgbFbW(6v?->xL%ho!}a@7#GBOM2}!8SWYYe-U@xzT4;&vx zi3sSa3teEL9}XdFQVkJ=5s<-WlHkQM*%MSn4{FL?xK1Z(ohcKlal(&US`}6yx|;rc z{`wgR_5m|cN1KrG;sM=cKyIxna0-^g&oR!yZx{a)r4bJfC~68PBut6Yb^WJ2np|TI zoa~`ElFA8VevR?k<-Piccz*tF9?Y zhn=3~(*Xkk&vEng6Q&qVEk5;{Dtk zY>TNk(6rl@D`ilO@8{hf55t&Vh^?n0^A~7qAYvwnzEGjzRy1)mu;Gtjv zin+@*W9-6Fo)5{IW+!Q7hWY{mgv>b$e=J5p0>^mO5wRc;2lBDJq9$Og*T#(GNCl)c zm>NGK6}UCR9-A%T+q9>yrei1?+BVeablxJl*(3R&&H@I5=bb>l0M%5*w-JUBk=_F( z^)P z!1+Eg|5KAOQlw5QfD<)1PS+OJ0|WO*P=*mpapxx7Pz=2%x{h*~!sW3YivkR<*NB(u z!z0Yzn;fGidy)Sj)~YnVn$Sk` zC0zQA^0=qhw6I|OgOq2~qTtOyBi3LJv^H9?Rmzqj*Pj^VO;M*6QG>Wl&fgfN4}K@5Uc1FF?+_~d*1X><2V%KW zn-5PHdaK$*$)%i}G|GtWhH7E$-<$U3CZD?)~UmOg>D zY#KgUg;!)U6=5ZX!6E3L(Xk|n0x8g$y+x!9?swBiR;m6Ue}3R414nH zp;FooI_eD7%b%x7TN1-|puspmP`Fq#Rs1nQUhs+NG(5%2)aaii)n+4g1EPAdGh=Fo zUNl~j6I+xYL=wr$(C+g-Q2 zwr!o>=Xu_7?(=3OA2PC%tbCax>zZ?3Kb0X|IF*#T#v@F@n9-eXwjS^+1|_NyY?QeW z8q(NgVv$Ak7R`9|j4WMu&2@^4c7Se0Ip<|8-HVi|o8_aVfIo?(z3RbH)-?kI3Lc1~ zOtk87vAXOr8r7M$q)0YXjT5Z(F2_ms@sPj>nv`W#k8QD>zFkE~SA&YC62r{S zvR`=g?W_)bQAoI@ST!m&Zk6N!J}e8Y?(iE#H4mlcWP!`SdRv~xe0to3zW@gsoYIH` zDeSd5nu5-rHr2bT)St*isc|JgV2S9>9sQNF$gv*C!PgOyc>x#S?e5ZVC}Vd8r<2&t zb1bR6q7GZerdrqu3`ns8K+*xzq(URC9w9-MX+$ygG7q0Sm+64>F z2f10hR}`muF%=YR-zXBd0sz%mi6Ar9tL~(NX(@x8fS@~Neb?6Ij=ZJuxUG07p*kdk z8$gS_Qi0R*NmD-(RC$ioK(!PTYeHW9bYA|V7a|CPg}w#jCpkO#7`s7be?}4Q%rxRt z&#t&%Q$Y}rlk$e0)!zHJyvIknC-GXh7 zS$DXXICTdE7<*XXpymdvo8jhQaey+mDqf@wF+;{k zJ2vfn*vcBL{OkiP2k;{v+;CVvy|x;HSn@>E$&86Djq2iQWNK30(LOXUGmxBrbhULA zyf8S2)`kYW-Mm8M5kSOJXQw5GXOjm}HPjd_PE$>1f&9WR+ZU{q=iJ*Nf1G>Mk^n}w ziwq@BLNc#~v08}QKKweTTvG00j_?ypbH1)y^aQct9|3~da(Z!gHwUjS#lj25MfFVs zzCGL~PaMT~-DJ_%ACWtiB1P-d64=KRl8D&mHuSX^H#9Pop!^&;`)D}bc*uRCLXgr*2m26|34u6B<< zSb3wq52~JQSdni~Ahnp>7Hyv$<2{c4f`M3KesfDWN>74@#o=#;!?XSdSCsqoz64f1 zehv5p9@n36D2^|QZaxCqrX;y|vB!-l(yw|xuY6255LW8_dlV&b7Lq7ZkKS%DY#`(j zt<_``B7hE3Gvb(Z%Pw|~^rnMbpJr;M#Fb^lnpN&Lw(v=Deq6t=`Fr1|aHG@=NIWf2 zweg(1DrO0ZHW+yXvdSp@LaMNAp=%RGbt7E-AnJT*rkBW!G!?xvX@(;;C-6M_#7?f{ z-YlRO!tLxJ!9Qg>8!Lv7vEtoW4_}!7lbZNJS3?2)2jWqb@IG3K0R)6C0t7_#A6*?U z6=?z&5-`nY?YuD-|AVroP+3Z!5{@JH%*MNRQP+vkYb253QS70oOa=|@i_3xn!vaDZ zw&Cdg?C>T?0~T7-PDLzL()4)v&(;a4hxVEwHgBYD3yUl!Tf^z8$SlBk@1h+px#`aH z_G&e4?3|r?RPk?P5}%PJoqlo#1bNnUWKshl9q2}`r_{a*X_I*>1KzrKNR#Xg+9KbB zsL{T6{BS+|e9a3(2RAH3$B{hM__yzg2hNW|liEn5-*=)?Os@){WCHeZ*E6R7wBSL$ zyyvWD$H^s6cDDK8b|;lW-sdG!vXJShcpx@+npw`t=eH+gNXc**>DNrJbbk{Xx@!UO zVa<^*9RCFL#)hn{AOfNYMt}o1+#TjmfsPb&RQHOF)Gs(@!2~?%Jk(G?J}>qiaDXix z-u+99Ph?BxKT|v3Z|Q+L5li>yk3(8VmbXqs-wkw-)L< z6I{?-gF3oLs2{Xq=xl4@$-)f*#rx}>Q69(b=7#^)*z+}n7c1b@g(qC2jw-~qYG?Z~ zB=CY-Wgc%eD(mF-&-6R!dbC}u3_t=VNwrgFR)=JFuU0!JBX}$dSudww_yC|4gpczE zCBrVVtgUgNvomMai(FL|(fHbfVk()32;u?^c2U0yLxDL&&YPruOJl$%gI1`CU>})H zCw8K!73P$=XBR;7_@{y3x>1>|p7Z({8l(vA3RATYrd>x>@_GB9B-G`mV8%rGnMQJS(vuD)Bq=`;E^NOCQok{YFmZ=r*y8M|x>3 z2uKPW9h9SsZfU9-EBXjXmC!T%?K8I*Mir`!rZNVnys|*Q-^|lUb{=5aj$E%Z=FxZc zyaeNN!s4?n-7&v%a%)ymv@8G&lEcVgILVi0oV1GG%@a8SEz6Vjf=eM0XAI`!%k@vM zt09uXf~_Mx%APQ`rUkr3>YSE!b5o+#eg5QX;S)6Na(vS^j}Uq7YWh$SCI%jeWveHv zw>TGwfpM{zbtFCY4>Vu^nFRci3;WaX1FEOlgilH|U}TTB;Q-+SW)p{3{#$<9Zy-eW zRT&~ABk;!)bB%2qgZV{p@!l2T+9c#|dYrM3@yd#h(*SW&=okzli{*j)aOc8?GS{SS z=9^+6Db%x`FO$Sg^K!Sq14E20IXo}>PW;lXESh?C4#{Q?`vs8G8`efMdt_iI&j}9s z15(Kzr$c7kt*uHRoKHtI^!VZ5y4#FN>>!>7;1kueH?MCmc<-m_FED%cMj$uZt;rTIc(U2Wo zo`lXLn{YwhFgH)uAriO+rpIY6I{&~k!*74Z_caVr#~R0j0Ekisi2Dd`y3Sp5s7=D@ zxJM9MbM5W0wgYmf3~(j2K%P$Dyq_$&Xh7ZgvuOa*Ie?_F1fDe3zoL;$NdeaTOF$P6Hme6VR{+rZymri2L2v^9qlowX@JN0=|ja?wl5Q>PuW}j_} z9xGchm+ap}iS|&jYr=_|%ub*Tzah5n%o&uypma*zk~YJ-o%m0fA13c zgt0QMc%>ZrFepXTAD&yFs;7pn51R>t*VjK3=OJcofCU6BA`&rRcWpOHFX@RE2|}Wu zorVW=z=J<1dGEjmr#q&ChyO|TRNKRe@vHawiFn#mcu4FvB2cgVl1UjtcHeFTF_gji z6$fDZR$=NjJ;|mXQq3mn5Y#K2aceeMncOYqF??E*T$iy4BQY8XtX)`_UoRFJ?BT=q zqU<0kX@jtF9us2l)CaEWNgRx0v0xBIeuw5F0qebX**X<2`u)c3IM=EWB1;c`QL^ql zDBTe`_R|hCB^?HPYV7(KCR%l~PaoY6s~9k!S(KZCl%x}n!cM^<7K6m-1zB~1Kn$`C zv+|3Y*O*J=9U6*t2m58y{6>=eoM3Mq!;Kues=O<)|rxMG;oK%OHQj4c!9?I@XWr? z?_GO%yN3bMzjQ>5-auItm?cM*U&pjBi*ou6$*T{Aepx9%xIM1IiBx48_EM3iACFl0 z;UQ13L!NE*CZP6ItoptX2X4W%0^KqhVN-F&=Wz!S0hy_-O43`%Ty2j$UIqaDF2%!% zNkbBRQaC3>WdkYJVHQmT=X9gn?w-e-i8ioCAesT(_dLi3Ieke@ygZDnOWs+al{6^@ zGN476^0w2h{TDX1&YJ)P93e{F*-V@pyds3v!4rr!6+4K7WuCkf`2F>gar3t(R4m0M zp8To;NEOnJs0lNaOD$p-XeA)Pvfm8gd24TzidlE9k@HVK4*Q>uUWlBXnALX8*NAtjGTROCQOaYwdH&>XeUEh2zHAq39}Hwe^ki8PY0x^24+-kS5q?k8wa{s zpnd^Qvo16-#i{;w9XSR#J3J-mn3|%tWBYWj5~nmAW>%G=WD9j6V)K0 zpjvPYBAG$lDK1v7LU)5T=AdPt>s5=|1|)bkCU_C5{DbNb?)*u6zqs1NU^El$$of^4 z`3AJYRSGvGvGHOtMUwQPzXaQt+Q47Hr-8}{H3uh?6^DxA^)CR(D>^PZag2MVxG#EQ0s-V$Z~Aa)Bnt{HZ*P^V-!Z6u%}Ju$<7;#3A3d{BMg;z>f8=$6$=Fb+Ks;R;_Z@i@_IF3tY=mx z9XtlT4&C(B8bC-4Yt30p$pkaYu|RB2ZN$gSS#1wBCy1FOO(9BLa|9!M2nBimYFfiS z5{W@$u-BUsnI%vWAvADgBVCb&6;p#+LWT)W=R`CtfMrA9yr)5-xYD-k7#`^!Mc;GZZn!on;(V6gx7s0HKnaBKU0o(`Csn5I05`XM$+{Fo z2P^lvjy&<#&C!#mlOX?Iq?+151C25+j$vOc2+$P&a|EC)s}sH=p3FuZ_;(BTgP_#xgUZgz4!7W@gGZlO?N$evO`TN1ZN4mXoIB`TkJ zqQ88CilGEGIQ~Ej8cg zcvf0beJFmTGKgtK+`TQnq4(d%Yjq$1B1uTfj|=t^MUtth%o{?tHP&*u^zwOurq6z3LQtVOKkQtl~28@~~@!g#X|I2-l)x z9qc=VHt$bcpE06znxi1zLysGid<<*yHm2jZ!Oyv&5o;DEb>O<$)4vSl@+EY1p=)`i4N zXa&Xa9Duu-yY#SNVnrLo$+*PAr0gF0aC+}{L^)Va^qVn z7tr`C@{o155)o7rIj~WGf4bcl1B`OI{2@F;`Hi3{ph3LZo@GgONd1qMVr`Dr? zi-owmLCOhpl@+&$^B*}0#dveOY5i3#QmhO614h3?@{u1a?#;)@|3LZt5uYak-M4_t zY<+ram{CVL$X}oau%2$npLn6wC-`0AXP8D(w>`|a!`V91p5esyK*Rr1beT0_-1W?gg#=r)AVB36eAX8A{tpvg8J)H>;p@3Z@~|@D`fnd}U(HrIJz%kwX*( z->!l|kR5J|iNG$wggfR)*jF&{Wy#tlu zgd8UW;4g#@1@?0dZeOsRq12V=bIm&_jhPQMXd7vAtEO77|wKnqPrQb1K<2Osns2bc~T8n}4D4V{%M zdzqPFv`>@<^!Q?T@Z|2@{d!FWKXFira~Ei=Y^o?zoD*88UaX6#-KjAAl>N_Ji%Q_= zoYg4!$>d1Zbv58EtC5lq-AK2q4h$#ub-Mb0^kcZE@^q#;dR9`J@6KxXGMX%>9ek?67D<;#)a?a5K)C`YVMWo6L9 zSdt=GZ+DPCVVU@%WXkRR(IO`0MArF)DZVuU0fyZ|f|jyR=9el-%*=+nG9Xa=_$$Y^ zK4kc)d&Pu8Mtiwnrfy!r_3wDv)Umq-*3PxJF`q~_dn!c|RrgAP3i+d8L1vB)AT>Iu zh2}yQ^l5%79RCFqA>RLHJstE~d!z7(&*L*wd%-`6arZM7b~Co&*x>A;$MXguYvPd} zHzz8xA0 zhVa#*3)*UC3qLlLgp$aqMSrgqu!;4FcM#AKfQ^IkH|7ZGT+;@L22B^H0CR@2_~nv? zz!#{s>83CQ)h+Q@0nAbV;P#aCF>M=se`9_y^?mfV84^Cp^)i#8K%|MD5R*!L86EKX zE43C~TThX;w%|TPns?hYkFA9)Lu=xnPUAT6<7PaJJLu!E()~YxjKJS7qh7QoAKhP9 zlD>B3foj^gEdJcypp8BA{8rloJcK%J)=;FEKSed{(^XJ6V(ge+GnIjZIt(owErdaW z&BFppA5Jzo(Bhe+trfRk**t5E;S|GS ztEl2$Sk|%&q>#gShmmf*Mrx0$m>aU@s(h4<5g2d-betGYdNeZJj>jHR7=PnDTwL)H zC0Q?kY}UK7jDS*8lJB~Fuln6lV9PlIz_-@nmDBsdg#EErtxpEp$adH;A;+Tu*2pf# zl6F|bmg!=IK|vNcc)yZMfT&lxztQi)Bu|OCVtyQ+t}6I;Qy2?Kf;)PeN@=FGVHumE z;h%`6b)(IcLd=}_VKZXPJdVUP#LC>U|jzOw>+G((<8aXXMG`6@} z&mGiQw9h6lEr;?DQUn&4-?^at8&PRqbEsxAeOtflGBOj%md6zzzbqMrG>6|$qhj>R zR-o*;qR?A4O(x(dE>T~l;fF>vGd>1!wUhJGdZ~s_zS($~YAQzT#v}Nyp7O0f+i3?( zW3=>MPP|vt*+Ob$>ZZx(ESlxKSF=ur$)&Hw$=`Lr`!%kBWfHuyq|wpXWX62*q)oFR z++KEG%hfrI8yx;sR-e94eyU(Iwf-CL*o<1{J71#AJ8P}alT%izdOD}F92!g%Yc!fI zuhpk=s;UgRO`Mve$-1q-(ePBVI++OQI`nTDwe=b!X83H;>M)m6rkhf#1QGnIkdC!= zyq@54%glkW=5DSM87$D$;2$kM-WGiO8%!}-HzT5KIVwHcuP|*jQlDIP(6!q*b1!&yT(^!F2LIGrBGupz|V91;oPXDxr{T_9#0LSopA+N zGGgve&VGKOn9TgM29`zM{YXAi;0sOe>}M-$;4Wgz+%}hFGf^c~yC%WO={l{Z!p$*y zYP}__4R54YvYwGFBU{bAgnkZiJC!rTkRPMF3BR~6^i+`rhjq8kd z54chm!SP)rcg^WAV!-W1L!U8%D)+r5W*{uPqQK?6vZB-3u6eh52(l0`jkAHXv262{ zpq)Jn-|R1IERgXD;CP~QQB`o-4+b6P9U+>58>Js(YH9w1E) zR&V-Xs{{QNLg;7*+nkmvt7@+zovI6|#5}EbRdwgDbZoVt5oSp-CZyS$HLP=uhBwe( zUqX1DI3wnXB&GOm%-Axjv;UEp{tJIfhxEmk8-6Tuhe^cu;f-I-P1dW!LLM9THv`pIG81LOUI6pjUhl zr8QSe@a<1WLYNFjN{N#nC%zSE32PFC8z4``GvF~WA=RbIT1uo5pV9Sm&|_zgP5Sjj zZYncTJQ8-&W%$f{`$PWQ#vuR<%>~X0!~!JDnzHdW56YP2vflL^?KUU;^c@#WST^N% zSK8qUwqhy4H*WlvyN#4u$ox%mLQ~+9rwES(zjk8*X*W%EL-^VO4M4=xrKvfKn5jxw zdlMgJ77lr&xy`qP%&pPPA7hR8r})4jg%D7#?G#3EEJd74Q;@{HW*C5>MJvYb7H_OE zoR%*4mN6a|%zq^2U;S-jJSJTkY=hbF_-Q-8q=mDEOV?q8zDEJKohEvW0v};Z>}jaj zV=J~S_Tv#G^hE{61wmI)y77U+H<}|xoiv`PVR`5?Q0~ctuTQik{M~yq);6d zB65mN2{v9a{AV6F9vA>Jd_v1s-X(&Z%&stnt2oBGI|7}zimgcz(7-d(qd7FP288v8 zA}!}q?~xU|nR?S}^!=S<23BO$ijcJZkmQ7#Vv_#&TN4fZIqF2HyEe$7$%Ih*-*jyC zayXZwC_{!AZMLeeCn%v|1(PJjJ3R!K&omPPCN1cgr*%55n6Z$=a@re#s0690yEv2p z=T2kNx8oOhL!NP#d6Z3?%XPZu4U_ILEtYZ?6LEhZkIx^bI~x)gyNy!7<7UJ+apLd%H;RCeW5AMnn2W}cJ#|lCZbF#{0)0$J zqAQ{3QWf;z2*Ro2Bo_Y(N1?J2Poi&hU zp^7O-0`lvR1x<;em&dXx`$rXE@R$U&=r6(RqZTTR9krJ5ojq?JUMv}iM>r+*Ztk!8 zwH0i_tsT)GYg7NZmivk#G;=mbajyXWg!^j zMwcm5Sw;!>kD8m|?$RvgVE=Rsbx!+`E{cgF3)3_TnD4F5Dyo^UPnOK(Cb4))QWF~H znuw{%BJuyJV59PS=M7_GVQL$l5+`QnSua;7$wg}d5~-=!aLS0OE2_B3 zAkh_OY{OL#>7eZQ)&{EoMZz>jaSc`Y`q&$WM2f~N)A%UrR41j*4r6Uo94E6z{D#FQ zZDkjopv3~q4}mL2A+8w1`wechj^sU$q*KR~rkkCGYMA2X)Gx|B1&d8sq)kDJJtMRm z(hgmAu|SFbo{_f;_+BxU21*Ve9|mz^wQ(C-q8TM1$$8NX!+SMqAKIdBOXD0YSLok2 z4tMr9&cq6%mdCGCO^2mbR`u$?fJcuRt!IF$6nmTU6yN>;U;fAiY@ZurRpc;IVm2%0 zq;sGt?Hb^~G2|UuYD*@hn?6ip8bQnaYgH=tusrg^i0w!ONJfYSUzqTh{3Ay+lM+{i z>tLG<(awCWsrKsoQn503IZ@U2{Hp|+cks8Ctv5geb=)X+cx-|oi>Iq0brwp>=u%xHXtkUuH@`?{h6e;>t!?1c0vx|BYmoxX#QAN%?3*tMXbC=ovd z7u-tK-M|zlpvrsr7Mifjd{#C|X~T0N(otoCFKt)jrkhN_&q9_mV`BxQj{T7fPs$Q%-G(|iM;&86d5?p| zi;m0=5ZkBtQ_PwEM}18%hT*KKFfXK9N!LSceU8(Ij6PN$NyF`SBG5d&bJX1@J%BED ziNgq48JAhJbS0lv`?%hksLBqqnVIY~foFDCWeBGF#<26H@Bjq@xY8PpEUQVRkmw(5 z;mAnJIFf#-XPkxj4YeDO=3dlv1mdn-#^B~EpqSjv23kmyrAU-)IIUHCIFI-=hR(f) zMn+B^&2*eB-F^2AKc-8)tm&V>O4Oi>0cZ9<)eU|k&|0;YGH(xB+Otj;k@uGHN92NY z9u$x3I;O0zlE{dbRSQo}^}zGU!uz0UBu1H(qG3;#AuXq~j?8Q+9t!@9*rdG|i1raG z0FnS3?R+D#-|Q0ct@kPh^Rc>yDGBV-6YlLSt@0&|!jHrin4hNmOU(6WZA)6g8fiYf zWZ>h>%O8$4;eJmS=5y*%9>bcplKfZ!xwQLht8bGdA+3i|x;{4Ci93S*3T$nhWeGoK z2XLrrQJ;FS^N&(}I?I;Z=`2f!y`H5XP+#eZV(Vbi?5Ykg5iQ2eF3;<*b|Bp(d}h6) z^u}Y_)d-_5womx<_iA|kBB65B{bFE0)e%?8c5uHlE=sS5u2w*0Q_e1Zl*RtAmFFw0 zl;7@Q%lYoA4a}oUA3C%&p-fI8WUBfp4l#gbs7ZvClGXhV+ov*7+^oIV zpAIOJ@eEw~YmJa(!R|qm7&U#Lv2fg`d56H>k_rso0bFd`%ZvU-w`%-r|FBx1C z!MuRf5Dud@cx0!a6pN=Q_dO<{l58~svX-4Mum||wS#U-fX_x)xyKGowE)%K@Tx&bp}tx?Y8#zAhOaCx#c*`_N6s%=kUz>XC;naNaquc9O;Nd z$m5CEAu#5eOMIv{F^?aEUx?YwtEau^=LH?V@JnsDe2gvrFazhH*Y6ot3HhIM7A^e% z*dss*RK3OjRl>;Qz)@39(Ll*PXCc2_o4!*`%7$&cMRE(jZtq1@VUA@*nVPH8!#qem zq(bP=opyVa66ijNcQa<_1v(jWm!0$@na2Bio##r8+IP|Qkd87ev-;x@-i?HEF! ztMzF6WI?t(ZujgE!Fu?W6PPG`dBJ+(H|F!HN9?Bzv}D?2y9m~9?{JqHU%`8K7ARw< zrXpNhAINe`?q^7tVPh!xrRY@X<4_}TBF9d-cByYg#@YFh!TK8pH`B8x+Lo_-`Y&J& zk5fb89WLBM<`L)lWy0o=^BiH>&vLiCrL(H6TaP8S-V!E3PP3dBuYXRdJXoH7$MPboyOjs9S z0?$)6j$xcNKJjf@z`!Pp(TYFmr(h%-O;WPQYZ>J{229d6srr*itX`NLoek$*+( zhS}0O2_n%FA--{+ycLNUmA98%p%XmaQo-*U`GV?r)*!}_`&?xR7)?%OM+W#*_3S8_ zdah`j+|o=G&k$QCJ4nZ=CTyL;47oP@$G8CaA22S`%d`p6$RyZnYDLy#|pm z6S{?@tb;k7waO9G+X1pq4iH{;A9%JvvDa-mecbyc7z(6{C%jq;AqfCWp5bb>C$B(k z0Eo>E&VNizn_=n2s-h~}J%W}^ORkXS7iF0U`~+^ z=CA8HU&yGHm4VnTzAaVqKV`F@RU;Y3(t0T~^^Dr7cJf@jtJPYIJFBXMZ~d*ngeHzc zw32eFkE9-axz_(sb^>ga{hm5U=xVjSFwU{I) ze5v1am~Wl@wu7JymMr7Y?{M^{5@3~B@! zzGQd(DWrLbiqKx^Xas*^xNYt zL&`?(6L5DpI$ks^loqrl-ZY&+nWG=Yu;s&p_YeMe3K()l+#yQ~!5MZVX$ZNpRe12q zsEFA7?nOmK`U4xKlgc9dVa;k_W8V#p+;dyMcI5MmQaqK~M7NO!+vU;cW6apo&eHJX z{P-iAzjw)~cLHzpCPSHF(yI8KIpe5Dszg*2f;ZN0%K4Lq|Ix4n zm`wN!ilN=LGED!p*ADSer#JR&POj`BkoAurc_OOglr(7~@&f9URrvFlCl3{3vA56j zC9m&yroJ0!QpDh=JdDmcC!F^U1*e|ByU^wDpEqu4@z>Q^9h>2Uo-OVl=q+Kp)j8<^ z0OwGk3wbMUr$N`N!4pfC%G7$Y2AkUGAPc(Ab!vY66SziwvtOhUMZrWWrzUI5kNd}YC>(;!h`*D*Bxo&X-MKE+ zy#`k~-tve3nc;ZSM^JwSz?s@VHc~Xq11bkh=72TY_1`YNp%Rz9XFD-z0y)pRX?K({ zMtt}0351(#^WLqP`NwZ+F8B#_`{`)HUK`CawnW`pM`1ei+j~=mKlkGmTGG%W8C?@u zti6`#P(Q7Vfx{I)OjfRN5dNmvCi-w-Wy|`d`Is3oJG^}qHq5?BZZiLE)!WMI0#pZ* zm-Z4wLDciaX-ANEsi}u~AOX@r0W5@{KGWZ%dG8e4nZ_3c1$;jr zyoD##PZzd{{T?^7XqyJQ}1^| zTsh98g@VELN-NY|;fQPGwW2W5HmgT3p)c^6O{&AwG{KhCJ6^87aN;H%6{6XZcv;IZ zN9%v8s`sVGJATE{y21nCX9+xrZKh(h`IzF%+Ej!PGyHYvg8ste1nN6k>Yf)h#3v8i zN2?=QD|eY=vYxM(X=C-y7QPJCp;{F{79C*F%Xkht^PK;^FEmwUwVS{M(Y|7I#l(yG1*8kgFx zI!nedPCE>AJ`kG4hQ5gU0}44WxNADhiRJb$XuIR3Fhb0RP6Q7sncE%OPl$^oA+Dh? z=CZ>n#H7l2Ypbb+BvIZRivmLBcbBmn`a_lTy(OJYDqERPZ=#E(r>X*jiIt4uEc)^r z!~56i!t?uHcVzPq41*T)yQ_7mvzr{DcSHZc0T9pW9T8P29$xyCmpBiQUald?!lZR0 z{jw}Z=71SP+agqKQwW-lvVp>hWK*L=w>_8iyV+au?oll9g=3pA7Mk4dM7ZXY9x0=U$q#vxrti|ui8yMvta%83P z7g2Iz?L`4>56w7@d?)g$iolDYpEs?v80ihKu0K#<{YGzE?TGi;n>eayN>}Det09~V zEAwni$DD4%U_7$}#epux`SQiaYLa)#7H%Pd@sgGi`|x)f{JXBRD3#Alx0%Y%Uy7o9 zL7#P*z5U;LdD@#T2n8E@!5B^rDr}u#Nc|YKhNA&m_8)i_KIuqZ3n+6f-5^hugcVT(-9@0VEdT-QQ3V)3+rP^HSx#z^jah`DR zP?bnPMurGRWh+IWWOR%`M^%zavp5t}oMr&Ntj|T525k)24=CUTqD8Yj_aL|8^Q~8N z_v(enzf8k*^U&D+?Hk6iZ4|qDBR?%VHY|^~ByZI%h`0D?5(|>s>!7=J3rk0#@sKgjR?3;8dk_o;;~a}ltAZeaP`w$rD}B&?IHl8 z*%od(Ld?dN=QJr9MhYt*bB9#apP;7z7R3Y=Q_5 zl%x|qz1e$|S;(n>>+hy-Pk0m`z`c`}C@J*%zCUd$;H_ zu@;*{KN)04)yT_V*UVgrtEW=77Fq&y4lnfoKXbGH#7lKI4HBK7IH~<}VEtFT{IA@n zx}=zxEbOeL-1I!W3_Z=v)NG>?(<1ARlftwNy)@l8V}lZvJUul%)%ffzwb~pL=OXBY zl@s{kaq5W|#yLb1M)8r!PjgQaS~7o$Q!rVHHZ__G&iT>daq$_&k*bp;@c$L%PZs&? zIew<1{&SH2*OXHG-f^`5-ylCqm^$!dCI<8q=Yfd+dxZToE*5}xjBW?y|9YmxC%I3d zG!PJV`cJRaf8Ft4pQ$7!qM#(&)Gd)(DZS9beZ5VxHIgZWhy-E?_Or`scpz=be7uQo zWJwe&Wnb%d=j2@QbS?|1m#~nM{`kgN-9LRlb#={10A}BFpXza02uE(dLzk{D<~i66 zLCQptuQ6zmPm=z14^i>;xYAV=1%kKf1saALtbQ+j4XF6(0s3`Ti1uws=QQ~?d6879 zVN~-$mp!0jvS%81Pb^a*<^_!1$wI#RZGO7*RaP9o!7(b%Tu~gw?qGJ>D4emk<#jr$ zl8yCd0ID+j4$(qv1`IZ~koj0Ik;?*K<~~i153`UP+V@p@QR|VxZo@{h+7j8}UTYLh z0H$vZh ziy#jC+tX&DLSwj0X?HAldtQFTRp<*n zz+js%ODHDQR1<_n}bBof5~)*p#QWc3N=&A4MX#;N!J9{d)kM0U!-;BOsF?* z=Y-QUdE~SifPHmp*gz1~t1ZGu>p(@jV&OZ8$3mi(&i41RO<^wRO!Dql++?nt7)x9Rex1k(l7Z{C}_w?X@40Hj@^ zZQ9i);XZnn3!bad^xO(dg>j7#$c-nUO1ot2*W6OZ$ei$c9QiaYO^Vp6S-D~gpf8&8L1I&wIuR1bSfi1*Lo6fr&5Eg_ z$oH*LWft$m5X~W8bj9qwhu%7X#fuIOK?e@zn4R%sbA~o`au08*on?EZY- zMB;~#UL#zD`)NDB2jWuxa)>TZLfIhT*9OJGNBJYltcO<=EXov^$J1IQ_rR-gM%Vi6kgR zt9F7^U>j3Aw9IrKwHj=lmm(mfM+&DoO0k^xY>C3`>vzMi_OAxB@H^5bX_N_WAk57h z?lF1zIAc)euj2T~@RM|bJK+l&ZQ_wa>J?fznqFB4dY(8pWP!Oy0KP-Tc&P0DzXrq7 zN^Y@Ai#eIgwv5R1YIX7&w5b9GqOY0W_StN7WA@bV`kcB>GmYk^P2`^`gGxso@N4Z{ z`J6eROvU+5VsHD*w4nmFK!@X;s@k*4Sy9Gse6m=Wb}*6zwKD}z2|pulyL42~ zT#>zVSlpqc{G5dWfca~o`y7t3x*FQ9OB zq&Wwemq(rO5e~&v+|CzbJz3O19lcb$F(fyH$IKCd)V|H;hYnJj6WB!p4r-`)NHP=S zIfUd0&BQ^--FeTFOHH#o2$W@n@A&L8x*T8UO2D1`8iEl2OqcSd6yazP2QPyzuY+We zN$V*4L!67{0E3cu2!Y}D);Ekir~4kh>`1FP&>q)VUSf~uX#2?6)aQ~%^Pe>Np+Lu^ zeYL!MW$`-*{{4jg>4GM;0~-x93qE+b$g8m<|u`MOV^1pErY-M|T^p&kOau0kQ9Lqfm+#2e!a|u}gsZ-hwe} zoq|hmat0kU@HB+WZ1%ppA@Pt5B_F2{>=7r7AENlOy&ILk&wbAKF2bdHb}@l>C=0_# zaC|%@Me2b7%^0Y6OXsPVR7jl!Vk!hrHaz=3;<;z8PvQRuPe8E09{sX>A?IB@rZ3rY3EN z03O_G^RkxUjD!~!g6=(ro+&2XyfX1ky8go*V!sbxjUv;Jqrj&ye!9dcPM#h_vvh@HDR;cP&`&;So9ykCS~u4zo=m)V-2PZXVOk zFH=7n+%HM{!i~jC)_Yx}DEBK}6p_d%xh4~g9ic4bIS(xxISoD42)9$D?hh=M$Z4A#J6UFWAZ_M)YP9+|r@C!*0CSZEj1a_o1yHbK#U} zuIPU&t+r|MkJPZ)200v?SIu~d)H5ia=(2k3uPS|Yc=d5zrNxh7Y4l#k11NDT4_vNuxF)tbWVfmTlQb>@v9H4aGVk0 zutT;e`yOV05;xCN4o+Lbn|bK(E7SPwX5ar=U_9x^8w5O2O|#@&bRF z%4R3!Yu9HeA*r8*M@d?#DNhK?2eOoe~m2joBK+weucudJRo0b zv}}89d4Oc;3(S=J^bW?q$=-jsZzGY6poG~zi$xAi)~6py_w;M1tzOf!#~d(DT5VDP+8Uyslc&aougJ4HErlNjC__)b{P z(@(wI_@_3WGTwjnvmx%!mW?UD1Cc#iU)pU!q2E+twPlaxM>m5Ucr)_4 zd#g5UvNluG`BX}c1H`O7*7`v2ODm>B7c_OUbuhjnsS$E@rtW@ zliK4#oEXqG{pU1O&` zz~FFf<<){8QP0x2yG4Hio^;r=I~e^|&;EmdSM{S)pUCEZ%u!!gu2T|vdeAH9EyKCc(!knz3dOmc~X}e zP=^Z{F`KmCdj8FS{iY6E)?U5Zhw2vT^uLv&Xpsw zY{QHZ-xmHK>MvREZSkjlW#fdx)`R1aC_$n+Aj?EtZ1Iee>=O?V^2*htee>IyZonOi z{j^wy_$gDKBi(=Yaq_wtJD*Dq0}|$4W2-5CzoVD}-PBVTh9_}B6>vaRP<`-*wnZTj z`l0iq->32DwU06T5-?!f=i%PPTn>=1B@_czKC&gDcfDaWhl5h|qXy63>;c*ig};~U zf79Ou;W-K+#k!s_1;}w4nK>T=_lYtym-09teROCl9OQpA8KTG?f`jkWIv$s%FFv~P z1UE*kwmf1j=q>q`nwww?V;bM)CQdEUlFT5ZeN1S=-M6~gU#>rmuj$zVO=7Gy*3in!e4rYCc30Y^Ot+*@|`979mJ}lP7<%?=>itq$vXGm{n4^xd<>~I}{q4rFg8qq;$dK zb~fOOf=L5 z7AoGHKmM&s`v}J^Q>1Hh|S7lHxVq|}Z8;6X#`vH#->4lv%(M#gVJ<>af zB+#F%;$2({wSSLGK)LTHRMoDr0 zzt?|#z%zvE3Sc4=Pg;B_Dol}E@#-FtOrJP&_in~7FZa49 z+&NC^YFJxKdPkA?yK8_T1?$>2({Ewgz6*_P)ubPzg?vH!pI~a zmXD#D&1W$A7ENB@zjn|5e;o1O{NMY&W54BqUvfWyzFa*cyz$K0CJwM-o{#moN4Ye_ z1JrNI!7gYC-(OF}vO~Xb^n7dQuULCc9dwW}52T4`og3RN;66r@zb_^QmMp6EwR3++ z`_4VA`+AegDEu!|{Iv-$j`GnD$}e+%IuWP29wnCS?onn&teAz($gSpZ)&){gbPz@E z=1Gc|t*!t4$MMQI^~1+YOJt7ckmlM{<&~1Ek1RLvk_^fQ>I2MSTejkPp{U3|Rm~Rp zssG~yy?eF6fM9NaqVER=UmpFT=`(+_m_rx39kEWRnag!uRlqF7T-zkkHwo~W|7pk` z=dt1OwWW(ja#(ZhNiJJ^r!Ev;^R@l$s=>VD$B?Y0+mL-z=RY*`ZM-A^FO;mh0nq)x zp;-NZ9$75MJqJ!X$~GEvTG@ura9|8~2>5NM`K6gZGVjxpsca&99N?p}Kf`|&cWjD- zhJd|yv}eNc)KfIxLvw~uGzNyu z>5QF2CR(o$j4I*gYvFITwGVB*8-R@oEA=o_Z1aGarD#+N=S7G|;bkIOHTQHg$MT6i zT++?J2>hFl>f`QXz45;1M)!Z=?E@w(PiYV1i!YDu>O^PEK<0a_&h3%5^%2_?EB-C@ zcv82lU>N)9S)}@+`)tJ>%`O}4mcJJlU(m7^20dxT1(K8xl1%E_g-XIg*w#6Tek*G{ z(a-J2B>!TcSTeW55iXJi!W(y;@pUJ~w|T@Nb(;f3e?7 z#*fUPNac8FXtn^6+gcl9?J#S-X1UC=G~9&gU>?0FR5rP@rAqHk{4aTkU-OI`5)Vgk z!1cAUF0oDZ*Bb3xM;RZi;U&vGSVZ-bSM%k*&D-~i?qBGq8h5uIguB=PXV$oOM?W8k zQJ0xRcnxU7q4x%0{I13P~K%<0tr+7?Bz_gv_QcADvVEauY(qq5$3XdO3~lt37_oS-;}zVkCe zX&KZPqDMrr&g*ibg}W`=_}_i5wr}SAYgIl72d;MJvKsd!7%ou9!emK ze_V`WMG0NrQQwoX&+YuKihOAY1jVtj*}^Cn5a>&9C^3IDA|QWW&V$I0)bYrN{SDJs ze;YvPe`(vB2b=OfuHyPjC6Ex0s7t^x<`n2`f;u*B>2=T<50sO&qmwz7Cb>>&KxB>u*JuR|9FH^=+j z(QcRPtxyL4HhRrf)iGieXO^_@4TGKqCuN*1wtYJ>_)5)#yxgLoH~Q&ozaLRvliHEo z^PO`UR0lnu@URL{lnFY!gbeV0*D1ov_@dZh2=T5Xez)-S24nuER#(B0gDmu@g}^Px ztvG+c3s1&UoCe(oC!lc}W=2#m8PIB5bogv)QM~1t;rsEIg3waW+|#~qM30Vy9gDuu z_<^`o;nA>!8D-(C$lO|7*QVSZSjAr{{L_&M&$orgoGR}bUjiaw4RF8~htscfU7xLFb4vZA zZSt?a$tkmwaVg=3-_ui!2vgT_!ceakFI2af*7!bljpyF&w{pR6^i#VIk710f z*Zq&%OamR-gk^rE3biGjZpwcEbRGsfm;h4>8jQ0j3$<}{hf+VR?I-zpu>7^lIcd`x z7;{XM!Z^234-0ex=#kQ$q0jaLKOqV}$gvhtsLJ(r1zK*w{&U z(bdz#(-RTjQq|wk-@AY9U%Q(N$caah(o!yHcHN&l4llXE?NI*_2Jj6uL%ysIRoI#7 zbGP8L_4d!BhOh1?duS!`$hYT$g!{A`Q~GR}-$BOvD5Lxit*!HY%y4g6j6y9Z7vlzkZXqrPg|z zk9$3$*`4i}lu3^U>B=m~YkVZHg7vZ#_N*DfD8^#;X$2z+zikCR`1z1N}R2@wuX(_h;BfQNI9 z+yvcqLPQ`{kwFn2%Vn;;(ClH$Z_#hZ0e+L;%Mx3>gl~VvBjWg*y&fKTC|?Mt&kRmfXNx|sv@?jPpx^KlNko7XnTK`U?@#0>DEU}KkFfTj`L3$WVWwPk|) zgK5ki_ALxOp@F~p{;L(Rl0Jr-FSW`9DiYda8U&t0^d2Aw^Uf_GjM}~UK*509{5kgR z4BL~L=C6MW1sU6rI%%{gqJnkBdQ6$GzOToJHLS#QuXC;fisURviMfMF=$n9e605im zr#|;zN3=0d=WG>WGmilfkD#V1PJnj|@hU$0X7Om!xjRxW^H7H!2BYr=@WgJ*YZJh4 z+ZRz}OzDG9M@6Pf3g74nP_`v%1o&5 zLQlh-(PAwyO;_tgTrC`!+140P9MG~YNRofg&HmwSfpJ@@-X^KzdU(4hL68|&f_!^$ zfJcAHn-)`FBNM9){%)!;paaOlx&_W?)v4J0<7Tqd7xeER|1;jle@cIM#lPQ+qTTxo zww{gQ(DJMUtgO%9(&CUldi{Q$?kQO>n^gb$!GGbDv@JI&P5sN>tG0mG#-wUd`H*Kf+>Ft z{ulD^pXHa1H$Z$lisim)C5hCu(-z#s8#od}Zj!de+ml7g%p+j#+J-0p*=GO5f2y;k z-*Tld4d})=9xFlJ&+aFQqdnMeVF!e9}&*IH%3}YUC z*++C@RI7OQavkJ(yeoZ6!QeIag~5L}j7N9cL+C-zVm;WhX_Wjc)jr|hFD?FmNOM17 zhxqC0@-Mq`ycWSiS!y80uWDhPWZU52$;PT1tKAO!;?8uYXr2$PyJcYzj{HpjKHk}I z42g8h@ifB#(1aY*6`^IUTl6r+xih%eqmzD#*ts=dwlaRS#E+3G8vDL)By4|Q(}xRm zyxtfWqI?$|xj{~5Zafs$Y z997=xH>yfecSU% z$M7DfsrG?b2cvY$qaL;$r5maYM_9-Xn0l#-rdX} z>^1`N^Hfk={j)}esdbKYPx9+g1qq}dSi#r5L30FkStaRqBPsNdn$>@V?qCY~M<~35 zn=kvruzoSHUI;ZN;|@WL5PQuB3iEWQQ<3Zxyo7E^XS66U44X>&VPyY%tMYjw?}w6e zCQ2N^@l6+0$FQiw!^8^8weikQaZqypd>9>Dm&5rMhC3X_K4QNWbIcl;g#G*g;RQIr zIB`Cr7Mo2`)Lm2QM%8~xakt8m4hU@7p%6+EAFsapBEwKjby)FCjg3HNI^fn%=M`C}I(q-f(z|%0&4`!{e z&w3p^yG!7o!IfXJkli@ih3{Sn|aW!bw;{%Gpo=G42 zPu2DtxG%oegKFfEoHOV#yB^7234fFyjT5_^eRE15R8}w_wX(S}5x8|2{t5g23_!c5 zt}a#WlS;dX)Y*R$%FPpF%9v`E^xiE#?2kdNR`2XRkJpcX?2(vT>K`TB$bb z=7&2YV45vpPvczXIU`j#(8y&FwZWc9b*ii)WrU?Fl;wa$v)hie$4cbRw3j@)+wXdh7xn-al#&vr^Omv|; zqe}qU?E-b#dJ7XI`V;w;n);4IW)Cz83f?h{nnrUN4@Xka+_l&r;r0?e!2B`o`{yBN z5=1`t_nCi4zSe^DwZ5~tmm>xYn&RYJA=ugciWS4rWUf$=Ipx0X6;ETIH#?BeBl|ty zp<26$rZAgh&sPZXi03EDG91e*Iz>M=A$(X$Qk))ot^KOod|qtJ*BQH@;c!QQ^HHXX z6Y8tNs!5jyGm`3}^KOG{*T*S4WXitSW(>m7&ntgo`95R!=;3724ZI#g%RDmyhy|tQ z%xmNbk{ncG4eRc_7GeBAciZ|!e<3`4oKMguiiso}}u0dnhiH^O?8Yx@&atoCZ4|_{C+w+i9<6;f^Aq&(v>1A(*HL zG4iC5!rbM;?y{dAsf8T%$;fK!;8n07q2Ws5+bYAqFN6*H`^T%zc0ru@A)OrzbnU&d z-#j*i#SS?~5_&Y!EUjkq*t4?tEa9RbY<7R6{`cZ}9@QUjX-J}`K(V&($y|^yUFt=Y zDDC-v1ot9wsu1#ut_C=JWLfM;l={wm-lKl4g{nGU0d^JX264R+A~BU0*)PE&deVcnu%*y9m)7;O^`#Hdlq1K1c|^O<|p42xL>DVqLs7n z(d6PxSDq*gNry+f;&^8Z6@~rhc2x43Ds;c@W;>*&B{82vgcM||Xdd>tlYjBA{eQCsx#pdG@SRuCv{loUS*dmelQBe|Yc zrIVds5fJ);{A&M+)Pmr?JDhIj=+_s0Dwj?n(~9WHEtJ z=imr27m()Ty!VJaKue>t$RRwSyDeh<*z0N<%6E24xt-$VXJI_XF)4*E7xSFwgi&TK zM?RA99w5s!zt6`_YT$1v?hE_d>mY+?%*K3BBv9#PSlc6(WMt8mlTjrERa~r#b5N9s zd~4;+-eBa%UjIIzH}!vCyibS3mQn>*@p$xf@UYFYj?fL+Y2E5!s$ak}#wjps%GkWFH>7lnLz_uw1* zdrA^rCB3ULy#;QE>U;#Q$T2zb(rw8SNEp^SFbK#h_st%A4herw{M73YS&1YllU|4r zJOTQnkrdg#tI**G^M?o(< zq=SRwQz{%x?RJspd%?r>DQ}F*M#lPtZ+@P7)42bkKOaBHll<#Q^T{zy6!9|mD??yZ zTGvf;;~)8T@D6`Y6)u>{X4sAA0cZ0d6oHUGz~9eZkZh2}=V=Q_!~n=~;M;P@%N%f9 z2ijwx(~8*_BiA43GdCg0pG@QZ8ihhfj=9t&eQpbX^e$uO&o=3Ugfm(9^my{-rN0xK z>^nSP+8}=#itmZU!lHw2l^hU3JkE0rG!H8qBN($7dFp>98Orklmr_{9{km=qy-nfk zjnR)cg8J-m3C>fX5~wz*_B|p@LTyl3<>3-Ixv-stXQC`ycUy%pioPk9w)Ds6^}Oe) zoR5!W88m)6DisPI7sVZs^oUF++7GXD0<#{>8x;@sG<`Z=$Q=yBB=&FgZ`%5{7s)js zar*KD7@A2!ETlr!0YJy8L4#9sgcX;iB*Ym~JbEbEd;=-@qbyGWI z3Ho@&5HeQ?I}CfP5qu&47hT9JcB=E&xW$Ayb?rt3i)eXBao(Wn%aPajSF8yFu)QEO z3yESqFn0R%rRcv{-uGqG6C8n$?&X$h(%QFoPXIkv*3PmCs5%iJYyLp2tb|B=aEC(B z&**<8-+s4z+3DcgyvfY{a#u%YEg#aNjQ7VC6$>)+ef&x`7*!?x!M2)bTKiU}Y#-?_ zH~ML{5DL`f{&gxBuVx0rL0LAxJEs-G+N*<2=%msdmjiiRZlaWW_f7u+e_JT|AY}Xd zDaEn+RN7NqsJ39pXfg4uQCOuvi(@C(_ik|ASpoWVm zwitRwrOSgl9s~uwP7pFvEv2o=QI&U_JizcDz?;l%zFq^#RLUGqv{hXtOG}=9M-=0w zpb0v+?~bEOTU@&bK2e|!C&mJ zYo=dkE*&q9yVn6mIx`2G&yH?Kw?Tiy?|paEjAXjj3UbCboq5~bKKSlA|AO0cO20gQ zmp8G9XmKL~rk1(w=0H!x9NY)PnwCW-_RIu`0a+^`{j5_`&}VEPr}4dMU-J~cc%bYa zLVCewdCgRcrDA%g8@B}0KE#=FLF9Gx6Bk969Rd>YXYdgE&sX1G!}j5UG>L!YCH#ex z&`dbY_tvkDBmw#H(uIQ%;6rNU1eX;Xm->m@W_f-8npx%Nb`2ZYO{3Gb~p=>FNV>_6{PZhubu6|2M-t2r<%DI;pw?rBnuX z19vzK|FDcV_x^n5=F1_a77L*q;Il@^u5tCC7<2FyszIoxsT^Lp+hqyb$%2n8y~7b0 z{9z@(6>Z`12G=@dQ{K4^C;gU?(U~>i;9$AgK!&Uod6R(yT$(=1?BIWU?(o$=eDuVZ zD2^mpx=MB_Ef*r3i=;;s^F}$jtEF<9mlv5cQ~0LRI}}8IGK_y*#m7cSr2!OkAp5w8g{(M)Hx5thM_+oRb+8{kk|A*>zQ9frJXqfc#Ae%pSX0Ek%-f}6Bb@x%Ilk*cv& zZM1%znr-yPv1l-N8A5->bT@s6Lc|YK`_e`bJj-&gyFa3K5>RXmd(A5@rhJ?H0A~n3 z0Bv&!tZUZ=dQFA$ZId0ydu{4d3u)gZ$?Iu!gepMQ*LC+&->7R!;;K zei^uV02oClDZfjoz>7>AYn4jc`J|;n&;L>FNl|P?BLifb9)IFq2`6>d3d; z;1j=pEsI7iqf(3>AFJ_<_voCHK!&O;4mw8?T zJ#9wyMFw+d87$}+nQG)kv(|y0sU{rte3#(u@W`hITE9-4XZk+9z61Dxa1#W5%W+t) zjhWKnFjSy( zog*?=t?r{_vpWJJ{?DVd{ZZxhm-Bygb5o9}TuL2$!i9nHtlH)$ln!`iUsLC?N%0BA z?D-xGyX}8-{qNh@;IEenj)h`c4Z^j{?J)`m9n2GgVkfid8@lbo>xkrO9}1oEJUUPO zBuRe>rjNe070UZV6Q{`=9vM}#API&Y8OBz?TA|c}%|LS|miUXfDG?ZgesHdT8~E;6 zaR|tewTD2Y0->{x?aj`k6Ei zJsM9$$LpkAn!#h6stYgorL4zWmeDCl5^qiq!GCfZzx2b$0qV>G_n;M%DuZMrk#n7t z)vF_}dc4Sa@ND&m1HtRe(Q$S#i2kH}{_sDHPua|z$->mQ;_P4{w&S2#+N>hcL7OTJ zlQVx}p3Ah>$>+gD=$-%P>2WV>n&M;s3ntF02Wa|xpK2`mK)8b2*Ta(3Dsr-2tek5_ zq)(W*e|o)lr}u{cwl5Ss@ObW>x-0S_mjPJ|=d(0mL3)ta@~o$Bcj1*T0nzpO*44YC zc%$c4TYj;eqj+DnyaffJ&ZaX?xdL_!6pep`ZT4X9J!o^^o-~?G_;eHBBIY~&`RL8B z)9fFgN=r4`Xvcsn;<6AP;5oT+EX}Ol-45OOC^8wf?=4vw=T~Y4>mdd)oPzr#(4Og8uR7e8ns=vspDW z;wgcrN4E^+>G^m9O8vn9bJX?ALt}r06{Kl@8CmNtA~FIEF$BDJ@T@amjoUWuT+ z?*l>cyp#|AFeqQ^Q=S`8XP+2XOUPG4TDV@l9ax8oZ)4@1YyHArYWi?L4%&0S`Yq-B z5A*9AK?O%(V^^QT(WT_X%PPung&P`b*IL1*`(-7Y>u|@G>hk*}0REZ!Jd1z!IsmGr zHq9QqsaRIHta;Lzg#if^Ltv8&F0eYL6DVNomMads_Y<@KfxfSOxuFv3?zVB#M5J2* zHRb7}7+OZP=u=@|;1&_I$HTJc=fO?#M{HB%Up71fB?t13y`hTgf*{ZYK+n%>F`t7~ zK}xnw7bL}7Uq|d`=^1+~68wJy{@wOorz zC{yu3$$%v+1wgJQvVukz`_8jraQ4~j{^aE#MK4%I! z>pInXw;wL9qOLbMgsz;`1yK)@?TVPs#}$8AWPcuX`mz;K7y@Ztk9s+t6CNaZsVU3n zR{Z|Bpw}$lTfTOz!oq)eUTy<_bPPW{I-iRs9n&xt_(rqHe?7pTKm&YrWl1$m)bCf=WW}c$bT=!zjiyBrcEm`@i<}Wa98&z z!d@P?aofDT$-t?w=b<;Ex^td(-`ueI)zgEGGZaK9z0r3; z6Dxy5FRWX}8n}r{4EhoN+wQ(R;ow1@eI{3rIc0IGB37GFmWZrkn4Bt5IrA48$U&!A zk>j74P|9uCz`M@@{{+fO23L3c|-JHSLPz(}g+a z@)=BkOBmH{k=V(3*gYF7)O6v>VA04hvwII)hG1*xg5RU+3{<& zQP|>#-dEVT*H=}Z>%i!O19cVr(myu}7NIh9F{)DsZMhiqcIf{v`p<2%*SgJ!5}F?} z(&F^fdOPGmdfs;gjEa}|aHE)tx|=zc^ZMeLvpJwj+cO5jYcw7)D#?ff8b*AbM_3Q?V6+o_( zT@#aB1jM>F*-b2V6#gCte?iOUxy}9dk~p+~2%t(sZhLnfggJ_52}846*`YSDU%j9a zZbHXIq*M>PEdYMzHeUph<>AaJYSapxeHeefwhOvSoeW-_VMPr0Zr?z)(E%zxJimDc z`LuZJXO%-_`f%b~7#K8$}` zj6h2cWAddp1-m?pOg-k~o;eQ!;+|AipsmYEG&hm?3Ha-ovDb>(h>?CftOB0VbZDz> z8eOG!LP7*G_t!W$#}~8;9__eEZ47-6p1+VEzRAagd&o;YXw`4SEjJ^Y)k8R^sH&=qpjrPH&_`F?*3;UD(f zwzt1u{DY@sxE`cUE6o1Gs@ei{U~*aApqjgQVmTlo&xSdl`S6AI6e==!|cL_tG!f zwIg=~^l?bPIglvkTww}2Sf*%LW(J9)2Tm)mhdDgU!*McckkE>=&kM_L^Pj}WA$UH^ z`1PytAWKjuhNRO`y7plmYW-~=ZSDvWt?Of$@SEE}eV-8_@eH5j2YY{BB;A-_-F@g` zxN#5T=;(BZxaEKMvN5@lDde|&q_wne?tm7>pbK@)4#PnBGxqC?0tda|4O2TceWJ4$ zT4KG%pjnfP6SsC-G|&RHBYQrOmSKnE$Op5E;>4fI(feH?e~lzuj6h@|TY@JnVJ>0n zxizxz3IL=+}Sv)J#hzsdPDOnbRstd+aDy zd<=;!oadPd&Pby-7Z}}9@8zE-^Ox#<6=hqhM)`wJ*Mp2cP7Z~}7mte4%SErCk~fFd zhQR~$a2Ra|WA9nwFJj++y?=<*F4EBD9`6oeMa64K2WV zo?ZA({B?gjdCS4?uP@FRC1vpP!3ER;yH?~NKd|qAp ze`~vvwMCU>JHOBJb1z6g1OWj7=^H0}fJi5;G~&}=_;*!pwWu1MGj!m}B%5e5B35i1 z)o+@l_quTMYD$#4rzXCn-HSfr$Fl{INNyxbr-y%-^T2hXrjs^4uGPKZ#z{@HBwgfv z`kp3)ovGZ4l$6$koZi>uP;ypuo3q_R0s#~iCENfN&*)vpTtjOsmq2>xOv;t_U(28l&$j5nKbO8{3o*&HO$|ZLW_s>l4CdA9j6a@C|9+ zO+$Z~P2K~!6FQ|Vo}bJMX|apcM>jU=7R>eVD)@rDFgU7b?U7_TGve;Rgbe5FwZ|*g zO_isFpY2Q1tyRk3&2l#+-z^(ocWAVtO0;*9Y#iacrUZf%mLTBM1l>?^#$O{%rex{b z=rOqRIB%JbE8_27!&B_(#nQ|V&y4Qb=@EaS$OGCB z-f(|oh8k!fq5)V#!qR0VsR>U`QF3E80o)~q$>%KFSbr3E4zH~dA8_N4{uaxIp(J!7}7I@&w`wg~5&E*#wsllC;9)4)@C;2J1WCyHdp;o5>VN zaPP8X7sc`Uu)n9JIHS*LCEMDNq)m{#6Iq;m)}*;(n81}!C8wubB9Jk{nD=n0S%7^V zIG((nKN!*4!*&bs+gW-wbcAZup%Z_@n7b=%o#m?pC0&bOJilQsM}9)Wi>e3z z?yLZATL`ZSI4tgIj6~q*BWRQm_H5>uWngylg_1b5In#ezz`nldBKbtHm=^jEAWu)7 zWT(X-2Wpv7DJR^BoYz8!>yCrI98qZN#bg04-|_&b%cz`)qoGd)|YJS0T_|)Fw{}m*XBs>gi!7 z+DH@LN^`k|*t{j&$Y=3fo|I&d6~hJ49KbK3x^O3H7wFi4%|K&C)?R;n?ueB~ZR3l( z;mu?OuS#I}0jmeFCF>DvqK@1s{(UHW=5Y<`Znq{{Lf4)yz7TB72>a{|W6G9Av^9kH z>D+32wappHYZldRK?T~{7r>b^aa@!@aR1s7$cD_kM_v4u=jhe39S&qamPtd4jeQHXKUOBf(#&UwmsZ%ld4E`P*Gw{?QMp-pSEE^y0B_=bF~x`@w8 z^*nR}Qe-}gG>HeFa?k`G@a=JNO)o|KZf58mGSO?=4!R-X?(^azygHp`b!LVWgD^4A zg+*YqRGvY}m1utx>LyzxUB~{RxXXuqQLK@{&oJ?d`PrqQI&2?oxlj_*Q7(8}Ti? z^GHmoeqMtx4!u4Sd*-r7wZcUO+hT(;YZ*%vQ^d2mVhsvxAa+3%e^jCFt>)oLx#kEV zHfkDn!gqfzn2wB%ML{3}a|0LHMzUENySgm+O82goLsQ z&MimABJ1jLLB2k%j~}4F6>laaz$yVH830XO>cW3(_QTWSf}69Lr??3if+y$F*>JjE zUR!N{1loU~zFCKtFi+5ZdNz0PF(98L!=K#ir8gRNvaXjzcB`CFPW5dDre!seO~6mm zSm0nDcDB_P&0~_=XK9}h4xhv0#46a;FB;qCK%Jf1bn zHEEF7G~ZHJI}yR4KjMCtgW2K^3C1THum^vSy8DGQR)<^``$15VhZuPQf|8bY()O^t zwyOAWVqcLLyK5p?Z+?R0lYmV$p8H-SL z@WrZbS}`Xzks`SHpkF(JB?ik%TWfzzIS}7{FY?E;(J%1di|?XgG0cq4dAJ8J(O}@b zET}vYKpQA%-%N{lUGY#4VHb(|NV>$WoVA%B3~sOTztBzn<_6F))?%okleeJGiUdggyo~&YI5~ekTTV&q zpGx2Dvjo`5%MJf5R{aUTnV$v9RaL`B-c+uxe)ZPQZ_DgYYGH*~>7GhJYI6X2>?VR* zeAbU>&rjrQww&EJn{Jeted3EH58b5PvRKdOB!jS04^QclQ3snfLyssgo%}_hIQa+E zTin!dgkEUqw{4>H_D#YEslR_v9{r?K;J}v_N?k>}_jd~D8n7BKdh?S`^(XGSxMfg% zIPZZor>@>BCq%TWyt&&q_)fqsqis%>f#Q+d@>pH_dP2HsX#e;n~vPyQ_)-(K?Qok-I=U(wfxvLnXq14s=>VR&@v^msDQ zbC^A}BE&6H{-(6w9`a{m>3?Slf1%&sD(u5W1cDRojyQ`OUAWJw5}76iNIM?Ll*b4W zQsOAGd{h@d4Z*N&h4u@4^{&=s`fq>4zvGla5q~7hRN;TKhchrpR9gk{e9=>R8hE)} z4pinlC=0Ulbk0WmM6kcp>i%VIT?sr@TNl6PG4pUa;--=a&3g4p^B|H+DH0Vzp+SYh zO|wX;wgyw(Cdp8gq|m5A8i+<|)TrUT21VaGd+&2!=lag?yPwxt|GoBFYp-eVb6khI zYi+bY+fr9+8@pNg-0?rG*@a3vwFb@tPc4Uzi%#azNxJ>)m!j(hPY+8-`oev zUUz0Xu5>!l_v*0)`~ON+HC&@N-nzQ`!c@K9@#&*Adq#}+Us>eEOo|=e^wnr(eAq6R zL;uv@N-j~o`Yb+osnvIDgL!V<26imJQepIZ&e7VawEVA@{l|rS9a?!lR{3%I-9y>Y z1tIy@HYgZAA9}B`dx>AaIL!~YnzVLKH|RKU#-*z!s%`#H6YMP>pYU|^KDT*h`N@P& z?x#f?f+ncbiCI z?G0hr_dfbf#Ww|oCAVJ9{#5u|WFG7LJHXcLf|dAQm(CA{F8O!Egj+$1bNz3oWeyMW z8(KbN^V@a%?7#NiH2lHICo12gy%PJp%%5gpIs41=?0Ju~c4ciDXR9un6FvCdW52JS zk+I_GTHh}2RLLqctEhbzRi!&pkR37B`eV(MmA>VjOJ~>5)}D7ce?eZf#o%x2Cy%wi zFvj+dd>b;HzU5lx=1ER7uV1lQ9^G4SV3&ap=KD-i+Tyvbe9-8%Zmq9JmfwB6Z(i89 zC&IC(e`po$2>#_i8Tp;4$sR7JaQ{lc<#5fD-3kxJ#1_`?TOn}QbavCceLFxUV@tq= zdWTJ--;2!ZMjrk4(Z6p|UGQTJ*r^yK?%f zlWjV6DH<(xnK9EnqLj;6#TQh+a571xg9xn-MXHP)iUe!X&&DW7G}f_J)N=tlwsla zzMCKJ$-TbIV8qUvyz;3n51k6vB|kpWM?7Ln@Wq}MZjxOGTo;`g*H~2K+bufX>xagr zwn)kM3rm*o*d8NqpR4g?T41v7nU+hohR+W!ihDSBh3`|1XKz)a)yMbASQAr_m1S4l z9N^ZrIn>P|!{YMrA%Ex9dNdrKX&U=7Ve7LVF1eZtDhbE`NtI6z>t&*9^6_oPFQ_eN zLt{Mc9rCOFgtH}w-b`1_8Rzk6b@3%Pg_GaXY=b-Qe_HKna$w7yt$9_|b@y7w1)e?I z_UgCX#PM?S1_}o7U0SMw0Qe%YqUA|tU*TVKH7;d@U0H0{ zx4gPiz)e+<#de0fEYvNL5n8a&af?5lu7$6B%vKTz3^|_gQzF(xcg8SX6j>1ZIfj9^ z1mP>e*s%^P8AB0!dvbT*21j5h2jkf<{o*c(xJ?s{>hvIS{d8R%Gx;~ zB|K2n&I~t}z3Dp-SgnQ+h%CJ%;^T{uyDMXWzB)6;L^|E^j8H)$o{Acs8N-y5OLW_D zdv!9obS((#ClUxcNqMn?mPZqATgNjD;Z^|LK>sys388U3V?qdRgrIql5bRwTbG!~p z1K`1{3OE8RmnDyRh<6rY zXi9#8VoKWy^>(UF%`_Nu2P``X{=69bm89Gy5&vDvj_i*4TUezz=0QB{1S9Ze9D9XD z8KOK_&Hy)Dv5byYM8-B(#*_$eHUSe_?h%P*J2~3yeEjT1U`I2-+4kt4S1@J zaMz1ihWW7-J1~ey~{eP!;k4_!|6AdV!uV33QMf zV}PN;1e51iASzinAL`MJp($k_RZ+4Vqe&OV4eRCgneeG~V@xog?$ef@(11VpW@RN1 zbdyfVdd;tI1lnyPV}_y1yKW3`f$0(<#`#maD4<)D7!$N&B9*RYo87%vkZ*QEcH`?D zuSuZqJIHCEhqI`S9>Y0PC$LdGEV76vMYbNno4C`IR95-($um&=Ab1k{r{=gzB33gd z&_H*F{DC?YXB(4g^~z4L+NvtF`F_>5$sJupKL`oZ=pUfDN^>v$!RlVm)2&*SkReMH!yVL~o_XY3k%;RA-n?P@}BXJFQ zM|ije5<;gb)CPAVI@k}A69{HNaPfWQawu~5V9e3tDO{dPnu6sR6HW+~Q@FKgAcRSa z3BkmJfioj0F&pLuC5!|mXMtCB;R&b{!9sguRKb_?c@D7ZU18Pus2wW^L#ebQoW|b} z0VCEq_-?8ai;|}@Mkp)}eDD2I>U%RyhHp*P7YH^u$y#|X#b1w;(`=Uos)qI$y%sEL znF3PrZmD}3WK(|7&Yn&1g-;Rm&>sX z#Z6-v@gnfo^jD)|t$QNMxEfz`y7&rR%lLj?`pa;y(MKRL+m@(AP+3I;4V4o;0V`1l=9>8XRW| zWMw|r?#%=m_fbx@oq5~#`d({UJ`MmIXk)w zT0euv#%7zqJP|-uAn?0OfozQKu_n+bGpNy`uUNMY0ton-OgJm_Mt)w5F|vXG!)|k} zA!N`Ph`3MS^58krOCnx@p$-`J)Ld6H1)S>`*onUi@_h-z%U(3i@7h16awe?82@n-B zz4VfZYX{M}2g~asYj5g@4d`&yIS@b@{DI$^EOjFI5N~RMXZ`p58UiqRs3Nvf#;gSv z4XFvS)w2Y8P zSt;_y=&cW9jIoA?>i4ul#vkV{%Ql6>4xGhhRrgtpP)Q=bmO#0znZ+0oE2j}k+!jo+ z#I+Xl|2Mlzt`6MbF31e|OL|GfnW^%qVwZduWarDR)nH%Dzj!a@|3MLeHNnSc?=5e@%G$$1@bkdwa*EaPfi@Tc&1?0WLyFe0*c7Um*CSe$+QV^uPLWC?uZs;Pd>; z!1%u^_Rq#~*v~EKN#D)lx7;@BKL^ZzbeXElU1ZM?nX5 z=iIHg(EME>h&dDdh#%(mJyCc7V~b7A6#kmi3|++cF|tm1vk!|hK-B>ZgQ0oVy1V;8 zV{AN1mSNi%0yPh$bs(iatf&kU-1P+6k}=<#K!XBlqDxA;Wj+eTv;k{^FH0|pI7m#O z8LJi4b!1l<-3r8B+O(QZy&iObIGHmI8;Fo!US=pDu~5+zdCsF*>IHM{@Nbx846L|F zIvKm#HklNgJ&(&$6@*J*7Ns;x6jab}j@5SUmGjM@`&0ChEg;AK7@*hA1=v|(c6>R| zpX?8b*tir=)D_JtQ_w)=L7X->gRnLM967fPZs=Mt?ImVgCSS8uAoh6}476^~1f4YES#{NZAGKd+*e{Pq6O3FkEJ+x+1%1-O!s5#t=`l>hhhh zbD(UBAp!IA#~3Y&9W$S5IP~1Y@7o}+_k_g3=ljzD#m#31lhCS|k4^fpiz3RhRy0CO zrZ7f?qO|~1n68-81-UKYmSGm5^s%L5s_hk(Q7VwJkP3^2pF^;&Vi1&HREFE(0fy*N zPsR3!1X}+_-phqNH4ADRKTZvMBArkgrzZ+G6f6eLa!_IU{aRaJ7G;Jq1}JK`^R@Z< zgL6}1^*+OgU%iS4QK2eBX=uE%7b>G(iGNetH|zXv`PNeKZ;x4h`j*FuGZ|#N0I20;6sL1M#7T z&MZ`jG{ULF{yo?K^(}yXheW~`-`9nOlA5VH$o5DYxb;-XdHf>0&>ba*Go(Y^SL!gV@}GDSURDyp_mO1cZN?>w79^*-yPsEWofq#h!-X@KW5Si|`+H-DwO z`_Tc33#rTZzWXkHP6v?FPS%^o%%j-)g^UsQJo`^`-mCwl0;_G0gObkLp#3<99@=K1mk?5 z(C~djltafIUQ7#8sQO~vp#UAE4|zfkYC2?Z=>^-s z`vhMc!RQf2HwdHq9|@ri{)tVIWh|HJ4~d`-{Eu&3{ci;7yp*aJzV7e^OPJ>n{Mimz zLg^(D7bz(r-3Ue>p`}bm%(g?e;+0uo3N;7;XoIDfL@YE%HA^As;uD=#cwPYKyg6Nv z$uh|KjC@ODS##l+;o9a*kyjA^T=?*g~h)mB&N=CdD4Uqoj#^qBzY$qp;$A>oBdAGq%r1h#S!wstlL%kTzo$X?< z{uhoD!mJo7RLRlhGk(CX-LV~yFO>v(726_!hs#$<#zoKok@sQd32)+^OJE?cw_ZzzI9-mSz1#pT!%a25j)FhrSt*1H8w{im`eW!S0x@*1pkN?HsB1rWAO` z$jPkoEN|o+$E|W=9K_Ws6Loe{5xN@3g~@&Peg{tt_P!8liy0m4Zg;jNtEG(<2(GHg za=q6@lL&rhfTjxa7SnQ9`7y-p5S)CS_m-7DZvh>UE~Zx5zAe<(st`mDkY%2-j$+@4 zxdbM+3IzN-r~{d_&nnJGCb0Lr1Z$D|ajR%2;CZJ-eIQu-#1Yx5bi_fMU?+~yR>d1i z;{T1Su(MgrKn?NmRLUY$n;b_Is$D#$#&{8`PdxQ6=Yb1C>>)oGRmjfsl|r+D8+Ju@ z-64wb#g9Xu4J=n0hnlPLOglFqxiwH+2Cb$u8G8N99SOb7Y^cfnr@qY_S*RguPS;jO ziL1FKO(TqglL+<3YI>rVs&IH(COG4=Ua|#r_GT7pg{3{9_dW9CUvNg=OHQV&57>>i ztbwxZwT8B_W@d4dJAq`=A$a&zeReGirB%Ir4OZ-IJyEQA4fojZV-aK?{d?s0;I%Z! znA+#-n9 z<@{!^fDf2 zjLIH>xK}4Q^i%f7P_Q80T4Xe^<}mtGRF(Jft6prB`p+BVTv3p8^Bdp7HFlZ;KZ0yo! zHBs!)B<@rjH*3c4@qJ4)U?p9ULE|LGiLeV!Y3Dwk1Z#>OC((SZwyj`R6U?(4wB$?O z`5T?D$7WhLj;1Dmssg@=-DI&Y-wAeyww^jVl}&dHb*AG5UqF|G;A0}5E;HnjTrxbE zxW1X@=*nwtm)-!x55(X@6I56z*-1GW>l9su27ewDIet@dT$M!`qkv@2!g0ws>y?NI zA=nWlJCn@COBJDR>Wry7wooywzk2Ow_tI9-vUTD~e8GzF$@kS)Lmpe`5wxEqbmRsI zAH$8ZuK&`G;&(zrpUL4bh1ey11Qp*y*5<33J(=KV1nX(E&pC2iu`=}@1k`sc_h`VC z5RyF!A#N)zT5We?d&X=oT64;#8Mq7B8c&ckZEjr%(njg1MjK_gM7AgP87Nwi}q81dTnPcQOdsBt^f9~b(fGdr7?SqTIl39?%_YfZv%`VOuM3*UifdfuB5GREn* zubY|c{&Kwyvqr$Iz*c%m!~$nBOdc_Mc

    ^2TP_ws~bFCRzM?n0-cJubqPgy>f?cg z5E`T3{_*tU9eRD_!KJgojrjYpz)(7MD)&l}C>8T7T1W`t>;I1`j`d5g6hNX;Hj`E5 zNGut4ES0K~l08Y_0$LA;%`JcBGB*(D#{>GRNNXpzrS7y7&)+XuO4ULOc5+L)l2AkU zuvBA|zmq-sdRVv-7BUW>X#>2yu$$?JQIA3v3H<>DDNM$1w9QzQ1uERlrJJhV*m%X15DwqX zg|zo@(pc=l)ZlJp*l8dj`yn83xD*mcAl24VNLMU`dgq#FfuMT@IIjo<;Vy}IRNozG zH2rs7vLr?UbU6+meyw^l2tX!i&m@EPuclO5Uw^$0`u{qJZ@v}m2g4{KYM5-Giz?G- z@$^iJx_%U%ztn}uO48zFfZW#@7$J>xTJjgS^_O=C=j#RekiWBdF@s{Kr*mgwVd;3S z!u%-3VmTP^a5|lKPg}X%bkK`^+|N&yofqH%#^`-IO?~$3{}E&Xt_n2bZ$ znPP)8X~&kiOnK>hNI<*6q4?4jZ=u+tOwOThWMWwwiz!7ei|bp>vM{B-gi_pqg8R3e zaXb8XgVCQtuIBe|2@feYEsN`1O9_{G&nTrii_=weFXpe%L@7>t>HcSSthV-D@IMpK z)spi=_zA~ly2x)IxY?$?v~0K;m%bSYUfT@0l<$yDdISp3sc0Qz9}Phrn`24{cKhgb zC&rZ9^nPb`Nbc;2=8qR z*yap!w)B#S_5MQc`(ckg#95rUCC2I z@PU6~n;B5y;EWJUgGG#zU}nM-EZRUVr@H(g=n)2vhQ>oBo=Gb|bE$c^J>M z@C}*CT+XO{%W$dJ(J8awNkFx$?5xqB$gtIixm1ur_!$b2dk&n;+&E18EZ_Tj-Y-C$ zED#4Ww)B#SyUP)%Q8s66hioj(@vRjy$~_STjDi(~=#X9#@wZL{y7dU{W#)v>xpD~H zG0s}nyr()7XdQ4R(>0UnQhN}BIT!}7=JOGXm0qx#W#Byg>0pO&ym`8o-Ne7it?7KY|p{M1?O=B&EzB5uq4S8HA)l3-a+$U7?eCqMR zkHZWS!6{RqSn&1g_kchH@@Z;l5WG$u4vugg=H#c?Hpn7u9A*Dwq=C-lb8}YZW38ef zKd{ta`820KaV=eI1GYQ}^@RW2@5g%r9aq5R7~cZSWBM0D*jzvpN0eTj#RoXhD(o*C zsU5!)Xng?}sh3PP;oxk;S`i%K94ZimL2C@(&soH zZZLrGEuZjz#L>EBxN0%vgulTj`2Nsy2pRTpim@tse4JaG4}|G`CqnI5NbNeP^2hQY zu)w#Vdh;3G7)hW3gJ2RGg#~iEMq>U=AED^c59Hrf>9tw3mow9 zo%z=(mJvCF*Ix6l?GkV`Mnl%D%gTsk15Z-PocgupP6FK=AtL#bSzaK}8~2UX(8iOr zQ>t$m(A@z%=!B1~7v)?c_$w#r;#S)1f946Yvagl%9nRnZftG(VR!3&VoYFmtvC{jW z5UN)(wg1@xfA7nI7PTTsR-M`x1iG)7s`E+xz{Fbc%r~GtU!CG#1p0>Kdcjg#Y$JkP z1bsdqda;c_d!M4a+s2yYPqW#W-7PCXHwz)E=qEI7cLYAL^PRFb2oD>`8kR7UyWsb? zhTtLkXn}`Nr`@=Ivs)+4h5C83SayNihSPC+CG@QI-Q29CDp0fnG6O$)QbQ=#vxG}+ zVI_FKuqT{SGD_%v$1kPG#SILQwOF>3VHQ&Cs}j0*DcD%7I~nS3b!S=W_9swm@6%Me z1KC|$jbSM^!H3_M+3ygdCl`bU=wXgf{lBJK{%lgvCRpYmPuVbwLn!Yw*Hk|`jTIT4 ziLP=*h zw@*KV6*_hkQ%q3J87`H7Ae5Iilv30z)M=-0z*wFlhHDl{=fgXB##ka-y=nSB{1|9VHszGV!9^-CJL``}QpFZ$bD=*mR){8+!y6Q;<@D4wY=TYH5?gphH%+#4{i25! zR=}$RZDou)npV!ac4#@y8`(n$HM5-CBcCCJnz4k?T+Wq@BW3$yAUM~^&i> z+G3)H?9Xx59&!#-JrNNh{2XI}9n&WMwP6uByf^`8{U)YCG2t5Q9%31_wxuz@CGAlfmntNf){Lz2G9Ae2_fS ztprcwSn8l+AmHU-SwX?X7vH>Z^~s>$6HV2S_9f~;-rG!atU5tjTPJ(;(Z}Eqyqklm zG77mwmxJZbO#&~hNH2+aS3l_>eN@g(^0;`;@v$&~eO;fAfA=5W9NAu`{5v$*E=_>t zZh%rgP|BS(&gubw@Q=GpeemVRoxAG62jijKbm8Fxomf1BVrQ8C@oRYRvY%`3f}KcL zXu7R)JwJz@LCp&DX8-WP%U7Lf0}! zSU*Pra#<^E?f6n$^d!(ni%rQ}pP02|>(9#vA$ypNkgWqM;RLU9m2MB57M?754JjlB z;Ly!VFNyfwS}9Z!P2FqS{+Gp%>3lo!8?4<7>4)!i)nxd@(iZ|L3dq>|9RA@;@B> BiE01< diff --git a/Misc/NEWS.d/next/Library/2024-11-13-20-03-18.gh-issue-126188.RJLKk-.rst b/Misc/NEWS.d/next/Library/2024-11-13-20-03-18.gh-issue-126188.RJLKk-.rst new file mode 100644 index 00000000000000..bb13662e6ae62c --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-11-13-20-03-18.gh-issue-126188.RJLKk-.rst @@ -0,0 +1 @@ +Update bundled pip to 24.3.1 From 73dedbd22345e5546ac0d201280588f5274b7085 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Wed, 13 Nov 2024 19:02:09 -0500 Subject: [PATCH 200/269] [3.12] Docs: re-create pages for removed modules to document their removal, based on GH-126622 (#126781) [3.12] Docs: re-create pages for removed modules to document their removal, based on #126622 --- Doc/library/asynchat.rst | 17 +++++++++++++++++ Doc/library/asyncore.rst | 17 +++++++++++++++++ Doc/library/distutils.rst | 17 +++++++++++++++++ Doc/library/imp.rst | 18 ++++++++++++++++++ Doc/library/index.rst | 1 + Doc/library/removed.rst | 20 ++++++++++++++++++++ Doc/library/smtpd.rst | 18 ++++++++++++++++++ Doc/whatsnew/3.12.rst | 6 ++++++ 8 files changed, 114 insertions(+) create mode 100644 Doc/library/asynchat.rst create mode 100644 Doc/library/asyncore.rst create mode 100644 Doc/library/distutils.rst create mode 100644 Doc/library/imp.rst create mode 100644 Doc/library/removed.rst create mode 100644 Doc/library/smtpd.rst diff --git a/Doc/library/asynchat.rst b/Doc/library/asynchat.rst new file mode 100644 index 00000000000000..5e5c3a99fe66f1 --- /dev/null +++ b/Doc/library/asynchat.rst @@ -0,0 +1,17 @@ +:mod:`!asynchat` --- Asynchronous socket command/response handler +================================================================= + +.. module:: asynchat + :synopsis: Removed in 3.12. + :deprecated: + +.. deprecated-removed:: 3.6 3.12 + +This module is no longer part of the Python standard library. +It was :ref:`removed in Python 3.12 ` after +being deprecated in Python 3.6. The removal was decided in :pep:`594`. + +Applications should use the :mod:`asyncio` module instead. + +The last version of Python that provided the :mod:`!asynchat` module was +`Python 3.11 `_. diff --git a/Doc/library/asyncore.rst b/Doc/library/asyncore.rst new file mode 100644 index 00000000000000..22c9881c3cca36 --- /dev/null +++ b/Doc/library/asyncore.rst @@ -0,0 +1,17 @@ +:mod:`!asyncore` --- Asynchronous socket handler +================================================ + +.. module:: asyncore + :synopsis: Removed in 3.12. + :deprecated: + +.. deprecated-removed:: 3.6 3.12 + +This module is no longer part of the Python standard library. +It was :ref:`removed in Python 3.12 ` after +being deprecated in Python 3.6. The removal was decided in :pep:`594`. + +Applications should use the :mod:`asyncio` module instead. + +The last version of Python that provided the :mod:`!asyncore` module was +`Python 3.11 `_. diff --git a/Doc/library/distutils.rst b/Doc/library/distutils.rst new file mode 100644 index 00000000000000..af63e035bf3c4a --- /dev/null +++ b/Doc/library/distutils.rst @@ -0,0 +1,17 @@ +:mod:`!distutils` --- Building and installing Python modules +============================================================ + +.. module:: distutils + :synopsis: Removed in 3.12. + :deprecated: + +.. deprecated-removed:: 3.10 3.12 + +This module is no longer part of the Python standard library. +It was :ref:`removed in Python 3.12 ` after +being deprecated in Python 3.10. The removal was decided in :pep:`632`, +which has `migration advice +`_. + +The last version of Python that provided the :mod:`!distutils` module was +`Python 3.11 `_. diff --git a/Doc/library/imp.rst b/Doc/library/imp.rst new file mode 100644 index 00000000000000..3dc4c568b1ae2f --- /dev/null +++ b/Doc/library/imp.rst @@ -0,0 +1,18 @@ +:mod:`!imp` --- Access the import internals +=========================================== + +.. module:: imp + :synopsis: Removed in 3.12. + :deprecated: + +.. deprecated-removed:: 3.4 3.12 + +This module is no longer part of the Python standard library. +It was :ref:`removed in Python 3.12 ` after +being deprecated in Python 3.4. + +The :ref:`removal notice ` includes guidance for +migrating code from :mod:`!imp` to :mod:`importlib`. + +The last version of Python that provided the :mod:`!imp` module was +`Python 3.11 `_. diff --git a/Doc/library/index.rst b/Doc/library/index.rst index 0b348ae6f5c8c0..951fbcf13fbb13 100644 --- a/Doc/library/index.rst +++ b/Doc/library/index.rst @@ -75,4 +75,5 @@ the `Python Package Index `_. unix.rst cmdline.rst superseded.rst + removed.rst security_warnings.rst diff --git a/Doc/library/removed.rst b/Doc/library/removed.rst new file mode 100644 index 00000000000000..4aafb0882c0a03 --- /dev/null +++ b/Doc/library/removed.rst @@ -0,0 +1,20 @@ +:tocdepth: 1 + +.. _removed: + +*************** +Removed Modules +*************** + +The modules described in this chapter have been removed from the Python +standard library. They are documented here to help people find replacements. + + +.. toctree:: + :maxdepth: 1 + + asynchat.rst + asyncore.rst + distutils.rst + imp.rst + smtpd.rst diff --git a/Doc/library/smtpd.rst b/Doc/library/smtpd.rst new file mode 100644 index 00000000000000..c704f4a241b469 --- /dev/null +++ b/Doc/library/smtpd.rst @@ -0,0 +1,18 @@ +:mod:`!smtpd` --- SMTP Server +============================= + +.. module:: smtpd + :synopsis: Removed in 3.12. + :deprecated: + +.. deprecated-removed:: 3.6 3.12 + +This module is no longer part of the Python standard library. +It was :ref:`removed in Python 3.12 ` after +being deprecated in Python 3.6. The removal was decided in :pep:`594`. + +A possible replacement is the third-party :pypi:`aiosmtpd` library. This +library is not maintained or supported by the Python core team. + +The last version of Python that provided the :mod:`!smtpd` module was +`Python 3.11 `_. diff --git a/Doc/whatsnew/3.12.rst b/Doc/whatsnew/3.12.rst index e9b5499155dd1b..5cfbe12ab1f666 100644 --- a/Doc/whatsnew/3.12.rst +++ b/Doc/whatsnew/3.12.rst @@ -1352,6 +1352,8 @@ Deprecated .. include:: ../deprecations/pending-removal-in-future.rst +.. _whatsnew312-removed: + Removed ======= @@ -1377,6 +1379,8 @@ configparser * :class:`configparser.ConfigParser` no longer has a ``readfp`` method. Use :meth:`~configparser.ConfigParser.read_file` instead. +.. _whatsnew312-removed-distutils: + distutils --------- @@ -1458,6 +1462,8 @@ importlib * ``importlib.abc.Finder``, ``pkgutil.ImpImporter``, and ``pkgutil.ImpLoader`` have been removed. (Contributed by Barry Warsaw in :gh:`98040`.) +.. _whatsnew312-removed-imp: + imp --- From 49e212a9297a2730ab7d47132fe004357c9f6a0c Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Thu, 14 Nov 2024 06:06:14 +0100 Subject: [PATCH 201/269] [3.12] gh-124448: Update bundled Tcl/Tk in macOS installer to 8.6.15. (GH-125800) (cherry picked from commit fc9e6bf53d1c9ce2b5f802864e0da265a77c111f) Co-authored-by: Ned Deily --- Mac/BuildScript/build-installer.py | 6 +++--- .../macOS/2024-09-24-10-48-46.gh-issue-124448.bFMrS6.rst | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) create mode 100644 Misc/NEWS.d/next/macOS/2024-09-24-10-48-46.gh-issue-124448.bFMrS6.rst diff --git a/Mac/BuildScript/build-installer.py b/Mac/BuildScript/build-installer.py index 9e1e180142d764..90f2b685c84da6 100755 --- a/Mac/BuildScript/build-installer.py +++ b/Mac/BuildScript/build-installer.py @@ -264,10 +264,10 @@ def library_recipes(): tk_patches = ['backport_gh71383_fix.patch', 'tk868_on_10_8_10_9.patch', 'backport_gh110950_fix.patch'] else: - tcl_tk_ver='8.6.14' - tcl_checksum='5880225babf7954c58d4fb0f5cf6279104ce1cd6aa9b71e9a6322540e1c4de66' + tcl_tk_ver='8.6.15' + tcl_checksum='861e159753f2e2fbd6ec1484103715b0be56be3357522b858d3cbb5f893ffef1' - tk_checksum='8ffdb720f47a6ca6107eac2dd877e30b0ef7fac14f3a84ebbd0b3612cee41a94' + tk_checksum='550969f35379f952b3020f3ab7b9dd5bfd11c1ef7c9b7c6a75f5c49aca793fec' tk_patches = [] diff --git a/Misc/NEWS.d/next/macOS/2024-09-24-10-48-46.gh-issue-124448.bFMrS6.rst b/Misc/NEWS.d/next/macOS/2024-09-24-10-48-46.gh-issue-124448.bFMrS6.rst new file mode 100644 index 00000000000000..6d57aa1ee190d6 --- /dev/null +++ b/Misc/NEWS.d/next/macOS/2024-09-24-10-48-46.gh-issue-124448.bFMrS6.rst @@ -0,0 +1 @@ +Update bundled Tcl/Tk in macOS installer to 8.6.15. From 3ed99b3eb3fe75c9562c95f5322c6e7f130ab266 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Thu, 14 Nov 2024 06:08:18 +0100 Subject: [PATCH 202/269] [3.12] gh-126731: Update outdated project information in `pprint.pp` doc (GH-126732) (#126819) gh-126731: Update outdated project information in `pprint.pp` doc (GH-126732) (cherry picked from commit 6a93a1adbb56a64ec6d20e8aab911439998502c9) Co-authored-by: Wulian --- Doc/library/pprint.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/pprint.rst b/Doc/library/pprint.rst index 1b3498e51f766d..2985f31bacb47a 100644 --- a/Doc/library/pprint.rst +++ b/Doc/library/pprint.rst @@ -267,7 +267,7 @@ let's fetch information about a project from `PyPI `_:: >>> import json >>> import pprint >>> from urllib.request import urlopen - >>> with urlopen('https://pypi.org/pypi/sampleproject/json') as resp: + >>> with urlopen('https://pypi.org/pypi/sampleproject/1.2.0/json') as resp: ... project_info = json.load(resp)['info'] In its basic form, :func:`~pprint.pp` shows the whole object:: From d79acd96e083e6ac2b9b9c71fcff1161fc3bfa18 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Thu, 14 Nov 2024 10:46:26 +0100 Subject: [PATCH 203/269] [3.12] Document that return-less user-defined functions return None (GH-126769) (#126823) Document that return-less user-defined functions return None (GH-126769) (cherry picked from commit e0692f11650acb6c2eed940eb94650b4703c072e) Co-authored-by: John Marshall Co-authored-by: Andrew Svetlov Co-authored-by: Carol Willing --- Doc/reference/expressions.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Doc/reference/expressions.rst b/Doc/reference/expressions.rst index 829200efa34c35..6c178ad8760829 100644 --- a/Doc/reference/expressions.rst +++ b/Doc/reference/expressions.rst @@ -1149,7 +1149,8 @@ a user-defined function: first thing the code block will do is bind the formal parameters to the arguments; this is described in section :ref:`function`. When the code block executes a :keyword:`return` statement, this specifies the return value of the - function call. + function call. If execution reaches the end of the code block without + executing a :keyword:`return` statement, the return value is ``None``. a built-in function or method: .. index:: From 306db142c274f186da951a68fe5121330da91151 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Thu, 14 Nov 2024 23:35:35 +0100 Subject: [PATCH 204/269] [3.12] gh-126807: pygettext: Do not attempt to extract messages from function definitions. (GH-126808) (GH-126847) Fixes a bug where pygettext would attempt to extract a message from a code like this: def _(x): pass This is because pygettext only looks at one token at a time and '_(x)' looks like a function call. However, since 'x' is not a string literal, it would erroneously issue a warning. (cherry picked from commit 9a456383bed52010b90bd491277ea855626a7bba) Co-authored-by: Tomas R --- Lib/test/test_tools/test_i18n.py | 33 ++++++++++++++++--- ...-11-13-22-23-36.gh-issue-126807.vpaWuN.rst | 2 ++ Tools/i18n/pygettext.py | 6 ++++ 3 files changed, 36 insertions(+), 5 deletions(-) create mode 100644 Misc/NEWS.d/next/Tools-Demos/2024-11-13-22-23-36.gh-issue-126807.vpaWuN.rst diff --git a/Lib/test/test_tools/test_i18n.py b/Lib/test/test_tools/test_i18n.py index 21dead8f943bb7..6f71f0976819f1 100644 --- a/Lib/test/test_tools/test_i18n.py +++ b/Lib/test/test_tools/test_i18n.py @@ -87,17 +87,23 @@ def assert_POT_equal(self, expected, actual): self.maxDiff = None self.assertEqual(normalize_POT_file(expected), normalize_POT_file(actual)) - def extract_docstrings_from_str(self, module_content): - """ utility: return all msgids extracted from module_content """ - filename = 'test_docstrings.py' - with temp_cwd(None) as cwd: + def extract_from_str(self, module_content, *, args=(), strict=True): + """Return all msgids extracted from module_content.""" + filename = 'test.py' + with temp_cwd(None): with open(filename, 'w', encoding='utf-8') as fp: fp.write(module_content) - assert_python_ok('-Xutf8', self.script, '-D', filename) + res = assert_python_ok('-Xutf8', self.script, *args, filename) + if strict: + self.assertEqual(res.err, b'') with open('messages.pot', encoding='utf-8') as fp: data = fp.read() return self.get_msgids(data) + def extract_docstrings_from_str(self, module_content): + """Return all docstrings extracted from module_content.""" + return self.extract_from_str(module_content, args=('--docstrings',), strict=False) + def test_header(self): """Make sure the required fields are in the header, according to: http://www.gnu.org/software/gettext/manual/gettext.html#Header-Entry @@ -344,6 +350,23 @@ def test_calls_in_fstring_with_partially_wrong_expression(self): self.assertNotIn('foo', msgids) self.assertIn('bar', msgids) + def test_function_and_class_names(self): + """Test that function and class names are not mistakenly extracted.""" + msgids = self.extract_from_str(dedent('''\ + def _(x): + pass + + def _(x="foo"): + pass + + async def _(x): + pass + + class _(object): + pass + ''')) + self.assertEqual(msgids, ['']) + def test_pygettext_output(self): """Test that the pygettext output exactly matches snapshots.""" for input_file in DATA_DIR.glob('*.py'): diff --git a/Misc/NEWS.d/next/Tools-Demos/2024-11-13-22-23-36.gh-issue-126807.vpaWuN.rst b/Misc/NEWS.d/next/Tools-Demos/2024-11-13-22-23-36.gh-issue-126807.vpaWuN.rst new file mode 100644 index 00000000000000..310286ce8319ea --- /dev/null +++ b/Misc/NEWS.d/next/Tools-Demos/2024-11-13-22-23-36.gh-issue-126807.vpaWuN.rst @@ -0,0 +1,2 @@ +Fix extraction warnings in :program:`pygettext.py` caused by mistaking +function definitions for function calls. diff --git a/Tools/i18n/pygettext.py b/Tools/i18n/pygettext.py index 3a0b27ba420e7a..0d16e8f7da0071 100755 --- a/Tools/i18n/pygettext.py +++ b/Tools/i18n/pygettext.py @@ -341,6 +341,9 @@ def __waiting(self, ttype, tstring, lineno): if ttype == tokenize.NAME and tstring in ('class', 'def'): self.__state = self.__suiteseen return + if ttype == tokenize.NAME and tstring in ('class', 'def'): + self.__state = self.__ignorenext + return if ttype == tokenize.NAME and tstring in opts.keywords: self.__state = self.__keywordseen return @@ -448,6 +451,9 @@ def __openseen(self, ttype, tstring, lineno): }, file=sys.stderr) self.__state = self.__waiting + def __ignorenext(self, ttype, tstring, lineno): + self.__state = self.__waiting + def __addentry(self, msg, lineno=None, isdocstring=0): if lineno is None: lineno = self.__lineno From 04f38bb775e998e78bb0cb2747ef57f50f129cb5 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Fri, 15 Nov 2024 00:52:46 +0100 Subject: [PATCH 205/269] [3.12] GH-126766: `url2pathname()`: handle empty authority section. (GH-126767) (#126837) GH-126766: `url2pathname()`: handle empty authority section. (GH-126767) Discard two leading slashes from the beginning of a `file:` URI if they introduce an empty authority section. As a result, file URIs like `///etc/hosts` are correctly parsed as `/etc/hosts`. (cherry picked from commit cae9d9d20f61cdbde0765efa340b6b596c31b67f) Co-authored-by: Barney Gale --- Lib/nturl2path.py | 7 +++---- Lib/test/test_urllib.py | 10 +++++----- Lib/urllib/request.py | 4 ++++ .../2024-11-12-21-43-12.gh-issue-126766.oi2KJ7.rst | 2 ++ 4 files changed, 14 insertions(+), 9 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-11-12-21-43-12.gh-issue-126766.oi2KJ7.rst diff --git a/Lib/nturl2path.py b/Lib/nturl2path.py index 9ecabff21c33e1..255eb2f547c2ce 100644 --- a/Lib/nturl2path.py +++ b/Lib/nturl2path.py @@ -19,10 +19,9 @@ def url2pathname(url): url = url.replace(':', '|') if not '|' in url: # No drive specifier, just convert slashes - if url[:4] == '////': - # path is something like ////host/path/on/remote/host - # convert this to \\host\path\on\remote\host - # (notice halving of slashes at the start of the path) + if url[:3] == '///': + # URL has an empty authority section, so the path begins on the + # third character. url = url[2:] # make sure not to convert quoted slashes :-) return urllib.parse.unquote(url.replace('/', '\\')) diff --git a/Lib/test/test_urllib.py b/Lib/test/test_urllib.py index 5433072bcda741..15a698cc10f217 100644 --- a/Lib/test/test_urllib.py +++ b/Lib/test/test_urllib.py @@ -1558,7 +1558,7 @@ def test_pathname2url_win(self): self.assertEqual(fn('//?/unc/server/share/dir'), '//server/share/dir') # Round-tripping urls = ['///C:', - '///folder/test/', + '/folder/test/', '///C:/foo/bar/spam.foo'] for url in urls: self.assertEqual(fn(urllib.request.url2pathname(url)), url) @@ -1582,7 +1582,7 @@ def test_url2pathname_win(self): self.assertEqual(fn('/C|//'), 'C:\\\\') self.assertEqual(fn('///C|/path'), 'C:\\path') # No DOS drive - self.assertEqual(fn("///C/test/"), '\\\\\\C\\test\\') + self.assertEqual(fn("///C/test/"), '\\C\\test\\') self.assertEqual(fn("////C/test/"), '\\\\C\\test\\') # DOS drive paths self.assertEqual(fn('C:/path/to/file'), 'C:\\path\\to\\file') @@ -1606,7 +1606,7 @@ def test_url2pathname_win(self): self.assertEqual(fn('//server/share/foo%2fbar'), '\\\\server\\share\\foo/bar') # Round-tripping paths = ['C:', - r'\\\C\test\\', + r'\C\test\\', r'C:\foo\bar\spam.foo'] for path in paths: self.assertEqual(fn(urllib.request.pathname2url(path)), path) @@ -1617,8 +1617,8 @@ def test_url2pathname_posix(self): fn = urllib.request.url2pathname self.assertEqual(fn('/foo/bar'), '/foo/bar') self.assertEqual(fn('//foo/bar'), '//foo/bar') - self.assertEqual(fn('///foo/bar'), '///foo/bar') - self.assertEqual(fn('////foo/bar'), '////foo/bar') + self.assertEqual(fn('///foo/bar'), '/foo/bar') + self.assertEqual(fn('////foo/bar'), '//foo/bar') self.assertEqual(fn('//localhost/foo/bar'), '//localhost/foo/bar') class Utility_Tests(unittest.TestCase): diff --git a/Lib/urllib/request.py b/Lib/urllib/request.py index 7228a35534b638..178c9795e19c6e 100644 --- a/Lib/urllib/request.py +++ b/Lib/urllib/request.py @@ -1681,6 +1681,10 @@ def data_open(self, req): def url2pathname(pathname): """OS-specific conversion from a relative URL of the 'file' scheme to a file system path; not recommended for general use.""" + if pathname[:3] == '///': + # URL has an empty authority section, so the path begins on the + # third character. + pathname = pathname[2:] return unquote(pathname) def pathname2url(pathname): diff --git a/Misc/NEWS.d/next/Library/2024-11-12-21-43-12.gh-issue-126766.oi2KJ7.rst b/Misc/NEWS.d/next/Library/2024-11-12-21-43-12.gh-issue-126766.oi2KJ7.rst new file mode 100644 index 00000000000000..e3936305164883 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-11-12-21-43-12.gh-issue-126766.oi2KJ7.rst @@ -0,0 +1,2 @@ +Fix issue where :func:`urllib.request.url2pathname` failed to discard two +leading slashes introducing an empty authority section. From ad4f8debda413e06d0a24aaa0e45458434d16cb1 Mon Sep 17 00:00:00 2001 From: "Tomas R." Date: Fri, 15 Nov 2024 12:07:16 +0100 Subject: [PATCH 206/269] [3.12] gh-126413: Add translation tests for getopt and optparse (GH-126698) (GH-126756) (cherry picked from commit dff074d1446bab23578a6b228b0c59a17006299c) --- Lib/test/support/i18n_helper.py | 63 ++++++++++++++++++++ Lib/test/test_argparse.py | 57 ++---------------- Lib/test/test_getopt.py | 19 ++++-- Lib/test/test_optparse.py | 11 +++- Lib/test/translationdata/getopt/msgids.txt | 6 ++ Lib/test/translationdata/optparse/msgids.txt | 14 +++++ Makefile.pre.in | 2 + 7 files changed, 114 insertions(+), 58 deletions(-) create mode 100644 Lib/test/support/i18n_helper.py create mode 100644 Lib/test/translationdata/getopt/msgids.txt create mode 100644 Lib/test/translationdata/optparse/msgids.txt diff --git a/Lib/test/support/i18n_helper.py b/Lib/test/support/i18n_helper.py new file mode 100644 index 00000000000000..2e304f29e8ba7f --- /dev/null +++ b/Lib/test/support/i18n_helper.py @@ -0,0 +1,63 @@ +import re +import subprocess +import sys +import unittest +from pathlib import Path +from test.support import REPO_ROOT, TEST_HOME_DIR, requires_subprocess +from test.test_tools import skip_if_missing + + +pygettext = Path(REPO_ROOT) / 'Tools' / 'i18n' / 'pygettext.py' + +msgid_pattern = re.compile(r'msgid(.*?)(?:msgid_plural|msgctxt|msgstr)', + re.DOTALL) +msgid_string_pattern = re.compile(r'"((?:\\"|[^"])*)"') + + +def _generate_po_file(path, *, stdout_only=True): + res = subprocess.run([sys.executable, pygettext, + '--no-location', '-o', '-', path], + stdout=subprocess.PIPE, stderr=subprocess.PIPE, + text=True) + if stdout_only: + return res.stdout + return res + + +def _extract_msgids(po): + msgids = [] + for msgid in msgid_pattern.findall(po): + msgid_string = ''.join(msgid_string_pattern.findall(msgid)) + msgid_string = msgid_string.replace(r'\"', '"') + if msgid_string: + msgids.append(msgid_string) + return sorted(msgids) + + +def _get_snapshot_path(module_name): + return Path(TEST_HOME_DIR) / 'translationdata' / module_name / 'msgids.txt' + + +@requires_subprocess() +class TestTranslationsBase(unittest.TestCase): + + def assertMsgidsEqual(self, module): + '''Assert that msgids extracted from a given module match a + snapshot. + + ''' + skip_if_missing('i18n') + res = _generate_po_file(module.__file__, stdout_only=False) + self.assertEqual(res.returncode, 0) + self.assertEqual(res.stderr, '') + msgids = _extract_msgids(res.stdout) + snapshot_path = _get_snapshot_path(module.__name__) + snapshot = snapshot_path.read_text().splitlines() + self.assertListEqual(msgids, snapshot) + + +def update_translation_snapshots(module): + contents = _generate_po_file(module.__file__) + msgids = _extract_msgids(contents) + snapshot_path = _get_snapshot_path(module.__name__) + snapshot_path.write_text('\n'.join(msgids)) diff --git a/Lib/test/test_argparse.py b/Lib/test/test_argparse.py index c8adc640f2ccaf..01d2a9f202255e 100644 --- a/Lib/test/test_argparse.py +++ b/Lib/test/test_argparse.py @@ -6,10 +6,8 @@ import io import operator import os -import re import shutil import stat -import subprocess import sys import textwrap import tempfile @@ -18,15 +16,8 @@ import warnings from enum import StrEnum -from pathlib import Path -from test.support import REPO_ROOT -from test.support import TEST_HOME_DIR -from test.support import captured_stderr -from test.support import import_helper from test.support import os_helper -from test.support import requires_subprocess -from test.support import script_helper -from test.test_tools import skip_if_missing +from test.support.i18n_helper import TestTranslationsBase, update_translation_snapshots from unittest import mock @@ -6402,50 +6393,10 @@ def test_os_error(self): # Translation tests # ================= -pygettext = Path(REPO_ROOT) / 'Tools' / 'i18n' / 'pygettext.py' -snapshot_path = Path(TEST_HOME_DIR) / 'translationdata' / 'argparse' / 'msgids.txt' - -msgid_pattern = re.compile(r'msgid(.*?)(?:msgid_plural|msgctxt|msgstr)', re.DOTALL) -msgid_string_pattern = re.compile(r'"((?:\\"|[^"])*)"') - - -@requires_subprocess() -class TestTranslations(unittest.TestCase): +class TestTranslations(TestTranslationsBase): def test_translations(self): - # Test messages extracted from the argparse module against a snapshot - skip_if_missing('i18n') - res = generate_po_file(stdout_only=False) - self.assertEqual(res.returncode, 0) - self.assertEqual(res.stderr, '') - msgids = extract_msgids(res.stdout) - snapshot = snapshot_path.read_text().splitlines() - self.assertListEqual(msgids, snapshot) - - -def generate_po_file(*, stdout_only=True): - res = subprocess.run([sys.executable, pygettext, - '--no-location', '-o', '-', argparse.__file__], - stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) - if stdout_only: - return res.stdout - return res - - -def extract_msgids(po): - msgids = [] - for msgid in msgid_pattern.findall(po): - msgid_string = ''.join(msgid_string_pattern.findall(msgid)) - msgid_string = msgid_string.replace(r'\"', '"') - if msgid_string: - msgids.append(msgid_string) - return sorted(msgids) - - -def update_translation_snapshots(): - contents = generate_po_file() - msgids = extract_msgids(contents) - snapshot_path.write_text('\n'.join(msgids)) + self.assertMsgidsEqual(argparse) def tearDownModule(): @@ -6457,6 +6408,6 @@ def tearDownModule(): if __name__ == '__main__': # To regenerate translation snapshots if len(sys.argv) > 1 and sys.argv[1] == '--snapshot-update': - update_translation_snapshots() + update_translation_snapshots(argparse) sys.exit(0) unittest.main() diff --git a/Lib/test/test_getopt.py b/Lib/test/test_getopt.py index c8b3442de4aa77..2b7d0c4c84ead4 100644 --- a/Lib/test/test_getopt.py +++ b/Lib/test/test_getopt.py @@ -1,11 +1,12 @@ # test_getopt.py # David Goodger 2000-08-19 -from test.support.os_helper import EnvironmentVarGuard import doctest -import unittest - import getopt +import sys +import unittest +from test.support.i18n_helper import TestTranslationsBase, update_translation_snapshots +from test.support.os_helper import EnvironmentVarGuard sentinel = object() @@ -173,10 +174,20 @@ def test_libref_examples(): ['a1', 'a2'] """ + +class TestTranslations(TestTranslationsBase): + def test_translations(self): + self.assertMsgidsEqual(getopt) + + def load_tests(loader, tests, pattern): tests.addTest(doctest.DocTestSuite()) return tests -if __name__ == "__main__": +if __name__ == '__main__': + # To regenerate translation snapshots + if len(sys.argv) > 1 and sys.argv[1] == '--snapshot-update': + update_translation_snapshots(getopt) + sys.exit(0) unittest.main() diff --git a/Lib/test/test_optparse.py b/Lib/test/test_optparse.py index 28b274462388ed..8655a0537a5e56 100644 --- a/Lib/test/test_optparse.py +++ b/Lib/test/test_optparse.py @@ -15,7 +15,7 @@ from io import StringIO from test import support from test.support import os_helper - +from test.support.i18n_helper import TestTranslationsBase, update_translation_snapshots import optparse from optparse import make_option, Option, \ @@ -1656,5 +1656,14 @@ def test__all__(self): support.check__all__(self, optparse, not_exported=not_exported) +class TestTranslations(TestTranslationsBase): + def test_translations(self): + self.assertMsgidsEqual(optparse) + + if __name__ == '__main__': + # To regenerate translation snapshots + if len(sys.argv) > 1 and sys.argv[1] == '--snapshot-update': + update_translation_snapshots(optparse) + sys.exit(0) unittest.main() diff --git a/Lib/test/translationdata/getopt/msgids.txt b/Lib/test/translationdata/getopt/msgids.txt new file mode 100644 index 00000000000000..1ffab1f31abad5 --- /dev/null +++ b/Lib/test/translationdata/getopt/msgids.txt @@ -0,0 +1,6 @@ +option -%s not recognized +option -%s requires argument +option --%s must not have an argument +option --%s not a unique prefix +option --%s not recognized +option --%s requires argument \ No newline at end of file diff --git a/Lib/test/translationdata/optparse/msgids.txt b/Lib/test/translationdata/optparse/msgids.txt new file mode 100644 index 00000000000000..ac5317c736af8c --- /dev/null +++ b/Lib/test/translationdata/optparse/msgids.txt @@ -0,0 +1,14 @@ +%prog [options] +%s option does not take a value +Options +Usage +Usage: %s\n +ambiguous option: %s (%s?) +complex +floating-point +integer +no such option: %s +option %s: invalid %s value: %r +option %s: invalid choice: %r (choose from %s) +show program's version number and exit +show this help message and exit \ No newline at end of file diff --git a/Makefile.pre.in b/Makefile.pre.in index f87de823974e96..083f4c750a0a74 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -2249,6 +2249,8 @@ TESTSUBDIRS= idlelib/idle_test \ test/tracedmodules \ test/translationdata \ test/translationdata/argparse \ + test/translationdata/getopt \ + test/translationdata/optparse \ test/typinganndata \ test/wheeldata \ test/xmltestdata \ From ef0a0059e126d44882f08e617a0f93b376e2de67 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Fri, 15 Nov 2024 14:07:04 +0100 Subject: [PATCH 207/269] [3.12] gh-123832: Adjust `socket.getaddrinfo` docs for better POSIX compliance (GH-126182) (GH-126824) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit gh-123832: Adjust `socket.getaddrinfo` docs for better POSIX compliance (GH-126182) * gh-123832: Adjust `socket.getaddrinfo` docs for better POSIX compliance This changes nothing changes for CPython supported platforms, but hints how to deal with platforms that stick to the letter of the spec. It also marks `socket.getaddrinfo` as a wrapper around `getaddrinfo(3)`; specifically, workarounds to make the function work consistently across platforms are out of scope in its code. Include wording similar to the POSIX's “by providing options and by limiting the returned information”, which IMO suggests that the hints limit the resulting list compared to the defaults, *but* can be interpreted differently. Details are added in a note. Specifically say that this wraps the underlying C function. So, the details are in OS docs. The “full range of results” bit goes away. Use `AF_UNSPEC` rather than zero for the *family* default, although I don't think a system where it's nonzero would be very usable. Suggest setting proto and/or type (with examples, as the appropriate values aren't obvious). Say why you probably want to do that that on all systems; mention the behavior on the “letter of the spec” systems. Suggest that the results should be tried in order, which is, AFAIK best practice -- see RFC 6724 section 2, and its predecessor from 2003 (which are specific to IP, but indicate how people use this): > Well-behaved applications SHOULD iterate through the list of > addresses returned from `getaddrinfo()` until they find a working address. (cherry picked from commit ff0ef0a54bef26fc507fbf9b7a6009eb7d3f17f5) Co-authored-by: Petr Viktorin Co-authored-by: Carol Willing --- Doc/library/socket.rst | 35 ++++++++++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/Doc/library/socket.rst b/Doc/library/socket.rst index 584a12c2514958..ab0ede803a0509 100644 --- a/Doc/library/socket.rst +++ b/Doc/library/socket.rst @@ -915,7 +915,9 @@ The :mod:`socket` module also offers various network-related services: .. versionadded:: 3.7 -.. function:: getaddrinfo(host, port, family=0, type=0, proto=0, flags=0) +.. function:: getaddrinfo(host, port, family=AF_UNSPEC, type=0, proto=0, flags=0) + + This function wraps the C function ``getaddrinfo`` of the underlying system. Translate the *host*/*port* argument into a sequence of 5-tuples that contain all the necessary arguments for creating a socket connected to that service. @@ -925,8 +927,10 @@ The :mod:`socket` module also offers various network-related services: and *port*, you can pass ``NULL`` to the underlying C API. The *family*, *type* and *proto* arguments can be optionally specified - in order to narrow the list of addresses returned. Passing zero as a - value for each of these arguments selects the full range of results. + in order to provide options and limit the list of addresses returned. + Pass their default values (:data:`AF_UNSPEC`, 0, and 0, respectively) + to not limit the results. See the note below for details. + The *flags* argument can be one or several of the ``AI_*`` constants, and will influence how results are computed and returned. For example, :const:`AI_NUMERICHOST` will disable domain name resolution @@ -946,6 +950,29 @@ The :mod:`socket` module also offers various network-related services: :const:`AF_INET6`), and is meant to be passed to the :meth:`socket.connect` method. + .. note:: + + If you intend to use results from :func:`!getaddrinfo` to create a socket + (rather than, for example, retrieve *canonname*), + consider limiting the results by *type* (e.g. :data:`SOCK_STREAM` or + :data:`SOCK_DGRAM`) and/or *proto* (e.g. :data:`IPPROTO_TCP` or + :data:`IPPROTO_UDP`) that your application can handle. + + The behavior with default values of *family*, *type*, *proto* + and *flags* is system-specific. + + Many systems (for example, most Linux configurations) will return a sorted + list of all matching addresses. + These addresses should generally be tried in order until a connection succeeds + (possibly tried in parallel, for example, using a `Happy Eyeballs`_ algorithm). + In these cases, limiting the *type* and/or *proto* can help eliminate + unsuccessful or unusable connecton attempts. + + Some systems will, however, only return a single address. + (For example, this was reported on Solaris and AIX configurations.) + On these systems, limiting the *type* and/or *proto* helps ensure that + this address is usable. + .. audit-event:: socket.getaddrinfo host,port,family,type,protocol socket.getaddrinfo The following example fetches address information for a hypothetical TCP @@ -965,6 +992,8 @@ The :mod:`socket` module also offers various network-related services: for IPv6 multicast addresses, string representing an address will not contain ``%scope_id`` part. +.. _Happy Eyeballs: https://en.wikipedia.org/wiki/Happy_Eyeballs + .. function:: getfqdn([name]) Return a fully qualified domain name for *name*. If *name* is omitted or empty, From d0d892f7a320fa5baf3f304c94d02056b19a9799 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Fri, 15 Nov 2024 15:04:35 +0100 Subject: [PATCH 208/269] [3.12] gh-71936: Fix race condition in multiprocessing.Pool (GH-124973) (GH-126870) Proxes of shared objects register a Finalizer in BaseProxy._incref(), and it will call BaseProxy._decref() when it is GCed. This may cause a race condition with Pool(maxtasksperchild=None) on Windows. A connection would be closed and raised TypeError when a GC occurs between _ConnectionBase._check_writable() and _ConnectionBase._send_bytes() in _ConnectionBase.send() in the second or later task, and a new object is allocated that shares the id() of a previously deleted one. Instead of using the id() of the token (or the proxy), use a unique, non-reusable number. (cherry picked from commit ba088c8f9cf7163b0f28c507cb1343befe21997e) Co-authored-by: Petr Viktorin Co-authored-by: Akinori Hattori --- Lib/multiprocessing/managers.py | 33 +++++++++++-------- Misc/ACKS | 1 + ...2-10-15-10-18-20.gh-issue-71936.MzJjc_.rst | 1 + 3 files changed, 22 insertions(+), 13 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2022-10-15-10-18-20.gh-issue-71936.MzJjc_.rst diff --git a/Lib/multiprocessing/managers.py b/Lib/multiprocessing/managers.py index 75d9c18c201a86..010d16e1498995 100644 --- a/Lib/multiprocessing/managers.py +++ b/Lib/multiprocessing/managers.py @@ -755,22 +755,29 @@ class BaseProxy(object): _address_to_local = {} _mutex = util.ForkAwareThreadLock() + # Each instance gets a `_serial` number. Unlike `id(...)`, this number + # is never reused. + _next_serial = 1 + def __init__(self, token, serializer, manager=None, authkey=None, exposed=None, incref=True, manager_owned=False): with BaseProxy._mutex: - tls_idset = BaseProxy._address_to_local.get(token.address, None) - if tls_idset is None: - tls_idset = util.ForkAwareLocal(), ProcessLocalSet() - BaseProxy._address_to_local[token.address] = tls_idset + tls_serials = BaseProxy._address_to_local.get(token.address, None) + if tls_serials is None: + tls_serials = util.ForkAwareLocal(), ProcessLocalSet() + BaseProxy._address_to_local[token.address] = tls_serials + + self._serial = BaseProxy._next_serial + BaseProxy._next_serial += 1 # self._tls is used to record the connection used by this # thread to communicate with the manager at token.address - self._tls = tls_idset[0] + self._tls = tls_serials[0] - # self._idset is used to record the identities of all shared - # objects for which the current process owns references and + # self._all_serials is a set used to record the identities of all + # shared objects for which the current process owns references and # which are in the manager at token.address - self._idset = tls_idset[1] + self._all_serials = tls_serials[1] self._token = token self._id = self._token.id @@ -850,20 +857,20 @@ def _incref(self): dispatch(conn, None, 'incref', (self._id,)) util.debug('INCREF %r', self._token.id) - self._idset.add(self._id) + self._all_serials.add(self._serial) state = self._manager and self._manager._state self._close = util.Finalize( self, BaseProxy._decref, - args=(self._token, self._authkey, state, - self._tls, self._idset, self._Client), + args=(self._token, self._serial, self._authkey, state, + self._tls, self._all_serials, self._Client), exitpriority=10 ) @staticmethod - def _decref(token, authkey, state, tls, idset, _Client): - idset.discard(token.id) + def _decref(token, serial, authkey, state, tls, idset, _Client): + idset.discard(serial) # check whether manager is still alive if state is None or state.value == State.STARTED: diff --git a/Misc/ACKS b/Misc/ACKS index 837ffbda18aea1..cb9de23eeeac11 100644 --- a/Misc/ACKS +++ b/Misc/ACKS @@ -723,6 +723,7 @@ Larry Hastings Tim Hatch Zac Hatfield-Dodds Shane Hathaway +Akinori Hattori Michael Haubenwallner Janko Hauser Flavian Hautbois diff --git a/Misc/NEWS.d/next/Library/2022-10-15-10-18-20.gh-issue-71936.MzJjc_.rst b/Misc/NEWS.d/next/Library/2022-10-15-10-18-20.gh-issue-71936.MzJjc_.rst new file mode 100644 index 00000000000000..a0959cc086fa9e --- /dev/null +++ b/Misc/NEWS.d/next/Library/2022-10-15-10-18-20.gh-issue-71936.MzJjc_.rst @@ -0,0 +1 @@ +Fix a race condition in :class:`multiprocessing.pool.Pool`. From 797a6327cc8974bb6a692b0761d425392776ae6f Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Sat, 16 Nov 2024 00:17:37 +0100 Subject: [PATCH 209/269] [3.12] Added a warning to the urljoin docs, indicating that it is not safe to use with attacker controlled URLs (GH-126659) (#126889) Added a warning to the urljoin docs, indicating that it is not safe to use with attacker controlled URLs (GH-126659) This was flagged to me at a party today by someone who works in red-teaming as a frequently encountered footgun. Documenting the potentially unexpected behavior seemed like a good place to start. (cherry picked from commit d6bcc154e93a0a20ab97187d3e8b726fffb14f8f) Co-authored-by: Alex Gaynor --- Doc/library/urllib.parse.rst | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Doc/library/urllib.parse.rst b/Doc/library/urllib.parse.rst index 27909b763e9e43..b32b4af1aa84bf 100644 --- a/Doc/library/urllib.parse.rst +++ b/Doc/library/urllib.parse.rst @@ -395,6 +395,15 @@ or on combining URL components into a URL string. If you do not want that behavior, preprocess the *url* with :func:`urlsplit` and :func:`urlunsplit`, removing possible *scheme* and *netloc* parts. + .. warning:: + + Because an absolute URL may be passed as the ``url`` parameter, it is + generally **not secure** to use ``urljoin`` with an attacker-controlled + ``url``. For example in, + ``urljoin("https://website.com/users/", username)``, if ``username`` can + contain an absolute URL, the result of ``urljoin`` will be the absolute + URL. + .. versionchanged:: 3.5 From cdc1dff33f43d410d9136b808b3d8cc3e7df82a9 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Sat, 16 Nov 2024 00:20:10 +0100 Subject: [PATCH 210/269] [3.12] gh-126476: Raise IllegalMonthError for calendar.formatmonth() when the input month is not correct (GH-126484) (GH-126878) gh-126476: Raise IllegalMonthError for calendar.formatmonth() when the input month is not correct (GH-126484) (cherry picked from commit 3be7498d2450519d5d8f63a35ef298db3b3d935b) Co-authored-by: Nadeshiko Manju Co-authored-by: Ethan Furman --- Lib/calendar.py | 16 +++++++++++++--- Lib/test/test_calendar.py | 14 +++++++++++++- ...024-11-06-18-30-50.gh-issue-126476.F1wh3c.rst | 2 ++ 3 files changed, 28 insertions(+), 4 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-11-06-18-30-50.gh-issue-126476.F1wh3c.rst diff --git a/Lib/calendar.py b/Lib/calendar.py index ee3ec838c96ac9..3509648435de3c 100644 --- a/Lib/calendar.py +++ b/Lib/calendar.py @@ -28,7 +28,9 @@ error = ValueError # Exceptions raised for bad input -class IllegalMonthError(ValueError): +# This is trick for backward compatibility. Since 3.13, we will raise IllegalMonthError instead of +# IndexError for bad month number(out of 1-12). But we can't remove IndexError for backward compatibility. +class IllegalMonthError(ValueError, IndexError): def __init__(self, month): self.month = month def __str__(self): @@ -158,11 +160,14 @@ def weekday(year, month, day): return Day(datetime.date(year, month, day).weekday()) +def _validate_month(month): + if not 1 <= month <= 12: + raise IllegalMonthError(month) + def monthrange(year, month): """Return weekday of first day of month (0-6 ~ Mon-Sun) and number of days (28-31) for year, month.""" - if not 1 <= month <= 12: - raise IllegalMonthError(month) + _validate_month(month) day1 = weekday(year, month, 1) ndays = mdays[month] + (month == FEBRUARY and isleap(year)) return day1, ndays @@ -370,6 +375,8 @@ def formatmonthname(self, theyear, themonth, width, withyear=True): """ Return a formatted month name. """ + _validate_month(themonth) + s = month_name[themonth] if withyear: s = "%s %r" % (s, theyear) @@ -500,6 +507,7 @@ def formatmonthname(self, theyear, themonth, withyear=True): """ Return a month name as a table row. """ + _validate_month(themonth) if withyear: s = '%s %s' % (month_name[themonth], theyear) else: @@ -781,6 +789,8 @@ def main(args): if options.month is None: optdict["c"] = options.spacing optdict["m"] = options.months + if options.month is not None: + _validate_month(options.month) if options.year is None: result = cal.formatyear(datetime.date.today().year, **optdict) elif options.month is None: diff --git a/Lib/test/test_calendar.py b/Lib/test/test_calendar.py index 24e472b5fee828..0712559be0fae9 100644 --- a/Lib/test/test_calendar.py +++ b/Lib/test/test_calendar.py @@ -456,6 +456,11 @@ def test_formatmonth(self): calendar.TextCalendar().formatmonth(0, 2), result_0_02_text ) + def test_formatmonth_with_invalid_month(self): + with self.assertRaises(calendar.IllegalMonthError): + calendar.TextCalendar().formatmonth(2017, 13) + with self.assertRaises(calendar.IllegalMonthError): + calendar.TextCalendar().formatmonth(2017, -1) def test_formatmonthname_with_year(self): self.assertEqual( @@ -972,7 +977,7 @@ def test__all__(self): not_exported = { 'mdays', 'January', 'February', 'EPOCH', 'different_locale', 'c', 'prweek', 'week', 'format', - 'formatstring', 'main', 'monthlen', 'prevmonth', 'nextmonth'} + 'formatstring', 'main', 'monthlen', 'prevmonth', 'nextmonth', ""} support.check__all__(self, calendar, not_exported=not_exported) @@ -1000,6 +1005,13 @@ def test_formatmonth(self): self.assertIn('class="text-center month"', self.cal.formatmonth(2017, 5)) + def test_formatmonth_with_invalid_month(self): + with self.assertRaises(calendar.IllegalMonthError): + self.cal.formatmonth(2017, 13) + with self.assertRaises(calendar.IllegalMonthError): + self.cal.formatmonth(2017, -1) + + def test_formatweek(self): weeks = self.cal.monthdays2calendar(2017, 5) self.assertIn('class="wed text-nowrap"', self.cal.formatweek(weeks[0])) diff --git a/Misc/NEWS.d/next/Library/2024-11-06-18-30-50.gh-issue-126476.F1wh3c.rst b/Misc/NEWS.d/next/Library/2024-11-06-18-30-50.gh-issue-126476.F1wh3c.rst new file mode 100644 index 00000000000000..f558c29e8b087f --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-11-06-18-30-50.gh-issue-126476.F1wh3c.rst @@ -0,0 +1,2 @@ +Raise :class:`calendar.IllegalMonthError` (now a subclass of :class:`IndexError`) for :func:`calendar.month` +when the input month is not correct. From 50e42b99802882d5e1f6286ec988ed310370a6ff Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Sun, 17 Nov 2024 01:26:59 +0100 Subject: [PATCH 211/269] [3.12] GH-126789: fix some sysconfig data on late site initializations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Filipe Laíns 🇵🇸 --- Lib/sysconfig.py | 18 ++++- Lib/test/support/venv.py | 70 +++++++++++++++++ Lib/test/test_sysconfig.py | 75 +++++++++++++++++++ ...-11-13-22-25-57.gh-issue-126789.lKzlc7.rst | 4 + 4 files changed, 163 insertions(+), 4 deletions(-) create mode 100644 Lib/test/support/venv.py create mode 100644 Misc/NEWS.d/next/Library/2024-11-13-22-25-57.gh-issue-126789.lKzlc7.rst diff --git a/Lib/sysconfig.py b/Lib/sysconfig.py index 122d441bd19f5e..517b13acaf6823 100644 --- a/Lib/sysconfig.py +++ b/Lib/sysconfig.py @@ -169,9 +169,7 @@ def joinuser(*args): _PY_VERSION = sys.version.split()[0] _PY_VERSION_SHORT = f'{sys.version_info[0]}.{sys.version_info[1]}' _PY_VERSION_SHORT_NO_DOT = f'{sys.version_info[0]}{sys.version_info[1]}' -_PREFIX = os.path.normpath(sys.prefix) _BASE_PREFIX = os.path.normpath(sys.base_prefix) -_EXEC_PREFIX = os.path.normpath(sys.exec_prefix) _BASE_EXEC_PREFIX = os.path.normpath(sys.base_exec_prefix) # Mutex guarding initialization of _CONFIG_VARS. _CONFIG_VARS_LOCK = threading.RLock() @@ -642,8 +640,10 @@ def _init_config_vars(): # Normalized versions of prefix and exec_prefix are handy to have; # in fact, these are the standard versions used most places in the # Distutils. - _CONFIG_VARS['prefix'] = _PREFIX - _CONFIG_VARS['exec_prefix'] = _EXEC_PREFIX + _PREFIX = os.path.normpath(sys.prefix) + _EXEC_PREFIX = os.path.normpath(sys.exec_prefix) + _CONFIG_VARS['prefix'] = _PREFIX # FIXME: This gets overwriten by _init_posix. + _CONFIG_VARS['exec_prefix'] = _EXEC_PREFIX # FIXME: This gets overwriten by _init_posix. _CONFIG_VARS['py_version'] = _PY_VERSION _CONFIG_VARS['py_version_short'] = _PY_VERSION_SHORT _CONFIG_VARS['py_version_nodot'] = _PY_VERSION_SHORT_NO_DOT @@ -711,6 +711,7 @@ def get_config_vars(*args): With arguments, return a list of values that result from looking up each argument in the configuration variable dictionary. """ + global _CONFIG_VARS_INITIALIZED # Avoid claiming the lock once initialization is complete. if not _CONFIG_VARS_INITIALIZED: @@ -721,6 +722,15 @@ def get_config_vars(*args): # don't re-enter init_config_vars(). if _CONFIG_VARS is None: _init_config_vars() + else: + # If the site module initialization happened after _CONFIG_VARS was + # initialized, a virtual environment might have been activated, resulting in + # variables like sys.prefix changing their value, so we need to re-init the + # config vars (see GH-126789). + if _CONFIG_VARS['base'] != os.path.normpath(sys.prefix): + with _CONFIG_VARS_LOCK: + _CONFIG_VARS_INITIALIZED = False + _init_config_vars() if args: vals = [] diff --git a/Lib/test/support/venv.py b/Lib/test/support/venv.py new file mode 100644 index 00000000000000..78e6a51ec1815e --- /dev/null +++ b/Lib/test/support/venv.py @@ -0,0 +1,70 @@ +import contextlib +import logging +import os +import subprocess +import shlex +import sys +import sysconfig +import tempfile +import venv + + +class VirtualEnvironment: + def __init__(self, prefix, **venv_create_args): + self._logger = logging.getLogger(self.__class__.__name__) + venv.create(prefix, **venv_create_args) + self._prefix = prefix + self._paths = sysconfig.get_paths( + scheme='venv', + vars={'base': self.prefix}, + expand=True, + ) + + @classmethod + @contextlib.contextmanager + def from_tmpdir(cls, *, prefix=None, dir=None, **venv_create_args): + delete = not bool(os.environ.get('PYTHON_TESTS_KEEP_VENV')) + with tempfile.TemporaryDirectory(prefix=prefix, dir=dir, delete=delete) as tmpdir: + yield cls(tmpdir, **venv_create_args) + + @property + def prefix(self): + return self._prefix + + @property + def paths(self): + return self._paths + + @property + def interpreter(self): + return os.path.join(self.paths['scripts'], os.path.basename(sys.executable)) + + def _format_output(self, name, data, indent='\t'): + if not data: + return indent + f'{name}: (none)' + if len(data.splitlines()) == 1: + return indent + f'{name}: {data}' + else: + prefixed_lines = '\n'.join(indent + '> ' + line for line in data.splitlines()) + return indent + f'{name}:\n' + prefixed_lines + + def run(self, *args, **subprocess_args): + if subprocess_args.get('shell'): + raise ValueError('Running the subprocess in shell mode is not supported.') + default_args = { + 'capture_output': True, + 'check': True, + } + try: + result = subprocess.run([self.interpreter, *args], **default_args | subprocess_args) + except subprocess.CalledProcessError as e: + if e.returncode != 0: + self._logger.error( + f'Interpreter returned non-zero exit status {e.returncode}.\n' + + self._format_output('COMMAND', shlex.join(e.cmd)) + '\n' + + self._format_output('STDOUT', e.stdout.decode()) + '\n' + + self._format_output('STDERR', e.stderr.decode()) + '\n' + ) + raise + else: + return result diff --git a/Lib/test/test_sysconfig.py b/Lib/test/test_sysconfig.py index 1137c2032b91af..b91977d0cc7624 100644 --- a/Lib/test/test_sysconfig.py +++ b/Lib/test/test_sysconfig.py @@ -3,6 +3,8 @@ import os import subprocess import shutil +import json +import textwrap from copy import copy from test.support import ( @@ -11,6 +13,7 @@ from test.support.import_helper import import_module from test.support.os_helper import (TESTFN, unlink, skip_unless_symlink, change_cwd) +from test.support.venv import VirtualEnvironment import sysconfig from sysconfig import (get_paths, get_platform, get_config_vars, @@ -90,6 +93,12 @@ def _cleanup_testfn(self): elif os.path.isdir(path): shutil.rmtree(path) + def venv(self, **venv_create_args): + return VirtualEnvironment.from_tmpdir( + prefix=f'{self.id()}-venv-', + **venv_create_args, + ) + def test_get_path_names(self): self.assertEqual(get_path_names(), sysconfig._SCHEME_KEYS) @@ -511,6 +520,72 @@ def test_osx_ext_suffix(self): suffix = sysconfig.get_config_var('EXT_SUFFIX') self.assertTrue(suffix.endswith('-darwin.so'), suffix) + @unittest.skipIf(sys.platform == 'wasi', 'venv is unsupported on WASI') + def test_config_vars_depend_on_site_initialization(self): + script = textwrap.dedent(""" + import sysconfig + + config_vars = sysconfig.get_config_vars() + + import json + print(json.dumps(config_vars, indent=2)) + """) + + with self.venv() as venv: + site_config_vars = json.loads(venv.run('-c', script).stdout) + no_site_config_vars = json.loads(venv.run('-S', '-c', script).stdout) + + self.assertNotEqual(site_config_vars, no_site_config_vars) + # With the site initialization, the virtual environment should be enabled. + self.assertEqual(site_config_vars['base'], venv.prefix) + self.assertEqual(site_config_vars['platbase'], venv.prefix) + #self.assertEqual(site_config_vars['prefix'], venv.prefix) # # FIXME: prefix gets overwriten by _init_posix + # Without the site initialization, the virtual environment should be disabled. + self.assertEqual(no_site_config_vars['base'], site_config_vars['installed_base']) + self.assertEqual(no_site_config_vars['platbase'], site_config_vars['installed_platbase']) + + @unittest.skipIf(sys.platform == 'wasi', 'venv is unsupported on WASI') + def test_config_vars_recalculation_after_site_initialization(self): + script = textwrap.dedent(""" + import sysconfig + + before = sysconfig.get_config_vars() + + import site + site.main() + + after = sysconfig.get_config_vars() + + import json + print(json.dumps({'before': before, 'after': after}, indent=2)) + """) + + with self.venv() as venv: + config_vars = json.loads(venv.run('-S', '-c', script).stdout) + + self.assertNotEqual(config_vars['before'], config_vars['after']) + self.assertEqual(config_vars['after']['base'], venv.prefix) + #self.assertEqual(config_vars['after']['prefix'], venv.prefix) # FIXME: prefix gets overwriten by _init_posix + #self.assertEqual(config_vars['after']['exec_prefix'], venv.prefix) # FIXME: exec_prefix gets overwriten by _init_posix + + @unittest.skipIf(sys.platform == 'wasi', 'venv is unsupported on WASI') + def test_paths_depend_on_site_initialization(self): + script = textwrap.dedent(""" + import sysconfig + + paths = sysconfig.get_paths() + + import json + print(json.dumps(paths, indent=2)) + """) + + with self.venv() as venv: + site_paths = json.loads(venv.run('-c', script).stdout) + no_site_paths = json.loads(venv.run('-S', '-c', script).stdout) + + self.assertNotEqual(site_paths, no_site_paths) + + class MakefileTests(unittest.TestCase): @unittest.skipIf(sys.platform.startswith('win'), diff --git a/Misc/NEWS.d/next/Library/2024-11-13-22-25-57.gh-issue-126789.lKzlc7.rst b/Misc/NEWS.d/next/Library/2024-11-13-22-25-57.gh-issue-126789.lKzlc7.rst new file mode 100644 index 00000000000000..09d4d2e5ab9037 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-11-13-22-25-57.gh-issue-126789.lKzlc7.rst @@ -0,0 +1,4 @@ +Fixed the values of :py:func:`sysconfig.get_config_vars`, +:py:func:`sysconfig.get_paths`, and their siblings when the :py:mod:`site` +initialization happens after :py:mod:`sysconfig` has built a cache for +:py:func:`sysconfig.get_config_vars`. From 9d986d9477b55004901f840ca0418ab302789d35 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Sun, 17 Nov 2024 21:12:29 +0100 Subject: [PATCH 212/269] [3.12] gh-124452: Fix header mismatches when folding/unfolding with email message (GH-125919) (#126916) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit gh-124452: Fix header mismatches when folding/unfolding with email message (GH-125919) The header-folder of the new email API has a long standing known buglet where if the first token is longer than max_line_length, it puts that token on the next line. It turns out there is also a *parsing* bug when parsing such a header: the space prefixing that first, non-empty line gets preserved and tacked on to the start of the header value, which is not the expected behavior per the RFCs. The bug arises from the fact that the parser assumed that there would be at least one token on the line with the header, which is going to be true for probably every email producer other than the python email library with its folding buglet. Clearly, though, this is a case that needs to be handled correctly. The fix is simple: strip the blanks off the start of the whole value, not just the first physical line of the value. (cherry picked from commit ed81971e6b26c34445f06850192b34458b029337) Co-authored-by: RanKKI Co-authored-by: blurb-it[bot] <43283697+blurb-it[bot]@users.noreply.github.com> Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> --- Lib/email/_policybase.py | 4 +- Lib/email/policy.py | 4 +- Lib/test/test_email/test_message.py | 50 ++++++++++++++++++- ...-10-24-10-49-47.gh-issue-124452.eqTRgx.rst | 4 ++ 4 files changed, 56 insertions(+), 6 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-10-24-10-49-47.gh-issue-124452.eqTRgx.rst diff --git a/Lib/email/_policybase.py b/Lib/email/_policybase.py index 5f9aa9fb091fa2..c9f0d743090e54 100644 --- a/Lib/email/_policybase.py +++ b/Lib/email/_policybase.py @@ -302,12 +302,12 @@ def header_source_parse(self, sourcelines): """+ The name is parsed as everything up to the ':' and returned unmodified. The value is determined by stripping leading whitespace off the - remainder of the first line, joining all subsequent lines together, and + remainder of the first line joined with all subsequent lines, and stripping any trailing carriage return or linefeed characters. """ name, value = sourcelines[0].split(':', 1) - value = value.lstrip(' \t') + ''.join(sourcelines[1:]) + value = ''.join((value, *sourcelines[1:])).lstrip(' \t\r\n') return (name, value.rstrip('\r\n')) def header_store_parse(self, name, value): diff --git a/Lib/email/policy.py b/Lib/email/policy.py index 46b7de5bb6d8ae..6e109b65011a44 100644 --- a/Lib/email/policy.py +++ b/Lib/email/policy.py @@ -119,13 +119,13 @@ def header_source_parse(self, sourcelines): """+ The name is parsed as everything up to the ':' and returned unmodified. The value is determined by stripping leading whitespace off the - remainder of the first line, joining all subsequent lines together, and + remainder of the first line joined with all subsequent lines, and stripping any trailing carriage return or linefeed characters. (This is the same as Compat32). """ name, value = sourcelines[0].split(':', 1) - value = value.lstrip(' \t') + ''.join(sourcelines[1:]) + value = ''.join((value, *sourcelines[1:])).lstrip(' \t\r\n') return (name, value.rstrip('\r\n')) def header_store_parse(self, name, value): diff --git a/Lib/test/test_email/test_message.py b/Lib/test/test_email/test_message.py index 034f7626c1fc7c..96979db27f3a21 100644 --- a/Lib/test/test_email/test_message.py +++ b/Lib/test/test_email/test_message.py @@ -1,6 +1,6 @@ -import unittest import textwrap -from email import policy, message_from_string +import unittest +from email import message_from_bytes, message_from_string, policy from email.message import EmailMessage, MIMEPart from test.test_email import TestEmailBase, parameterize @@ -958,6 +958,52 @@ def test_folding_with_utf8_encoding_8(self): b'123456789-123456789\n 123456789 Hello ' b'=?utf-8?q?W=C3=B6rld!?= 123456789 123456789\n\n') + def test_folding_with_short_nospace_1(self): + # bpo-36520 + # + # Fold a line that contains a long whitespace after + # the fold point. + + m = EmailMessage(policy.default) + m['Message-ID'] = '123456789' * 3 + parsed_msg = message_from_bytes(m.as_bytes(), policy=policy.default) + self.assertEqual(parsed_msg['Message-ID'], m['Message-ID']) + + def test_folding_with_long_nospace_default_policy_1(self): + # Fixed: https://github.com/python/cpython/issues/124452 + # + # When the value is too long, it should be converted back + # to its original form without any modifications. + + m = EmailMessage(policy.default) + message = '123456789' * 10 + m['Message-ID'] = message + self.assertEqual(m.as_bytes(), + f'Message-ID:\n {message}\n\n'.encode()) + parsed_msg = message_from_bytes(m.as_bytes(), policy=policy.default) + self.assertEqual(parsed_msg['Message-ID'], m['Message-ID']) + + def test_folding_with_long_nospace_compat32_policy_1(self): + m = EmailMessage(policy.compat32) + message = '123456789' * 10 + m['Message-ID'] = message + parsed_msg = message_from_bytes(m.as_bytes(), policy=policy.default) + self.assertEqual(parsed_msg['Message-ID'], m['Message-ID']) + + def test_folding_with_long_nospace_smtp_policy_1(self): + m = EmailMessage(policy.SMTP) + message = '123456789' * 10 + m['Message-ID'] = message + parsed_msg = message_from_bytes(m.as_bytes(), policy=policy.default) + self.assertEqual(parsed_msg['Message-ID'], m['Message-ID']) + + def test_folding_with_long_nospace_http_policy_1(self): + m = EmailMessage(policy.HTTP) + message = '123456789' * 10 + m['Message-ID'] = message + parsed_msg = message_from_bytes(m.as_bytes(), policy=policy.default) + self.assertEqual(parsed_msg['Message-ID'], m['Message-ID']) + def test_get_body_malformed(self): """test for bpo-42892""" msg = textwrap.dedent("""\ diff --git a/Misc/NEWS.d/next/Library/2024-10-24-10-49-47.gh-issue-124452.eqTRgx.rst b/Misc/NEWS.d/next/Library/2024-10-24-10-49-47.gh-issue-124452.eqTRgx.rst new file mode 100644 index 00000000000000..b0d63794022db4 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-10-24-10-49-47.gh-issue-124452.eqTRgx.rst @@ -0,0 +1,4 @@ +Fix an issue in :meth:`email.policy.EmailPolicy.header_source_parse` and +:meth:`email.policy.Compat32.header_source_parse` that introduced spurious +leading whitespaces into header values when the header includes a newline +character after the header name delimiter (``:``) and before the value. From 8eb25faed706de41bd6965efbb15d04b5715ed48 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Mon, 18 Nov 2024 13:24:13 +0200 Subject: [PATCH 213/269] [3.12] gh-67877: Fix memory leaks in terminated RE matching (GH-126840) (GH-126961) If SRE(match) function terminates abruptly, either because of a signal or because memory allocation fails, allocated SRE_REPEAT blocks might be never released. (cherry picked from commit 7538e7f5696408fa0aa02fce8a413a7dfac76a04) --- Lib/test/test_re.py | 44 ++++++ ...4-11-14-22-25-49.gh-issue-67877.G9hw0w.rst | 2 + Modules/_sre/clinic/sre.c.h | 44 +++++- Modules/_sre/sre.c | 132 +++++++++++++++++- Modules/_sre/sre.h | 17 ++- Modules/_sre/sre_lib.h | 26 +++- 6 files changed, 251 insertions(+), 14 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-11-14-22-25-49.gh-issue-67877.G9hw0w.rst diff --git a/Lib/test/test_re.py b/Lib/test/test_re.py index 756a7ccd506be4..130e1e1d01dec5 100644 --- a/Lib/test/test_re.py +++ b/Lib/test/test_re.py @@ -2621,6 +2621,50 @@ def test_regression_gh94675(self): p.terminate() p.join() + def test_fail(self): + self.assertEqual(re.search(r'12(?!)|3', '123')[0], '3') + + def test_character_set_any(self): + # The union of complementary character sets matches any character + # and is equivalent to "(?s:.)". + s = '1x\n' + for p in r'[\s\S]', r'[\d\D]', r'[\w\W]', r'[\S\s]', r'\s|\S': + with self.subTest(pattern=p): + self.assertEqual(re.findall(p, s), list(s)) + self.assertEqual(re.fullmatch('(?:' + p + ')+', s).group(), s) + + def test_character_set_none(self): + # Negation of the union of complementary character sets does not match + # any character. + s = '1x\n' + for p in r'[^\s\S]', r'[^\d\D]', r'[^\w\W]', r'[^\S\s]': + with self.subTest(pattern=p): + self.assertIsNone(re.search(p, s)) + self.assertIsNone(re.search('(?s:.)' + p, s)) + + def check_interrupt(self, pattern, string, maxcount): + class Interrupt(Exception): + pass + p = re.compile(pattern) + for n in range(maxcount): + try: + p._fail_after(n, Interrupt) + p.match(string) + return n + except Interrupt: + pass + finally: + p._fail_after(-1, None) + + @unittest.skipUnless(hasattr(re.Pattern, '_fail_after'), 'requires debug build') + def test_memory_leaks(self): + self.check_interrupt(r'(.)*:', 'abc:', 100) + self.check_interrupt(r'([^:])*?:', 'abc:', 100) + self.check_interrupt(r'([^:])*+:', 'abc:', 100) + self.check_interrupt(r'(.){2,4}:', 'abc:', 100) + self.check_interrupt(r'([^:]){2,4}?:', 'abc:', 100) + self.check_interrupt(r'([^:]){2,4}+:', 'abc:', 100) + def get_debug_out(pat): with captured_stdout() as out: diff --git a/Misc/NEWS.d/next/Library/2024-11-14-22-25-49.gh-issue-67877.G9hw0w.rst b/Misc/NEWS.d/next/Library/2024-11-14-22-25-49.gh-issue-67877.G9hw0w.rst new file mode 100644 index 00000000000000..021b4ae2e100bc --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-11-14-22-25-49.gh-issue-67877.G9hw0w.rst @@ -0,0 +1,2 @@ +Fix memory leaks when :mod:`regular expression ` matching terminates +abruptly, either because of a signal or because memory allocation fails. diff --git a/Modules/_sre/clinic/sre.c.h b/Modules/_sre/clinic/sre.c.h index 529c634e76d63c..56a4e6048fa8ef 100644 --- a/Modules/_sre/clinic/sre.c.h +++ b/Modules/_sre/clinic/sre.c.h @@ -975,6 +975,44 @@ PyDoc_STRVAR(_sre_SRE_Pattern___deepcopy____doc__, #define _SRE_SRE_PATTERN___DEEPCOPY___METHODDEF \ {"__deepcopy__", (PyCFunction)_sre_SRE_Pattern___deepcopy__, METH_O, _sre_SRE_Pattern___deepcopy____doc__}, +#if defined(Py_DEBUG) + +PyDoc_STRVAR(_sre_SRE_Pattern__fail_after__doc__, +"_fail_after($self, count, exception, /)\n" +"--\n" +"\n" +"For debugging."); + +#define _SRE_SRE_PATTERN__FAIL_AFTER_METHODDEF \ + {"_fail_after", _PyCFunction_CAST(_sre_SRE_Pattern__fail_after), METH_FASTCALL, _sre_SRE_Pattern__fail_after__doc__}, + +static PyObject * +_sre_SRE_Pattern__fail_after_impl(PatternObject *self, int count, + PyObject *exception); + +static PyObject * +_sre_SRE_Pattern__fail_after(PatternObject *self, PyObject *const *args, Py_ssize_t nargs) +{ + PyObject *return_value = NULL; + int count; + PyObject *exception; + + if (!_PyArg_CheckPositional("_fail_after", nargs, 2, 2)) { + goto exit; + } + count = _PyLong_AsInt(args[0]); + if (count == -1 && PyErr_Occurred()) { + goto exit; + } + exception = args[1]; + return_value = _sre_SRE_Pattern__fail_after_impl(self, count, exception); + +exit: + return return_value; +} + +#endif /* defined(Py_DEBUG) */ + PyDoc_STRVAR(_sre_compile__doc__, "compile($module, /, pattern, flags, code, groups, groupindex,\n" " indexgroup)\n" @@ -1460,4 +1498,8 @@ _sre_SRE_Scanner_search(ScannerObject *self, PyTypeObject *cls, PyObject *const } return _sre_SRE_Scanner_search_impl(self, cls); } -/*[clinic end generated code: output=045de53cfe02dee0 input=a9049054013a1b77]*/ + +#ifndef _SRE_SRE_PATTERN__FAIL_AFTER_METHODDEF + #define _SRE_SRE_PATTERN__FAIL_AFTER_METHODDEF +#endif /* !defined(_SRE_SRE_PATTERN__FAIL_AFTER_METHODDEF) */ +/*[clinic end generated code: output=2165ecf43a7c20e8 input=a9049054013a1b77]*/ diff --git a/Modules/_sre/sre.c b/Modules/_sre/sre.c index 6d9843bb76d791..35c6058dac4eae 100644 --- a/Modules/_sre/sre.c +++ b/Modules/_sre/sre.c @@ -218,6 +218,85 @@ data_stack_grow(SRE_STATE* state, Py_ssize_t size) return 0; } +/* memory pool functions for SRE_REPEAT, this can avoid memory + leak when SRE(match) function terminates abruptly. + state->repeat_pool_used is a doubly-linked list, so that we + can remove a SRE_REPEAT node from it. + state->repeat_pool_unused is a singly-linked list, we put/get + node at the head. */ +static SRE_REPEAT * +repeat_pool_malloc(SRE_STATE *state) +{ + SRE_REPEAT *repeat; + + if (state->repeat_pool_unused) { + /* remove from unused pool (singly-linked list) */ + repeat = state->repeat_pool_unused; + state->repeat_pool_unused = repeat->pool_next; + } + else { + repeat = PyObject_Malloc(sizeof(SRE_REPEAT)); + if (!repeat) { + return NULL; + } + } + + /* add to used pool (doubly-linked list) */ + SRE_REPEAT *temp = state->repeat_pool_used; + if (temp) { + temp->pool_prev = repeat; + } + repeat->pool_prev = NULL; + repeat->pool_next = temp; + state->repeat_pool_used = repeat; + + return repeat; +} + +static void +repeat_pool_free(SRE_STATE *state, SRE_REPEAT *repeat) +{ + SRE_REPEAT *prev = repeat->pool_prev; + SRE_REPEAT *next = repeat->pool_next; + + /* remove from used pool (doubly-linked list) */ + if (prev) { + prev->pool_next = next; + } + else { + state->repeat_pool_used = next; + } + if (next) { + next->pool_prev = prev; + } + + /* add to unused pool (singly-linked list) */ + repeat->pool_next = state->repeat_pool_unused; + state->repeat_pool_unused = repeat; +} + +static void +repeat_pool_clear(SRE_STATE *state) +{ + /* clear used pool */ + SRE_REPEAT *next = state->repeat_pool_used; + state->repeat_pool_used = NULL; + while (next) { + SRE_REPEAT *temp = next; + next = temp->pool_next; + PyObject_Free(temp); + } + + /* clear unused pool */ + next = state->repeat_pool_unused; + state->repeat_pool_unused = NULL; + while (next) { + SRE_REPEAT *temp = next; + next = temp->pool_next; + PyObject_Free(temp); + } +} + /* generate 8-bit version */ #define SRE_CHAR Py_UCS1 @@ -463,6 +542,11 @@ state_init(SRE_STATE* state, PatternObject* pattern, PyObject* string, state->pos = start; state->endpos = end; +#ifdef Py_DEBUG + state->fail_after_count = pattern->fail_after_count; + state->fail_after_exc = pattern->fail_after_exc; // borrowed ref +#endif + return string; err: /* We add an explicit cast here because MSVC has a bug when @@ -485,6 +569,8 @@ state_fini(SRE_STATE* state) /* See above PyMem_Del for why we explicitly cast here. */ PyMem_Free((void*) state->mark); state->mark = NULL; + /* SRE_REPEAT pool */ + repeat_pool_clear(state); } /* calculate offset from start of string */ @@ -571,6 +657,9 @@ pattern_traverse(PatternObject *self, visitproc visit, void *arg) Py_VISIT(self->groupindex); Py_VISIT(self->indexgroup); Py_VISIT(self->pattern); +#ifdef Py_DEBUG + Py_VISIT(self->fail_after_exc); +#endif return 0; } @@ -580,6 +669,9 @@ pattern_clear(PatternObject *self) Py_CLEAR(self->groupindex); Py_CLEAR(self->indexgroup); Py_CLEAR(self->pattern); +#ifdef Py_DEBUG + Py_CLEAR(self->fail_after_exc); +#endif return 0; } @@ -642,7 +734,7 @@ _sre_SRE_Pattern_match_impl(PatternObject *self, PyTypeObject *cls, Py_ssize_t status; PyObject *match; - if (!state_init(&state, (PatternObject *)self, string, pos, endpos)) + if (!state_init(&state, self, string, pos, endpos)) return NULL; state.ptr = state.start; @@ -1330,6 +1422,29 @@ _sre_SRE_Pattern___deepcopy__(PatternObject *self, PyObject *memo) return Py_NewRef(self); } +#ifdef Py_DEBUG +/*[clinic input] +_sre.SRE_Pattern._fail_after + + count: int + exception: object + / + +For debugging. +[clinic start generated code]*/ + +static PyObject * +_sre_SRE_Pattern__fail_after_impl(PatternObject *self, int count, + PyObject *exception) +/*[clinic end generated code: output=9a6bf12135ac50c2 input=ef80a45c66c5499d]*/ +{ + self->fail_after_count = count; + Py_INCREF(exception); + Py_XSETREF(self->fail_after_exc, exception); + Py_RETURN_NONE; +} +#endif /* Py_DEBUG */ + static PyObject * pattern_repr(PatternObject *obj) { @@ -1456,6 +1571,10 @@ _sre_compile_impl(PyObject *module, PyObject *pattern, int flags, self->pattern = NULL; self->groupindex = NULL; self->indexgroup = NULL; +#ifdef Py_DEBUG + self->fail_after_count = -1; + self->fail_after_exc = NULL; +#endif self->codesize = n; @@ -2552,7 +2671,8 @@ pattern_new_match(_sremodulestate* module_state, if (!match) return NULL; - match->pattern = (PatternObject*)Py_NewRef(pattern); + Py_INCREF(pattern); + match->pattern = pattern; match->string = Py_NewRef(state->string); @@ -2688,7 +2808,7 @@ _sre_SRE_Scanner_match_impl(ScannerObject *self, PyTypeObject *cls) return NULL; } - match = pattern_new_match(module_state, (PatternObject*) self->pattern, + match = pattern_new_match(module_state, self->pattern, state, status); if (status == 0) @@ -2738,7 +2858,7 @@ _sre_SRE_Scanner_search_impl(ScannerObject *self, PyTypeObject *cls) return NULL; } - match = pattern_new_match(module_state, (PatternObject*) self->pattern, + match = pattern_new_match(module_state, self->pattern, state, status); if (status == 0) @@ -2774,7 +2894,8 @@ pattern_scanner(_sremodulestate *module_state, return NULL; } - scanner->pattern = Py_NewRef(self); + Py_INCREF(self); + scanner->pattern = self; PyObject_GC_Track(scanner); return (PyObject*) scanner; @@ -2968,6 +3089,7 @@ static PyMethodDef pattern_methods[] = { _SRE_SRE_PATTERN_SCANNER_METHODDEF _SRE_SRE_PATTERN___COPY___METHODDEF _SRE_SRE_PATTERN___DEEPCOPY___METHODDEF + _SRE_SRE_PATTERN__FAIL_AFTER_METHODDEF {"__class_getitem__", Py_GenericAlias, METH_O|METH_CLASS, PyDoc_STR("See PEP 585")}, {NULL, NULL} diff --git a/Modules/_sre/sre.h b/Modules/_sre/sre.h index a0f235606e290e..b8c6f8e3e660d1 100644 --- a/Modules/_sre/sre.h +++ b/Modules/_sre/sre.h @@ -34,6 +34,11 @@ typedef struct { int flags; /* flags used when compiling pattern source */ PyObject *weakreflist; /* List of weak references */ int isbytes; /* pattern type (1 - bytes, 0 - string, -1 - None) */ +#ifdef Py_DEBUG + /* for simulation of user interruption */ + int fail_after_count; + PyObject *fail_after_exc; +#endif /* pattern code */ Py_ssize_t codesize; SRE_CODE code[1]; @@ -68,6 +73,9 @@ typedef struct SRE_REPEAT_T { const SRE_CODE* pattern; /* points to REPEAT operator arguments */ const void* last_ptr; /* helper to check for infinite loops */ struct SRE_REPEAT_T *prev; /* points to previous repeat context */ + /* for SRE_REPEAT pool */ + struct SRE_REPEAT_T *pool_prev; + struct SRE_REPEAT_T *pool_next; } SRE_REPEAT; typedef struct { @@ -94,12 +102,19 @@ typedef struct { size_t data_stack_base; /* current repeat context */ SRE_REPEAT *repeat; + /* SRE_REPEAT pool */ + SRE_REPEAT *repeat_pool_used; + SRE_REPEAT *repeat_pool_unused; unsigned int sigcount; +#ifdef Py_DEBUG + int fail_after_count; + PyObject *fail_after_exc; +#endif } SRE_STATE; typedef struct { PyObject_HEAD - PyObject* pattern; + PatternObject* pattern; SRE_STATE state; int executing; } ScannerObject; diff --git a/Modules/_sre/sre_lib.h b/Modules/_sre/sre_lib.h index 95c1ada908d222..d82ba7aa3c8b83 100644 --- a/Modules/_sre/sre_lib.h +++ b/Modules/_sre/sre_lib.h @@ -524,13 +524,28 @@ typedef struct { Py_ssize_t last_ctx_pos; } SRE(match_context); -#define MAYBE_CHECK_SIGNALS \ +#define _MAYBE_CHECK_SIGNALS \ do { \ if ((0 == (++sigcount & 0xfff)) && PyErr_CheckSignals()) { \ RETURN_ERROR(SRE_ERROR_INTERRUPTED); \ } \ } while (0) +#ifdef Py_DEBUG +# define MAYBE_CHECK_SIGNALS \ + do { \ + _MAYBE_CHECK_SIGNALS; \ + if (state->fail_after_count >= 0) { \ + if (state->fail_after_count-- == 0) { \ + PyErr_SetNone(state->fail_after_exc); \ + RETURN_ERROR(SRE_ERROR_INTERRUPTED); \ + } \ + } \ + } while (0) +#else +# define MAYBE_CHECK_SIGNALS _MAYBE_CHECK_SIGNALS +#endif /* Py_DEBUG */ + #ifdef HAVE_COMPUTED_GOTOS #ifndef USE_COMPUTED_GOTOS #define USE_COMPUTED_GOTOS 1 @@ -1083,12 +1098,9 @@ SRE(match)(SRE_STATE* state, const SRE_CODE* pattern, int toplevel) pattern[1], pattern[2])); /* install new repeat context */ - /* TODO(https://github.com/python/cpython/issues/67877): Fix this - * potential memory leak. */ - ctx->u.rep = (SRE_REPEAT*) PyObject_Malloc(sizeof(*ctx->u.rep)); + ctx->u.rep = repeat_pool_malloc(state); if (!ctx->u.rep) { - PyErr_NoMemory(); - RETURN_FAILURE; + RETURN_ERROR(SRE_ERROR_MEMORY); } ctx->u.rep->count = -1; ctx->u.rep->pattern = pattern; @@ -1099,7 +1111,7 @@ SRE(match)(SRE_STATE* state, const SRE_CODE* pattern, int toplevel) state->ptr = ptr; DO_JUMP(JUMP_REPEAT, jump_repeat, pattern+pattern[0]); state->repeat = ctx->u.rep->prev; - PyObject_Free(ctx->u.rep); + repeat_pool_free(state, ctx->u.rep); if (ret) { RETURN_ON_ERROR(ret); From 06dc4bd0731368b99bd4b0f7047a93f33b69774f Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Mon, 18 Nov 2024 13:03:19 +0100 Subject: [PATCH 214/269] [3.12] gh-101955: Fix SystemError in possesive quantifier with alternative and group (GH-111362) (GH-126963) (cherry picked from commit f9c5573dedcb2f2e9ae152672ce157987cdea612) Co-authored-by: Serhiy Storchaka --- Lib/test/test_re.py | 6 ++++++ ...3-10-26-16-36-22.gh-issue-101955.Ixu3IF.rst | 2 ++ Modules/_sre/sre_lib.h | 18 ++++++++++++++++++ 3 files changed, 26 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2023-10-26-16-36-22.gh-issue-101955.Ixu3IF.rst diff --git a/Lib/test/test_re.py b/Lib/test/test_re.py index 130e1e1d01dec5..fbab49534183de 100644 --- a/Lib/test/test_re.py +++ b/Lib/test/test_re.py @@ -2601,6 +2601,12 @@ def test_bug_gh106052(self): self.assertEqual(re.match("(?>(?:ab?c){1,3})", "aca").span(), (0, 2)) self.assertEqual(re.match("(?:ab?c){1,3}+", "aca").span(), (0, 2)) + def test_bug_gh101955(self): + # Possessive quantifier with nested alternative with capture groups + self.assertEqual(re.match('((x)|y|z)*+', 'xyz').groups(), ('z', 'x')) + self.assertEqual(re.match('((x)|y|z){3}+', 'xyz').groups(), ('z', 'x')) + self.assertEqual(re.match('((x)|y|z){3,}+', 'xyz').groups(), ('z', 'x')) + @unittest.skipIf(multiprocessing is None, 'test requires multiprocessing') def test_regression_gh94675(self): pattern = re.compile(r'(?<=[({}])(((//[^\n]*)?[\n])([\000-\040])*)*' diff --git a/Misc/NEWS.d/next/Library/2023-10-26-16-36-22.gh-issue-101955.Ixu3IF.rst b/Misc/NEWS.d/next/Library/2023-10-26-16-36-22.gh-issue-101955.Ixu3IF.rst new file mode 100644 index 00000000000000..89431010f784f8 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-10-26-16-36-22.gh-issue-101955.Ixu3IF.rst @@ -0,0 +1,2 @@ +Fix SystemError when match regular expression pattern containing some +combination of possessive quantifier, alternative and capture group. diff --git a/Modules/_sre/sre_lib.h b/Modules/_sre/sre_lib.h index d82ba7aa3c8b83..9e9a0ec92683f0 100644 --- a/Modules/_sre/sre_lib.h +++ b/Modules/_sre/sre_lib.h @@ -1269,6 +1269,17 @@ SRE(match)(SRE_STATE* state, const SRE_CODE* pattern, int toplevel) pointer */ state->ptr = ptr; + /* Set state->repeat to non-NULL */ + ctx->u.rep = repeat_pool_malloc(state); + if (!ctx->u.rep) { + RETURN_ERROR(SRE_ERROR_MEMORY); + } + ctx->u.rep->count = -1; + ctx->u.rep->pattern = NULL; + ctx->u.rep->prev = state->repeat; + ctx->u.rep->last_ptr = NULL; + state->repeat = ctx->u.rep; + /* Initialize Count to 0 */ ctx->count = 0; @@ -1283,6 +1294,9 @@ SRE(match)(SRE_STATE* state, const SRE_CODE* pattern, int toplevel) } else { state->ptr = ptr; + /* Restore state->repeat */ + state->repeat = ctx->u.rep->prev; + repeat_pool_free(state, ctx->u.rep); RETURN_FAILURE; } } @@ -1355,6 +1369,10 @@ SRE(match)(SRE_STATE* state, const SRE_CODE* pattern, int toplevel) } } + /* Restore state->repeat */ + state->repeat = ctx->u.rep->prev; + repeat_pool_free(state, ctx->u.rep); + /* Evaluate Tail */ /* Jump to end of pattern indicated by skip, and then skip the SUCCESS op code that follows it. */ From fd241d64c174a4d1d60e348d29d26344fc5670c2 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Mon, 18 Nov 2024 14:22:58 +0100 Subject: [PATCH 215/269] [3.12] gh-126909: Fix running xattr tests on systems with lower limits (GH-126930) (#126964) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit gh-126909: Fix running xattr tests on systems with lower limits (GH-126930) Modify the extended attribute tests to write fewer and smaller extended attributes, in order to fit within filesystems with total xattr limit of 1 KiB (e.g. ext4 with 1 KiB blocks). Previously, the test would write over 2 KiB, making it fail with ENOSPC on such systems. (cherry picked from commit 2c0a21c1aad65ab8362491acf856eb574b1257ad) Co-authored-by: Michał Górny --- Lib/test/test_os.py | 6 +++--- .../Tests/2024-11-17-16-56-48.gh-issue-126909.60VTxW.rst | 2 ++ 2 files changed, 5 insertions(+), 3 deletions(-) create mode 100644 Misc/NEWS.d/next/Tests/2024-11-17-16-56-48.gh-issue-126909.60VTxW.rst diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py index 8277ec9b608e84..1d6d92e02356b4 100644 --- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@ -3887,10 +3887,10 @@ def _check_xattrs_str(self, s, getxattr, setxattr, removexattr, listxattr, **kwa xattr.remove("user.test") self.assertEqual(set(listxattr(fn)), xattr) self.assertEqual(getxattr(fn, s("user.test2"), **kwargs), b"foo") - setxattr(fn, s("user.test"), b"a"*1024, **kwargs) - self.assertEqual(getxattr(fn, s("user.test"), **kwargs), b"a"*1024) + setxattr(fn, s("user.test"), b"a"*256, **kwargs) + self.assertEqual(getxattr(fn, s("user.test"), **kwargs), b"a"*256) removexattr(fn, s("user.test"), **kwargs) - many = sorted("user.test{}".format(i) for i in range(100)) + many = sorted("user.test{}".format(i) for i in range(32)) for thing in many: setxattr(fn, thing, b"x", **kwargs) self.assertEqual(set(listxattr(fn)), set(init_xattr) | set(many)) diff --git a/Misc/NEWS.d/next/Tests/2024-11-17-16-56-48.gh-issue-126909.60VTxW.rst b/Misc/NEWS.d/next/Tests/2024-11-17-16-56-48.gh-issue-126909.60VTxW.rst new file mode 100644 index 00000000000000..68bd9ac70cd1f4 --- /dev/null +++ b/Misc/NEWS.d/next/Tests/2024-11-17-16-56-48.gh-issue-126909.60VTxW.rst @@ -0,0 +1,2 @@ +Fix test_os extended attribute tests to work on filesystems with 1 KiB xattr size +limit. From 126acc1bb0782ac7b7944ba1d0f877da728967a7 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Mon, 18 Nov 2024 16:58:22 +0100 Subject: [PATCH 216/269] [3.12] gh-126911: Update credits output (GH-126913) (#126974) Co-authored-by: Stan U <89152624+StanFromIreland@users.noreply.github.com> Co-authored-by: Petr Viktorin Co-authored-by: blurb-it[bot] <43283697+blurb-it[bot]@users.noreply.github.com> Co-authored-by: Terry Jan Reedy Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> --- Lib/site.py | 5 +++-- .../Windows/2024-11-16-22-08-41.gh-issue-126911.HchCZZ.rst | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Windows/2024-11-16-22-08-41.gh-issue-126911.HchCZZ.rst diff --git a/Lib/site.py b/Lib/site.py index 924cfbecec2bf3..aed254ad504d35 100644 --- a/Lib/site.py +++ b/Lib/site.py @@ -426,8 +426,9 @@ def setcopyright(): """Set 'copyright' and 'credits' in builtins""" builtins.copyright = _sitebuiltins._Printer("copyright", sys.copyright) builtins.credits = _sitebuiltins._Printer("credits", """\ - Thanks to CWI, CNRI, BeOpen.com, Zope Corporation and a cast of thousands - for supporting Python development. See www.python.org for more information.""") + Thanks to CWI, CNRI, BeOpen, Zope Corporation, the Python Software + Foundation, and a cast of thousands for supporting Python + development. See www.python.org for more information.""") files, dirs = [], [] # Not all modules are required to have a __file__ attribute. See # PEP 420 for more details. diff --git a/Misc/NEWS.d/next/Windows/2024-11-16-22-08-41.gh-issue-126911.HchCZZ.rst b/Misc/NEWS.d/next/Windows/2024-11-16-22-08-41.gh-issue-126911.HchCZZ.rst new file mode 100644 index 00000000000000..32481cde930fcd --- /dev/null +++ b/Misc/NEWS.d/next/Windows/2024-11-16-22-08-41.gh-issue-126911.HchCZZ.rst @@ -0,0 +1 @@ +Update credits command output. From 9345dc165c3389208da45d391bf5b2d146302e75 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Tue, 19 Nov 2024 09:33:18 +0100 Subject: [PATCH 217/269] [3.12] gh-126594: Fix typeobject.c wrap_buffer() cast (GH-126754) (#127005) gh-126594: Fix typeobject.c wrap_buffer() cast (GH-126754) Reject flags smaller than INT_MIN. (cherry picked from commit 84f07c3a4cbcfe488ccfb4030571be0bc4de7e45) Co-authored-by: Victor Stinner Co-authored-by: Jelle Zijlstra --- Lib/test/test_buffer.py | 15 +++++++++++++++ Objects/typeobject.c | 6 +++--- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_buffer.py b/Lib/test/test_buffer.py index 84a34bccbc9af1..5f3e8b0ff59f8d 100644 --- a/Lib/test/test_buffer.py +++ b/Lib/test/test_buffer.py @@ -4442,6 +4442,21 @@ def test_pybuffer_size_from_format(self): self.assertEqual(_testcapi.PyBuffer_SizeFromFormat(format), struct.calcsize(format)) + @support.cpython_only + def test_flags_overflow(self): + # gh-126594: Check for integer overlow on large flags + try: + from _testcapi import INT_MIN, INT_MAX + except ImportError: + INT_MIN = -(2 ** 31) + INT_MAX = 2 ** 31 - 1 + + obj = b'abc' + for flags in (INT_MIN - 1, INT_MAX + 1): + with self.subTest(flags=flags): + with self.assertRaises(OverflowError): + obj.__buffer__(flags) + class TestPythonBufferProtocol(unittest.TestCase): def test_basic(self): diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 7c678907ed5602..5bca4b4e788608 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -8207,13 +8207,13 @@ wrap_buffer(PyObject *self, PyObject *args, void *wrapped) if (flags == -1 && PyErr_Occurred()) { return NULL; } - if (flags > INT_MAX) { + if (flags > INT_MAX || flags < INT_MIN) { PyErr_SetString(PyExc_OverflowError, - "buffer flags too large"); + "buffer flags out of range"); return NULL; } - return _PyMemoryView_FromBufferProc(self, Py_SAFE_DOWNCAST(flags, Py_ssize_t, int), + return _PyMemoryView_FromBufferProc(self, (int)flags, (getbufferproc)wrapped); } From 1f911ba7061feb4ee072cdd0da0e3be8c41fd690 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Tue, 19 Nov 2024 18:01:54 +0800 Subject: [PATCH 218/269] [3.12] gh-126789: Correct sysconfig test exclusions for iOS and Android. (GH-126941) (GH-126959) (cherry picked from commit 3938fd60c0c88891b213097380aeea91a45bcd77) --- Lib/test/test_sysconfig.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_sysconfig.py b/Lib/test/test_sysconfig.py index b91977d0cc7624..3468d0ce022ae0 100644 --- a/Lib/test/test_sysconfig.py +++ b/Lib/test/test_sysconfig.py @@ -520,7 +520,7 @@ def test_osx_ext_suffix(self): suffix = sysconfig.get_config_var('EXT_SUFFIX') self.assertTrue(suffix.endswith('-darwin.so'), suffix) - @unittest.skipIf(sys.platform == 'wasi', 'venv is unsupported on WASI') + @requires_subprocess() def test_config_vars_depend_on_site_initialization(self): script = textwrap.dedent(""" import sysconfig @@ -544,7 +544,7 @@ def test_config_vars_depend_on_site_initialization(self): self.assertEqual(no_site_config_vars['base'], site_config_vars['installed_base']) self.assertEqual(no_site_config_vars['platbase'], site_config_vars['installed_platbase']) - @unittest.skipIf(sys.platform == 'wasi', 'venv is unsupported on WASI') + @requires_subprocess() def test_config_vars_recalculation_after_site_initialization(self): script = textwrap.dedent(""" import sysconfig @@ -568,7 +568,7 @@ def test_config_vars_recalculation_after_site_initialization(self): #self.assertEqual(config_vars['after']['prefix'], venv.prefix) # FIXME: prefix gets overwriten by _init_posix #self.assertEqual(config_vars['after']['exec_prefix'], venv.prefix) # FIXME: exec_prefix gets overwriten by _init_posix - @unittest.skipIf(sys.platform == 'wasi', 'venv is unsupported on WASI') + @requires_subprocess() def test_paths_depend_on_site_initialization(self): script = textwrap.dedent(""" import sysconfig From cb98122df25d618bfdcaadedbfb23615b8dd362b Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Tue, 19 Nov 2024 16:41:41 +0100 Subject: [PATCH 219/269] [3.12] Update docs 'make serve' to suggest 'make htmllive' (GH-126969) (#127016) Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> --- Doc/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/Makefile b/Doc/Makefile index a090ee5ba92705..22e43ee3e542ee 100644 --- a/Doc/Makefile +++ b/Doc/Makefile @@ -294,7 +294,7 @@ check: _ensure-pre-commit .PHONY: serve serve: - @echo "The serve target was removed, use htmlview instead (see bpo-36329)" + @echo "The serve target was removed, use htmllive instead (see gh-80510)" # Targets for daily automated doc build # By default, Sphinx only rebuilds pages where the page content has changed. From 153221a1d050a74f1cfbb7af69232cb92bf505ba Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Tue, 19 Nov 2024 19:19:18 +0100 Subject: [PATCH 220/269] [3.12] gh-126991: Fix reference leak in loading pickle's opcode BUILD (GH-126990) (GH-127019) If PyObject_SetItem() fails in the `load_build()` function of _pickle.c, no DECREF for the `dict` variable. (cherry picked from commit 29cbcbd73bbfd8c953c0b213fb33682c289934ff) Co-authored-by: Justin Applegate <70449145+Legoclones@users.noreply.github.com> --- Modules/_pickle.c | 1 + 1 file changed, 1 insertion(+) diff --git a/Modules/_pickle.c b/Modules/_pickle.c index 179500d6956a78..b8f701c2af2e67 100644 --- a/Modules/_pickle.c +++ b/Modules/_pickle.c @@ -6726,6 +6726,7 @@ load_build(PickleState *st, UnpicklerObject *self) } if (PyObject_SetItem(dict, d_key, d_value) < 0) { Py_DECREF(d_key); + Py_DECREF(dict); goto error; } Py_DECREF(d_key); From 7735f58ea5274fd72caee42b090761df2b27c087 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Tue, 19 Nov 2024 22:55:10 +0100 Subject: [PATCH 221/269] [3.12] GH-85168: Use filesystem encoding when converting to/from `file` URIs (GH-126852) (#127040) GH-85168: Use filesystem encoding when converting to/from `file` URIs (GH-126852) Adjust `urllib.request.url2pathname()` and `pathname2url()` to use the filesystem encoding when quoting and unquoting file URIs, rather than forcing use of UTF-8. No changes are needed in the `nturl2path` module because Windows always uses UTF-8, per PEP 529. (cherry picked from commit c9b399fbdb01584dcfff0d7f6ad484644ff269c3) Co-authored-by: Barney Gale --- Lib/test/test_urllib.py | 20 +++++++++++++++---- Lib/test/test_urllib2.py | 4 ---- Lib/urllib/request.py | 8 ++++++-- ...4-11-15-01-50-36.gh-issue-85168.bP8VIN.rst | 4 ++++ 4 files changed, 26 insertions(+), 10 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-11-15-01-50-36.gh-issue-85168.bP8VIN.rst diff --git a/Lib/test/test_urllib.py b/Lib/test/test_urllib.py index 15a698cc10f217..b06855e7b3a930 100644 --- a/Lib/test/test_urllib.py +++ b/Lib/test/test_urllib.py @@ -718,10 +718,6 @@ def tearDown(self): def constructLocalFileUrl(self, filePath): filePath = os.path.abspath(filePath) - try: - filePath.encode("utf-8") - except UnicodeEncodeError: - raise unittest.SkipTest("filePath is not encodable to utf8") return "file://%s" % urllib.request.pathname2url(filePath) def createNewTempFile(self, data=b""): @@ -1571,6 +1567,13 @@ def test_pathname2url_posix(self): self.assertEqual(fn('/a/b.c'), '/a/b.c') self.assertEqual(fn('/a/b%#c'), '/a/b%25%23c') + @unittest.skipUnless(os_helper.FS_NONASCII, 'need os_helper.FS_NONASCII') + def test_pathname2url_nonascii(self): + encoding = sys.getfilesystemencoding() + errors = sys.getfilesystemencodeerrors() + url = urllib.parse.quote(os_helper.FS_NONASCII, encoding=encoding, errors=errors) + self.assertEqual(urllib.request.pathname2url(os_helper.FS_NONASCII), url) + @unittest.skipUnless(sys.platform == 'win32', 'test specific to Windows pathnames.') def test_url2pathname_win(self): @@ -1621,6 +1624,15 @@ def test_url2pathname_posix(self): self.assertEqual(fn('////foo/bar'), '//foo/bar') self.assertEqual(fn('//localhost/foo/bar'), '//localhost/foo/bar') + @unittest.skipUnless(os_helper.FS_NONASCII, 'need os_helper.FS_NONASCII') + def test_url2pathname_nonascii(self): + encoding = sys.getfilesystemencoding() + errors = sys.getfilesystemencodeerrors() + url = os_helper.FS_NONASCII + self.assertEqual(urllib.request.url2pathname(url), os_helper.FS_NONASCII) + url = urllib.parse.quote(url, encoding=encoding, errors=errors) + self.assertEqual(urllib.request.url2pathname(url), os_helper.FS_NONASCII) + class Utility_Tests(unittest.TestCase): """Testcase to test the various utility functions in the urllib.""" diff --git a/Lib/test/test_urllib2.py b/Lib/test/test_urllib2.py index 69cf1dc7aef19f..12b1053aa23bab 100644 --- a/Lib/test/test_urllib2.py +++ b/Lib/test/test_urllib2.py @@ -716,10 +716,6 @@ def test_processors(self): def sanepathname2url(path): - try: - path.encode("utf-8") - except UnicodeEncodeError: - raise unittest.SkipTest("path is not encodable to utf8") urlpath = urllib.request.pathname2url(path) if os.name == "nt" and urlpath.startswith("///"): urlpath = urlpath[2:] diff --git a/Lib/urllib/request.py b/Lib/urllib/request.py index 178c9795e19c6e..c89e217b9de1b7 100644 --- a/Lib/urllib/request.py +++ b/Lib/urllib/request.py @@ -1685,12 +1685,16 @@ def url2pathname(pathname): # URL has an empty authority section, so the path begins on the # third character. pathname = pathname[2:] - return unquote(pathname) + encoding = sys.getfilesystemencoding() + errors = sys.getfilesystemencodeerrors() + return unquote(pathname, encoding=encoding, errors=errors) def pathname2url(pathname): """OS-specific conversion from a file system path to a relative URL of the 'file' scheme; not recommended for general use.""" - return quote(pathname) + encoding = sys.getfilesystemencoding() + errors = sys.getfilesystemencodeerrors() + return quote(pathname, encoding=encoding, errors=errors) ftpcache = {} diff --git a/Misc/NEWS.d/next/Library/2024-11-15-01-50-36.gh-issue-85168.bP8VIN.rst b/Misc/NEWS.d/next/Library/2024-11-15-01-50-36.gh-issue-85168.bP8VIN.rst new file mode 100644 index 00000000000000..abceda8f6fd707 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-11-15-01-50-36.gh-issue-85168.bP8VIN.rst @@ -0,0 +1,4 @@ +Fix issue where :func:`urllib.request.url2pathname` and +:func:`~urllib.request.pathname2url` always used UTF-8 when quoting and +unquoting file URIs. They now use the :term:`filesystem encoding and error +handler`. From b33c37aa8c8010d250a68cc1d41d1806ef33a725 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Wed, 20 Nov 2024 16:49:58 +0100 Subject: [PATCH 222/269] [3.12] gh-126991: Add tests for unpickling bad object state (GH-127031) (GH-127064) This catches a memory leak in loading the BUILD opcode. (cherry picked from commit addb225f3823b03774cddacce35214dd471bec46) Co-authored-by: Serhiy Storchaka --- Lib/test/pickletester.py | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/Lib/test/pickletester.py b/Lib/test/pickletester.py index dd7466e2c150d6..85710a976191e9 100644 --- a/Lib/test/pickletester.py +++ b/Lib/test/pickletester.py @@ -1344,6 +1344,41 @@ def test_bad_newobj_ex(self): self.check_unpickling_error(error, b'cbuiltins\nint\nN}\x92.') self.check_unpickling_error(error, b'cbuiltins\nint\n)N\x92.') + def test_bad_state(self): + c = C() + c.x = None + base = b'c__main__\nC\n)\x81' + self.assertEqual(self.loads(base + b'}X\x01\x00\x00\x00xNsb.'), c) + self.assertEqual(self.loads(base + b'N}X\x01\x00\x00\x00xNs\x86b.'), c) + # non-hashable dict key + self.check_unpickling_error(TypeError, base + b'}]Nsb.') + # state = list + error = (pickle.UnpicklingError, AttributeError) + self.check_unpickling_error(error, base + b'](}}eb.') + # state = 1-tuple + self.check_unpickling_error(error, base + b'}\x85b.') + # state = 3-tuple + self.check_unpickling_error(error, base + b'}}}\x87b.') + # non-hashable slot name + self.check_unpickling_error(TypeError, base + b'}}]Ns\x86b.') + # non-string slot name + self.check_unpickling_error(TypeError, base + b'}}NNs\x86b.') + # dict = True + self.check_unpickling_error(error, base + b'\x88}\x86b.') + # slots dict = True + self.check_unpickling_error(error, base + b'}\x88\x86b.') + + class BadKey1: + count = 1 + def __hash__(self): + if not self.count: + raise CustomError + self.count -= 1 + return 42 + __main__.BadKey1 = BadKey1 + # bad hashable dict key + self.check_unpickling_error(CustomError, base + b'}c__main__\nBadKey1\n)\x81Nsb.') + def test_bad_stack(self): badpickles = [ b'.', # STOP From 512a5bdb2f409cc924214806d09cd266c36249cc Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Wed, 20 Nov 2024 20:37:24 +0100 Subject: [PATCH 223/269] [3.12] Run `apt update` before `apt install git` in autoconf CI job (GH-127071) (cherry picked from commit 0af4ec30bd2e3a52350344d1011c0c125d6dcd71) Co-authored-by: Zachary Ware --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 329ebc42a301b4..f7f96ceb574cc0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -104,7 +104,7 @@ jobs: steps: - name: Install Git run: | - apt install git -yq + apt update && apt install git -yq git config --global --add safe.directory "$GITHUB_WORKSPACE" - uses: actions/checkout@v4 with: From d997be051017ac84d3b5ebf69aa0a11b2a61c230 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Thu, 21 Nov 2024 12:32:16 +0100 Subject: [PATCH 224/269] [3.12] gh-126997: Fix support of non-ASCII strings in pickletools (GH-127062) (GH-127095) * Fix support of STRING and GLOBAL opcodes with non-ASCII arguments. * dis() now outputs non-ASCII bytes in STRING, BINSTRING and SHORT_BINSTRING arguments as escaped (\xXX). (cherry picked from commit eaf217108226633c03cc5c4c90f0b6e4587c8803) Co-authored-by: Serhiy Storchaka --- Lib/pickletools.py | 11 ++- Lib/test/test_pickletools.py | 82 +++++++++++++++++++ ...-11-20-16-58-59.gh-issue-126997.0PI41Y.rst | 3 + 3 files changed, 92 insertions(+), 4 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-11-20-16-58-59.gh-issue-126997.0PI41Y.rst diff --git a/Lib/pickletools.py b/Lib/pickletools.py index 51ee4a7a2632ac..33a51492ea9c3b 100644 --- a/Lib/pickletools.py +++ b/Lib/pickletools.py @@ -312,7 +312,7 @@ def read_uint8(f): doc="Eight-byte unsigned integer, little-endian.") -def read_stringnl(f, decode=True, stripquotes=True): +def read_stringnl(f, decode=True, stripquotes=True, *, encoding='latin-1'): r""" >>> import io >>> read_stringnl(io.BytesIO(b"'abcd'\nefg\n")) @@ -356,7 +356,7 @@ def read_stringnl(f, decode=True, stripquotes=True): raise ValueError("no string quotes around %r" % data) if decode: - data = codecs.escape_decode(data)[0].decode("ascii") + data = codecs.escape_decode(data)[0].decode(encoding) return data stringnl = ArgumentDescriptor( @@ -370,7 +370,7 @@ def read_stringnl(f, decode=True, stripquotes=True): """) def read_stringnl_noescape(f): - return read_stringnl(f, stripquotes=False) + return read_stringnl(f, stripquotes=False, encoding='utf-8') stringnl_noescape = ArgumentDescriptor( name='stringnl_noescape', @@ -2513,7 +2513,10 @@ def dis(pickle, out=None, memo=None, indentlevel=4, annotate=0): # make a mild effort to align arguments line += ' ' * (10 - len(opcode.name)) if arg is not None: - line += ' ' + repr(arg) + if opcode.name in ("STRING", "BINSTRING", "SHORT_BINSTRING"): + line += ' ' + ascii(arg) + else: + line += ' ' + repr(arg) if markmsg: line += ' ' + markmsg if annotate: diff --git a/Lib/test/test_pickletools.py b/Lib/test/test_pickletools.py index 8cb1f6dffcc6be..5cb9ce430b4657 100644 --- a/Lib/test/test_pickletools.py +++ b/Lib/test/test_pickletools.py @@ -371,6 +371,88 @@ def test_annotate(self): highest protocol among opcodes = 0 ''', annotate=20) + def test_string(self): + self.check_dis(b"S'abc'\n.", '''\ + 0: S STRING 'abc' + 7: . STOP +highest protocol among opcodes = 0 +''') + self.check_dis(b'S"abc"\n.', '''\ + 0: S STRING 'abc' + 7: . STOP +highest protocol among opcodes = 0 +''') + self.check_dis(b"S'\xc3\xb5'\n.", '''\ + 0: S STRING '\\xc3\\xb5' + 6: . STOP +highest protocol among opcodes = 0 +''') + + def test_string_without_quotes(self): + self.check_dis_error(b"Sabc'\n.", '', + 'no string quotes around b"abc\'"') + self.check_dis_error(b'Sabc"\n.', '', + "no string quotes around b'abc\"'") + self.check_dis_error(b"S'abc\n.", '', + '''strinq quote b"'" not found at both ends of b"'abc"''') + self.check_dis_error(b'S"abc\n.', '', + r"""strinq quote b'"' not found at both ends of b'"abc'""") + self.check_dis_error(b"S'abc\"\n.", '', + r"""strinq quote b"'" not found at both ends of b'\\'abc"'""") + self.check_dis_error(b"S\"abc'\n.", '', + r"""strinq quote b'"' not found at both ends of b'"abc\\''""") + + def test_binstring(self): + self.check_dis(b"T\x03\x00\x00\x00abc.", '''\ + 0: T BINSTRING 'abc' + 8: . STOP +highest protocol among opcodes = 1 +''') + self.check_dis(b"T\x02\x00\x00\x00\xc3\xb5.", '''\ + 0: T BINSTRING '\\xc3\\xb5' + 7: . STOP +highest protocol among opcodes = 1 +''') + + def test_short_binstring(self): + self.check_dis(b"U\x03abc.", '''\ + 0: U SHORT_BINSTRING 'abc' + 5: . STOP +highest protocol among opcodes = 1 +''') + self.check_dis(b"U\x02\xc3\xb5.", '''\ + 0: U SHORT_BINSTRING '\\xc3\\xb5' + 4: . STOP +highest protocol among opcodes = 1 +''') + + def test_global(self): + self.check_dis(b"cmodule\nname\n.", '''\ + 0: c GLOBAL 'module name' + 13: . STOP +highest protocol among opcodes = 0 +''') + self.check_dis(b"cm\xc3\xb6dule\nn\xc3\xa4me\n.", '''\ + 0: c GLOBAL 'm\xf6dule n\xe4me' + 15: . STOP +highest protocol among opcodes = 0 +''') + + def test_inst(self): + self.check_dis(b"(imodule\nname\n.", '''\ + 0: ( MARK + 1: i INST 'module name' (MARK at 0) + 14: . STOP +highest protocol among opcodes = 0 +''') + + def test_persid(self): + self.check_dis(b"Pabc\n.", '''\ + 0: P PERSID 'abc' + 5: . STOP +highest protocol among opcodes = 0 +''') + class MiscTestCase(unittest.TestCase): def test__all__(self): diff --git a/Misc/NEWS.d/next/Library/2024-11-20-16-58-59.gh-issue-126997.0PI41Y.rst b/Misc/NEWS.d/next/Library/2024-11-20-16-58-59.gh-issue-126997.0PI41Y.rst new file mode 100644 index 00000000000000..b85c51ef07dcbe --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-11-20-16-58-59.gh-issue-126997.0PI41Y.rst @@ -0,0 +1,3 @@ +Fix support of STRING and GLOBAL opcodes with non-ASCII arguments in +:mod:`pickletools`. :func:`pickletools.dis` now outputs non-ASCII bytes in +STRING, BINSTRING and SHORT_BINSTRING arguments as escaped (``\xXX``). From 4b4e0dbdf49adc91c35a357ad332ab3abd4c31b1 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Thu, 21 Nov 2024 13:44:37 +0200 Subject: [PATCH 225/269] [3.12] gh-126727: Fix locale.nl_langinfo(locale.ERA) (GH-126730) (GH-127098) It now returns multiple era description segments separated by semicolons. Previously it only returned the first segment on platforms with Glibc. (cherry picked from commit 4803cd0244847f286641c85591fda08b513cea52) --- Doc/library/locale.rst | 10 +-- Lib/test/test__locale.py | 46 +++++++++++++ ...-11-12-13-14-47.gh-issue-126727.5Eqfqd.rst | 3 + Modules/_localemodule.c | 65 ++++++++++++------- 4 files changed, 96 insertions(+), 28 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-11-12-13-14-47.gh-issue-126727.5Eqfqd.rst diff --git a/Doc/library/locale.rst b/Doc/library/locale.rst index a81879a2fe48dc..fee5aba7ee3e32 100644 --- a/Doc/library/locale.rst +++ b/Doc/library/locale.rst @@ -281,7 +281,8 @@ The :mod:`locale` module defines the following exception and functions: .. data:: ERA - Get a string that represents the era used in the current locale. + Get a string which describes how years are counted and displayed for + each era in a locale. Most locales do not define this value. An example of a locale which does define this value is the Japanese one. In Japan, the traditional @@ -290,9 +291,10 @@ The :mod:`locale` module defines the following exception and functions: Normally it should not be necessary to use this value directly. Specifying the ``E`` modifier in their format strings causes the :func:`time.strftime` - function to use this information. The format of the returned string is not - specified, and therefore you should not assume knowledge of it on different - systems. + function to use this information. + The format of the returned string is specified in *The Open Group Base + Specifications Issue 8*, paragraph `7.3.5.2 LC_TIME C-Language Access + `_. .. data:: ERA_D_T_FMT diff --git a/Lib/test/test__locale.py b/Lib/test/test__locale.py index a680e6edb63c0e..89c203250557f0 100644 --- a/Lib/test/test__locale.py +++ b/Lib/test/test__locale.py @@ -90,6 +90,14 @@ def accept(loc): 'bn_IN': (100, {0: '\u09e6', 10: '\u09e7\u09e6', 99: '\u09ef\u09ef'}), } +known_era = { + 'C': (0, ''), + 'en_US': (0, ''), + 'ja_JP': (11, '+:1:2019/05/01:2019/12/31:令和:%EC元年'), + 'zh_TW': (3, '+:1:1912/01/01:1912/12/31:民國:%EC元年'), + 'th_TW': (1, '+:1:-543/01/01:+*:พ.ศ.:%EC %Ey'), +} + if sys.platform == 'win32': # ps_AF doesn't work on Windows: see bpo-38324 (msg361830) del known_numerics['ps_AF'] @@ -228,6 +236,44 @@ def test_alt_digits_nl_langinfo(self): if not tested: self.skipTest('no suitable locales') + @unittest.skipUnless(nl_langinfo, "nl_langinfo is not available") + @unittest.skipUnless(hasattr(locale, 'ERA'), "requires locale.ERA") + @unittest.skipIf( + support.is_emscripten or support.is_wasi, + "musl libc issue on Emscripten, bpo-46390" + ) + def test_era_nl_langinfo(self): + # Test nl_langinfo(ERA) + tested = False + for loc in candidate_locales: + with self.subTest(locale=loc): + try: + setlocale(LC_TIME, loc) + setlocale(LC_CTYPE, loc) + except Error: + self.skipTest(f'no locale {loc!r}') + continue + + with self.subTest(locale=loc): + era = nl_langinfo(locale.ERA) + self.assertIsInstance(era, str) + if era: + self.assertEqual(era.count(':'), (era.count(';') + 1) * 5, era) + + loc1 = loc.split('.', 1)[0] + if loc1 in known_era: + count, sample = known_era[loc1] + if count: + if not era: + self.skipTest(f'ERA is not set for locale {loc!r} on this platform') + self.assertGreaterEqual(era.count(';') + 1, count) + self.assertIn(sample, era) + else: + self.assertEqual(era, '') + tested = True + if not tested: + self.skipTest('no suitable locales') + def test_float_parsing(self): # Bug #1391872: Test whether float parsing is okay on European # locales. diff --git a/Misc/NEWS.d/next/Library/2024-11-12-13-14-47.gh-issue-126727.5Eqfqd.rst b/Misc/NEWS.d/next/Library/2024-11-12-13-14-47.gh-issue-126727.5Eqfqd.rst new file mode 100644 index 00000000000000..7bec8a6b7a830a --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-11-12-13-14-47.gh-issue-126727.5Eqfqd.rst @@ -0,0 +1,3 @@ +``locale.nl_langinfo(locale.ERA)`` now returns multiple era description +segments separated by semicolons. Previously it only returned the first +segment on platforms with Glibc. diff --git a/Modules/_localemodule.c b/Modules/_localemodule.c index 53ebb57d23ae07..db8194372dae49 100644 --- a/Modules/_localemodule.c +++ b/Modules/_localemodule.c @@ -595,6 +595,37 @@ static struct langinfo_constant{ {0, 0} }; +#ifdef __GLIBC__ +#if defined(ALT_DIGITS) || defined(ERA) +static PyObject * +decode_strings(const char *result, size_t max_count) +{ + /* Convert a sequence of NUL-separated C strings to a Python string + * containing semicolon separated items. */ + size_t i = 0; + size_t count = 0; + for (; count < max_count && result[i]; count++) { + i += strlen(result + i) + 1; + } + char *buf = PyMem_Malloc(i); + if (buf == NULL) { + PyErr_NoMemory(); + return NULL; + } + memcpy(buf, result, i); + /* Replace all NULs with semicolons. */ + i = 0; + while (--count) { + i += strlen(buf + i); + buf[i++] = ';'; + } + PyObject *pyresult = PyUnicode_DecodeLocale(buf, NULL); + PyMem_Free(buf); + return pyresult; +} +#endif +#endif + /*[clinic input] _locale.nl_langinfo @@ -620,32 +651,18 @@ _locale_nl_langinfo_impl(PyObject *module, int item) result = result != NULL ? result : ""; PyObject *pyresult; #ifdef __GLIBC__ + /* According to the POSIX specification the result must be + * a sequence of semicolon-separated strings. + * But in Glibc they are NUL-separated. */ #ifdef ALT_DIGITS if (item == ALT_DIGITS && *result) { - /* According to the POSIX specification the result must be - * a sequence of up to 100 semicolon-separated strings. - * But in Glibc they are NUL-separated. */ - Py_ssize_t i = 0; - int count = 0; - for (; count < 100 && result[i]; count++) { - i += strlen(result + i) + 1; - } - char *buf = PyMem_Malloc(i); - if (buf == NULL) { - PyErr_NoMemory(); - pyresult = NULL; - } - else { - memcpy(buf, result, i); - /* Replace all NULs with semicolons. */ - i = 0; - while (--count) { - i += strlen(buf + i); - buf[i++] = ';'; - } - pyresult = PyUnicode_DecodeLocale(buf, NULL); - PyMem_Free(buf); - } + pyresult = decode_strings(result, 100); + } + else +#endif +#ifdef ERA + if (item == ERA && *result) { + pyresult = decode_strings(result, SIZE_MAX); } else #endif From 4b705f50d1c65536e19048b32397e8118d92558d Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Fri, 22 Nov 2024 04:42:46 +0100 Subject: [PATCH 226/269] [3.12] GH-126766: `url2pathname()`: handle 'localhost' authority (GH-127129) (#127131) GH-126766: `url2pathname()`: handle 'localhost' authority (GH-127129) Discard any 'localhost' authority from the beginning of a `file:` URI. As a result, file URIs like `//localhost/etc/hosts` are correctly decoded as `/etc/hosts`. (cherry picked from commit ebf564a1d3e2e81b9846535114e481d6096443d2) Co-authored-by: Barney Gale --- Lib/nturl2path.py | 11 +++++++---- Lib/test/test_urllib.py | 4 +++- Lib/urllib/request.py | 3 +++ .../2024-11-22-02-31-55.gh-issue-126766.jfkhBH.rst | 2 ++ 4 files changed, 15 insertions(+), 5 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-11-22-02-31-55.gh-issue-126766.jfkhBH.rst diff --git a/Lib/nturl2path.py b/Lib/nturl2path.py index 255eb2f547c2ce..6e0d26c129cb99 100644 --- a/Lib/nturl2path.py +++ b/Lib/nturl2path.py @@ -15,14 +15,17 @@ def url2pathname(url): # become # C:\foo\bar\spam.foo import string, urllib.parse + if url[:3] == '///': + # URL has an empty authority section, so the path begins on the third + # character. + url = url[2:] + elif url[:12] == '//localhost/': + # Skip past 'localhost' authority. + url = url[11:] # Windows itself uses ":" even in URLs. url = url.replace(':', '|') if not '|' in url: # No drive specifier, just convert slashes - if url[:3] == '///': - # URL has an empty authority section, so the path begins on the - # third character. - url = url[2:] # make sure not to convert quoted slashes :-) return urllib.parse.unquote(url.replace('/', '\\')) comp = url.split('|') diff --git a/Lib/test/test_urllib.py b/Lib/test/test_urllib.py index b06855e7b3a930..4520f8c85e6b03 100644 --- a/Lib/test/test_urllib.py +++ b/Lib/test/test_urllib.py @@ -1604,6 +1604,8 @@ def test_url2pathname_win(self): # Localhost paths self.assertEqual(fn('//localhost/C:/path/to/file'), 'C:\\path\\to\\file') self.assertEqual(fn('//localhost/C|/path/to/file'), 'C:\\path\\to\\file') + self.assertEqual(fn('//localhost/path/to/file'), '\\path\\to\\file') + self.assertEqual(fn('//localhost//server/path/to/file'), '\\\\server\\path\\to\\file') # Percent-encoded forward slashes are preserved for backwards compatibility self.assertEqual(fn('C:/foo%2fbar'), 'C:\\foo/bar') self.assertEqual(fn('//server/share/foo%2fbar'), '\\\\server\\share\\foo/bar') @@ -1622,7 +1624,7 @@ def test_url2pathname_posix(self): self.assertEqual(fn('//foo/bar'), '//foo/bar') self.assertEqual(fn('///foo/bar'), '/foo/bar') self.assertEqual(fn('////foo/bar'), '//foo/bar') - self.assertEqual(fn('//localhost/foo/bar'), '//localhost/foo/bar') + self.assertEqual(fn('//localhost/foo/bar'), '/foo/bar') @unittest.skipUnless(os_helper.FS_NONASCII, 'need os_helper.FS_NONASCII') def test_url2pathname_nonascii(self): diff --git a/Lib/urllib/request.py b/Lib/urllib/request.py index c89e217b9de1b7..c521d96d953883 100644 --- a/Lib/urllib/request.py +++ b/Lib/urllib/request.py @@ -1685,6 +1685,9 @@ def url2pathname(pathname): # URL has an empty authority section, so the path begins on the # third character. pathname = pathname[2:] + elif pathname[:12] == '//localhost/': + # Skip past 'localhost' authority. + pathname = pathname[11:] encoding = sys.getfilesystemencoding() errors = sys.getfilesystemencodeerrors() return unquote(pathname, encoding=encoding, errors=errors) diff --git a/Misc/NEWS.d/next/Library/2024-11-22-02-31-55.gh-issue-126766.jfkhBH.rst b/Misc/NEWS.d/next/Library/2024-11-22-02-31-55.gh-issue-126766.jfkhBH.rst new file mode 100644 index 00000000000000..998c99bf4358d5 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-11-22-02-31-55.gh-issue-126766.jfkhBH.rst @@ -0,0 +1,2 @@ +Fix issue where :func:`urllib.request.url2pathname` failed to discard any +'localhost' authority present in the URL. From c470e822bf6b8cde65c61323d0d7892c5df37326 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Fri, 22 Nov 2024 05:37:51 +0100 Subject: [PATCH 227/269] [3.12] GH-127078: `url2pathname()`: handle extra slash before UNC drive in URL path (GH-127132) (#127136) GH-127078: `url2pathname()`: handle extra slash before UNC drive in URL path (GH-127132) Decode a file URI like `file://///server/share` as a UNC path like `\\server\share`. This form of file URI is created by software the simply prepends `file:///` to any absolute Windows path. (cherry picked from commit 8c98ed846a7d7e50c4cf06f823d94737144dcf6a) Co-authored-by: Barney Gale --- Lib/nturl2path.py | 3 +++ Lib/test/test_urllib.py | 2 +- .../Library/2024-11-22-03-40-02.gh-issue-127078.gI_PaP.rst | 2 ++ 3 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2024-11-22-03-40-02.gh-issue-127078.gI_PaP.rst diff --git a/Lib/nturl2path.py b/Lib/nturl2path.py index 6e0d26c129cb99..757fd01bec8223 100644 --- a/Lib/nturl2path.py +++ b/Lib/nturl2path.py @@ -22,6 +22,9 @@ def url2pathname(url): elif url[:12] == '//localhost/': # Skip past 'localhost' authority. url = url[11:] + if url[:3] == '///': + # Skip past extra slash before UNC drive in URL path. + url = url[1:] # Windows itself uses ":" even in URLs. url = url.replace(':', '|') if not '|' in url: diff --git a/Lib/test/test_urllib.py b/Lib/test/test_urllib.py index 4520f8c85e6b03..4bdbc19c1ffb7a 100644 --- a/Lib/test/test_urllib.py +++ b/Lib/test/test_urllib.py @@ -1600,7 +1600,7 @@ def test_url2pathname_win(self): # UNC paths self.assertEqual(fn('//server/path/to/file'), '\\\\server\\path\\to\\file') self.assertEqual(fn('////server/path/to/file'), '\\\\server\\path\\to\\file') - self.assertEqual(fn('/////server/path/to/file'), '\\\\\\server\\path\\to\\file') + self.assertEqual(fn('/////server/path/to/file'), '\\\\server\\path\\to\\file') # Localhost paths self.assertEqual(fn('//localhost/C:/path/to/file'), 'C:\\path\\to\\file') self.assertEqual(fn('//localhost/C|/path/to/file'), 'C:\\path\\to\\file') diff --git a/Misc/NEWS.d/next/Library/2024-11-22-03-40-02.gh-issue-127078.gI_PaP.rst b/Misc/NEWS.d/next/Library/2024-11-22-03-40-02.gh-issue-127078.gI_PaP.rst new file mode 100644 index 00000000000000..a84c06f3c7a273 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-11-22-03-40-02.gh-issue-127078.gI_PaP.rst @@ -0,0 +1,2 @@ +Fix issue where :func:`urllib.request.url2pathname` failed to discard an +extra slash before a UNC drive in the URL path on Windows. From caad9047cb13a4e60eb708ce07578576ce9a84c9 Mon Sep 17 00:00:00 2001 From: Jun Komoda <45822440+junkmd@users.noreply.github.com> Date: Fri, 22 Nov 2024 22:08:37 +0900 Subject: [PATCH 228/269] [3.12] gh-127082: Replace "Windows only" with the `availability: Windows` in `ctypes` doc (GH-127099) (#127145) (cherry picked from commit 3c770e3f0978d825c5ebea98fcd654660e7e135f) --- Doc/library/ctypes.rst | 59 ++++++++++++++++++++++++++++++++---------- 1 file changed, 45 insertions(+), 14 deletions(-) diff --git a/Doc/library/ctypes.rst b/Doc/library/ctypes.rst index 9ebf9d8f751a96..4b5c8f8b7c1257 100644 --- a/Doc/library/ctypes.rst +++ b/Doc/library/ctypes.rst @@ -1390,13 +1390,15 @@ way is to instantiate one of the following classes: .. class:: OleDLL(name, mode=DEFAULT_MODE, handle=None, use_errno=False, use_last_error=False, winmode=None) - Windows only: Instances of this class represent loaded shared libraries, + Instances of this class represent loaded shared libraries, functions in these libraries use the ``stdcall`` calling convention, and are assumed to return the windows specific :class:`HRESULT` code. :class:`HRESULT` values contain information specifying whether the function call failed or succeeded, together with additional error code. If the return value signals a failure, an :class:`OSError` is automatically raised. + .. availability:: Windows + .. versionchanged:: 3.3 :exc:`WindowsError` used to be raised, which is now an alias of :exc:`OSError`. @@ -1408,14 +1410,17 @@ way is to instantiate one of the following classes: .. class:: WinDLL(name, mode=DEFAULT_MODE, handle=None, use_errno=False, use_last_error=False, winmode=None) - Windows only: Instances of this class represent loaded shared libraries, + Instances of this class represent loaded shared libraries, functions in these libraries use the ``stdcall`` calling convention, and are assumed to return :c:expr:`int` by default. + .. availability:: Windows + .. versionchanged:: 3.12 The *name* parameter can now be a :term:`path-like object`. + The Python :term:`global interpreter lock` is released before calling any function exported by these libraries, and reacquired afterwards. @@ -1551,13 +1556,17 @@ These prefabricated library loaders are available: .. data:: windll :noindex: - Windows only: Creates :class:`WinDLL` instances. + Creates :class:`WinDLL` instances. + + .. availability:: Windows .. data:: oledll :noindex: - Windows only: Creates :class:`OleDLL` instances. + Creates :class:`OleDLL` instances. + + .. availability:: Windows .. data:: pydll @@ -1729,11 +1738,13 @@ See :ref:`ctypes-callback-functions` for examples. .. function:: WINFUNCTYPE(restype, *argtypes, use_errno=False, use_last_error=False) - Windows only: The returned function prototype creates functions that use the + The returned function prototype creates functions that use the ``stdcall`` calling convention. The function will release the GIL during the call. *use_errno* and *use_last_error* have the same meaning as above. + .. availability:: Windows + .. function:: PYFUNCTYPE(restype, *argtypes) @@ -1959,17 +1970,21 @@ Utility functions .. function:: DllCanUnloadNow() - Windows only: This function is a hook which allows implementing in-process + This function is a hook which allows implementing in-process COM servers with ctypes. It is called from the DllCanUnloadNow function that the _ctypes extension dll exports. + .. availability:: Windows + .. function:: DllGetClassObject() - Windows only: This function is a hook which allows implementing in-process + This function is a hook which allows implementing in-process COM servers with ctypes. It is called from the DllGetClassObject function that the ``_ctypes`` extension dll exports. + .. availability:: Windows + .. function:: find_library(name) :module: ctypes.util @@ -1985,7 +2000,7 @@ Utility functions .. function:: find_msvcrt() :module: ctypes.util - Windows only: return the filename of the VC runtime library used by Python, + Returns the filename of the VC runtime library used by Python, and by the extension modules. If the name of the library cannot be determined, ``None`` is returned. @@ -1993,20 +2008,27 @@ Utility functions with a call to the ``free(void *)``, it is important that you use the function in the same library that allocated the memory. + .. availability:: Windows + .. function:: FormatError([code]) - Windows only: Returns a textual description of the error code *code*. If no + Returns a textual description of the error code *code*. If no error code is specified, the last error code is used by calling the Windows api function GetLastError. + .. availability:: Windows + .. function:: GetLastError() - Windows only: Returns the last error code set by Windows in the calling thread. + Returns the last error code set by Windows in the calling thread. This function calls the Windows ``GetLastError()`` function directly, it does not return the ctypes-private copy of the error code. + .. availability:: Windows + + .. function:: get_errno() Returns the current value of the ctypes-private copy of the system @@ -2016,11 +2038,14 @@ Utility functions .. function:: get_last_error() - Windows only: returns the current value of the ctypes-private copy of the system + Returns the current value of the ctypes-private copy of the system :data:`!LastError` variable in the calling thread. + .. availability:: Windows + .. audit-event:: ctypes.get_last_error "" ctypes.get_last_error + .. function:: memmove(dst, src, count) Same as the standard C memmove library function: copies *count* bytes from @@ -2069,10 +2094,12 @@ Utility functions .. function:: set_last_error(value) - Windows only: set the current value of the ctypes-private copy of the system + Sets the current value of the ctypes-private copy of the system :data:`!LastError` variable in the calling thread to *value* and return the previous value. + .. availability:: Windows + .. audit-event:: ctypes.set_last_error error ctypes.set_last_error @@ -2093,12 +2120,14 @@ Utility functions .. function:: WinError(code=None, descr=None) - Windows only: this function is probably the worst-named thing in ctypes. It + This function is probably the worst-named thing in ctypes. It creates an instance of :exc:`OSError`. If *code* is not specified, ``GetLastError`` is called to determine the error code. If *descr* is not specified, :func:`FormatError` is called to get a textual description of the error. + .. availability:: Windows + .. versionchanged:: 3.3 An instance of :exc:`WindowsError` used to be created, which is now an alias of :exc:`OSError`. @@ -2438,9 +2467,11 @@ These are the fundamental ctypes data types: .. class:: HRESULT - Windows only: Represents a :c:type:`!HRESULT` value, which contains success or + Represents a :c:type:`!HRESULT` value, which contains success or error information for a function or method call. + .. availability:: Windows + .. class:: py_object From 32bc8e83db3bcca7138dbd23e7925c0a5217c0a0 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Fri, 22 Nov 2024 15:13:43 +0100 Subject: [PATCH 229/269] [3.12] GH-122679: Add `register()` to argparse docs (GH-126939) (GH-127148) (cherry picked from commit fcfdb55465636afc256bc29781b283404d88e6ca) Co-authored-by: Savannah Ostrowski --- Doc/library/argparse.rst | 36 +++++++++++++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/Doc/library/argparse.rst b/Doc/library/argparse.rst index b142135f6e635b..00d7c384946cf1 100644 --- a/Doc/library/argparse.rst +++ b/Doc/library/argparse.rst @@ -750,7 +750,8 @@ Only actions that consume command-line arguments (e.g. ``'store'``, The recommended way to create a custom action is to extend :class:`Action`, overriding the :meth:`!__call__` method and optionally the :meth:`!__init__` and -:meth:`!format_usage` methods. +:meth:`!format_usage` methods. You can also register custom actions using the +:meth:`~ArgumentParser.register` method and reference them by their registered name. An example of a custom action:: @@ -971,10 +972,11 @@ necessary type-checking and type conversions to be performed. If the type_ keyword is used with the default_ keyword, the type converter is only applied if the default is a string. -The argument to ``type`` can be any callable that accepts a single string. +The argument to ``type`` can be a callable that accepts a single string or +the name of a registered type (see :meth:`~ArgumentParser.register`) If the function raises :exc:`ArgumentTypeError`, :exc:`TypeError`, or :exc:`ValueError`, the exception is caught and a nicely formatted error -message is displayed. No other exception types are handled. +message is displayed. Other exception types are not handled. Common built-in types and functions can be used as type converters: @@ -2058,6 +2060,34 @@ Intermixed parsing .. versionadded:: 3.7 +Registering custom types or actions +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. method:: ArgumentParser.register(registry_name, value, object) + + Sometimes it's desirable to use a custom string in error messages to provide + more user-friendly output. In these cases, :meth:`!register` can be used to + register custom actions or types with a parser and allow you to reference the + type by their registered name instead of their callable name. + + The :meth:`!register` method accepts three arguments - a *registry_name*, + specifying the internal registry where the object will be stored (e.g., + ``action``, ``type``), *value*, which is the key under which the object will + be registered, and object, the callable to be registered. + + The following example shows how to register a custom type with a parser:: + + >>> import argparse + >>> parser = argparse.ArgumentParser() + >>> parser.register('type', 'hexadecimal integer', lambda s: int(s, 16)) + >>> parser.add_argument('--foo', type='hexadecimal integer') + _StoreAction(option_strings=['--foo'], dest='foo', nargs=None, const=None, default=None, type='hexadecimal integer', choices=None, required=False, help=None, metavar=None, deprecated=False) + >>> parser.parse_args(['--foo', '0xFA']) + Namespace(foo=250) + >>> parser.parse_args(['--foo', '1.2']) + usage: PROG [-h] [--foo FOO] + PROG: error: argument --foo: invalid 'hexadecimal integer' value: '1.2' + Exceptions ---------- From 29648980d440e70ade4e0975b8d6aa83214866ed Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Fri, 22 Nov 2024 18:33:50 +0200 Subject: [PATCH 230/269] [3.12] gh-127001: Fix PATHEXT issues in shutil.which() on Windows (GH-127035) (GH-127158) * Name without a PATHEXT extension is only searched if the mode does not include X_OK. * Support multi-component PATHEXT extensions (e.g. ".foo.bar"). * Support files without extensions in PATHEXT contains dot-only extension (".", "..", etc). * Support PATHEXT extensions that end with a dot (e.g. ".foo."). (cherry picked from commit 8899e85de100557899da05f0b37867a371a73800) --- Doc/library/shutil.rst | 6 - Lib/shutil.py | 22 +- Lib/test/test_shutil.py | 515 ++++++++++-------- ...-11-22-10-42-34.gh-issue-127035.UnbDlr.rst | 4 + 4 files changed, 301 insertions(+), 246 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-11-22-10-42-34.gh-issue-127035.UnbDlr.rst diff --git a/Doc/library/shutil.rst b/Doc/library/shutil.rst index 028778d56518f8..08128f8711b451 100644 --- a/Doc/library/shutil.rst +++ b/Doc/library/shutil.rst @@ -481,12 +481,6 @@ Directory and files operations or ends with an extension that is in ``PATHEXT``; and filenames that have no extension can now be found. - .. versionchanged:: 3.12.1 - On Windows, if *mode* includes ``os.X_OK``, executables with an - extension in ``PATHEXT`` will be preferred over executables without a - matching extension. - This brings behavior closer to that of Python 3.11. - .. exception:: Error This exception collects exceptions that are raised during a multi-file diff --git a/Lib/shutil.py b/Lib/shutil.py index 20ad1cb5684b25..2d2856912893bd 100644 --- a/Lib/shutil.py +++ b/Lib/shutil.py @@ -1534,21 +1534,21 @@ def which(cmd, mode=os.F_OK | os.X_OK, path=None): if sys.platform == "win32": # PATHEXT is necessary to check on Windows. pathext_source = os.getenv("PATHEXT") or _WIN_DEFAULT_PATHEXT - pathext = [ext for ext in pathext_source.split(os.pathsep) if ext] + pathext = pathext_source.split(os.pathsep) + pathext = [ext.rstrip('.') for ext in pathext if ext] if use_bytes: pathext = [os.fsencode(ext) for ext in pathext] - files = ([cmd] + [cmd + ext for ext in pathext]) + files = [cmd + ext for ext in pathext] - # gh-109590. If we are looking for an executable, we need to look - # for a PATHEXT match. The first cmd is the direct match - # (e.g. python.exe instead of python) - # Check that direct match first if and only if the extension is in PATHEXT - # Otherwise check it last - suffix = os.path.splitext(files[0])[1].upper() - if mode & os.X_OK and not any(suffix == ext.upper() for ext in pathext): - files.append(files.pop(0)) + # If X_OK in mode, simulate the cmd.exe behavior: look at direct + # match if and only if the extension is in PATHEXT. + # If X_OK not in mode, simulate the first result of where.exe: + # always look at direct match before a PATHEXT match. + normcmd = cmd.upper() + if not (mode & os.X_OK) or any(normcmd.endswith(ext.upper()) for ext in pathext): + files.insert(0, cmd) else: # On other platforms you don't have things like PATHEXT to tell you # what file suffixes are executable, so just pass on cmd as-is. @@ -1557,7 +1557,7 @@ def which(cmd, mode=os.F_OK | os.X_OK, path=None): seen = set() for dir in path: normdir = os.path.normcase(dir) - if not normdir in seen: + if normdir not in seen: seen.add(normdir) for thefile in files: name = os.path.join(dir, thefile) diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py index 7bc5d12e09c057..c553ea09e07d55 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -71,18 +71,17 @@ def wrap(*args, **kwargs): os.rename = builtin_rename return wrap -def write_file(path, content, binary=False): +def create_file(path, content=b''): """Write *content* to a file located at *path*. If *path* is a tuple instead of a string, os.path.join will be used to - make a path. If *binary* is true, the file will be opened in binary - mode. + make a path. """ if isinstance(path, tuple): path = os.path.join(*path) - mode = 'wb' if binary else 'w' - encoding = None if binary else "utf-8" - with open(path, mode, encoding=encoding) as fp: + if isinstance(content, str): + content = content.encode() + with open(path, 'xb') as fp: fp.write(content) def write_test_file(path, size): @@ -191,7 +190,7 @@ def test_rmtree_works_on_bytes(self): tmp = self.mkdtemp() victim = os.path.join(tmp, 'killme') os.mkdir(victim) - write_file(os.path.join(victim, 'somefile'), 'foo') + create_file(os.path.join(victim, 'somefile'), 'foo') victim = os.fsencode(victim) self.assertIsInstance(victim, bytes) shutil.rmtree(victim) @@ -243,7 +242,7 @@ def test_rmtree_works_on_symlinks(self): for d in dir1, dir2, dir3: os.mkdir(d) file1 = os.path.join(tmp, 'file1') - write_file(file1, 'foo') + create_file(file1, 'foo') link1 = os.path.join(dir1, 'link1') os.symlink(dir2, link1) link2 = os.path.join(dir1, 'link2') @@ -305,7 +304,7 @@ def test_rmtree_works_on_junctions(self): for d in dir1, dir2, dir3: os.mkdir(d) file1 = os.path.join(tmp, 'file1') - write_file(file1, 'foo') + create_file(file1, 'foo') link1 = os.path.join(dir1, 'link1') _winapi.CreateJunction(dir2, link1) link2 = os.path.join(dir1, 'link2') @@ -327,8 +326,8 @@ def test_rmtree_errors_onerror(self): # existing file tmpdir = self.mkdtemp() - write_file((tmpdir, "tstfile"), "") filename = os.path.join(tmpdir, "tstfile") + create_file(filename) with self.assertRaises(NotADirectoryError) as cm: shutil.rmtree(filename) self.assertEqual(cm.exception.filename, filename) @@ -359,8 +358,8 @@ def test_rmtree_errors_onexc(self): # existing file tmpdir = self.mkdtemp() - write_file((tmpdir, "tstfile"), "") filename = os.path.join(tmpdir, "tstfile") + create_file(filename) with self.assertRaises(NotADirectoryError) as cm: shutil.rmtree(filename) self.assertEqual(cm.exception.filename, filename) @@ -549,7 +548,7 @@ def raiser(fn, *args, **kwargs): os.lstat = raiser os.mkdir(TESTFN) - write_file((TESTFN, 'foo'), 'foo') + create_file((TESTFN, 'foo'), 'foo') shutil.rmtree(TESTFN) finally: os.lstat = orig_lstat @@ -622,7 +621,7 @@ def test_rmtree_with_dir_fd(self): self.addCleanup(os.close, dir_fd) os.mkdir(fullname) os.mkdir(os.path.join(fullname, 'subdir')) - write_file(os.path.join(fullname, 'subdir', 'somefile'), 'foo') + create_file(os.path.join(fullname, 'subdir', 'somefile'), 'foo') self.assertTrue(os.path.exists(fullname)) shutil.rmtree(victim, dir_fd=dir_fd) self.assertFalse(os.path.exists(fullname)) @@ -662,7 +661,7 @@ def test_rmtree_on_junction(self): src = os.path.join(TESTFN, 'cheese') dst = os.path.join(TESTFN, 'shop') os.mkdir(src) - open(os.path.join(src, 'spam'), 'wb').close() + create_file(os.path.join(src, 'spam')) _winapi.CreateJunction(src, dst) self.assertRaises(OSError, shutil.rmtree, dst) shutil.rmtree(dst, ignore_errors=True) @@ -704,9 +703,9 @@ def test_copytree_simple(self): dst_dir = os.path.join(self.mkdtemp(), 'destination') self.addCleanup(shutil.rmtree, src_dir) self.addCleanup(shutil.rmtree, os.path.dirname(dst_dir)) - write_file((src_dir, 'test.txt'), '123') + create_file((src_dir, 'test.txt'), '123') os.mkdir(os.path.join(src_dir, 'test_dir')) - write_file((src_dir, 'test_dir', 'test.txt'), '456') + create_file((src_dir, 'test_dir', 'test.txt'), '456') shutil.copytree(src_dir, dst_dir) self.assertTrue(os.path.isfile(os.path.join(dst_dir, 'test.txt'))) @@ -724,11 +723,11 @@ def test_copytree_dirs_exist_ok(self): self.addCleanup(shutil.rmtree, src_dir) self.addCleanup(shutil.rmtree, dst_dir) - write_file((src_dir, 'nonexisting.txt'), '123') + create_file((src_dir, 'nonexisting.txt'), '123') os.mkdir(os.path.join(src_dir, 'existing_dir')) os.mkdir(os.path.join(dst_dir, 'existing_dir')) - write_file((dst_dir, 'existing_dir', 'existing.txt'), 'will be replaced') - write_file((src_dir, 'existing_dir', 'existing.txt'), 'has been replaced') + create_file((dst_dir, 'existing_dir', 'existing.txt'), 'will be replaced') + create_file((src_dir, 'existing_dir', 'existing.txt'), 'has been replaced') shutil.copytree(src_dir, dst_dir, dirs_exist_ok=True) self.assertTrue(os.path.isfile(os.path.join(dst_dir, 'nonexisting.txt'))) @@ -751,7 +750,7 @@ def test_copytree_symlinks(self): sub_dir = os.path.join(src_dir, 'sub') os.mkdir(src_dir) os.mkdir(sub_dir) - write_file((src_dir, 'file.txt'), 'foo') + create_file((src_dir, 'file.txt'), 'foo') src_link = os.path.join(sub_dir, 'link') dst_link = os.path.join(dst_dir, 'sub/link') os.symlink(os.path.join(src_dir, 'file.txt'), @@ -782,16 +781,16 @@ def test_copytree_with_exclude(self): src_dir = self.mkdtemp() try: dst_dir = join(self.mkdtemp(), 'destination') - write_file((src_dir, 'test.txt'), '123') - write_file((src_dir, 'test.tmp'), '123') + create_file((src_dir, 'test.txt'), '123') + create_file((src_dir, 'test.tmp'), '123') os.mkdir(join(src_dir, 'test_dir')) - write_file((src_dir, 'test_dir', 'test.txt'), '456') + create_file((src_dir, 'test_dir', 'test.txt'), '456') os.mkdir(join(src_dir, 'test_dir2')) - write_file((src_dir, 'test_dir2', 'test.txt'), '456') + create_file((src_dir, 'test_dir2', 'test.txt'), '456') os.mkdir(join(src_dir, 'test_dir2', 'subdir')) os.mkdir(join(src_dir, 'test_dir2', 'subdir2')) - write_file((src_dir, 'test_dir2', 'subdir', 'test.txt'), '456') - write_file((src_dir, 'test_dir2', 'subdir2', 'test.py'), '456') + create_file((src_dir, 'test_dir2', 'subdir', 'test.txt'), '456') + create_file((src_dir, 'test_dir2', 'subdir2', 'test.py'), '456') # testing glob-like patterns try: @@ -850,7 +849,7 @@ def test_copytree_arg_types_of_ignore(self): os.mkdir(join(src_dir)) os.mkdir(join(src_dir, 'test_dir')) os.mkdir(os.path.join(src_dir, 'test_dir', 'subdir')) - write_file((src_dir, 'test_dir', 'subdir', 'test.txt'), '456') + create_file((src_dir, 'test_dir', 'subdir', 'test.txt'), '456') invokations = [] @@ -890,9 +889,9 @@ def test_copytree_retains_permissions(self): self.addCleanup(shutil.rmtree, tmp_dir) os.chmod(src_dir, 0o777) - write_file((src_dir, 'permissive.txt'), '123') + create_file((src_dir, 'permissive.txt'), '123') os.chmod(os.path.join(src_dir, 'permissive.txt'), 0o777) - write_file((src_dir, 'restrictive.txt'), '456') + create_file((src_dir, 'restrictive.txt'), '456') os.chmod(os.path.join(src_dir, 'restrictive.txt'), 0o600) restrictive_subdir = tempfile.mkdtemp(dir=src_dir) self.addCleanup(os_helper.rmtree, restrictive_subdir) @@ -935,8 +934,7 @@ def custom_cpfun(a, b): flag = [] src = self.mkdtemp() dst = tempfile.mktemp(dir=self.mkdtemp()) - with open(os.path.join(src, 'foo'), 'w', encoding='utf-8') as f: - f.close() + create_file(os.path.join(src, 'foo')) shutil.copytree(src, dst, copy_function=custom_cpfun) self.assertEqual(len(flag), 1) @@ -971,9 +969,9 @@ def test_copytree_named_pipe(self): def test_copytree_special_func(self): src_dir = self.mkdtemp() dst_dir = os.path.join(self.mkdtemp(), 'destination') - write_file((src_dir, 'test.txt'), '123') + create_file((src_dir, 'test.txt'), '123') os.mkdir(os.path.join(src_dir, 'test_dir')) - write_file((src_dir, 'test_dir', 'test.txt'), '456') + create_file((src_dir, 'test_dir', 'test.txt'), '456') copied = [] def _copy(src, dst): @@ -986,7 +984,7 @@ def _copy(src, dst): def test_copytree_dangling_symlinks(self): src_dir = self.mkdtemp() valid_file = os.path.join(src_dir, 'test.txt') - write_file(valid_file, 'abc') + create_file(valid_file, 'abc') dir_a = os.path.join(src_dir, 'dir_a') os.mkdir(dir_a) for d in src_dir, dir_a: @@ -1014,8 +1012,7 @@ def test_copytree_symlink_dir(self): src_dir = self.mkdtemp() dst_dir = os.path.join(self.mkdtemp(), 'destination') os.mkdir(os.path.join(src_dir, 'real_dir')) - with open(os.path.join(src_dir, 'real_dir', 'test.txt'), 'wb'): - pass + create_file(os.path.join(src_dir, 'real_dir', 'test.txt')) os.symlink(os.path.join(src_dir, 'real_dir'), os.path.join(src_dir, 'link_to_dir'), target_is_directory=True) @@ -1035,7 +1032,7 @@ def test_copytree_return_value(self): dst_dir = src_dir + "dest" self.addCleanup(shutil.rmtree, dst_dir, True) src = os.path.join(src_dir, 'foo') - write_file(src, 'foo') + create_file(src, 'foo') rv = shutil.copytree(src_dir, dst_dir) self.assertEqual(['foo'], os.listdir(rv)) @@ -1047,7 +1044,7 @@ def test_copytree_subdirectory(self): dst_dir = os.path.join(src_dir, "somevendor", "1.0") os.makedirs(src_dir) src = os.path.join(src_dir, 'pol') - write_file(src, 'pol') + create_file(src, 'pol') rv = shutil.copytree(src_dir, dst_dir) self.assertEqual(['pol'], os.listdir(rv)) @@ -1062,8 +1059,8 @@ def test_copymode_follow_symlinks(self): dst = os.path.join(tmp_dir, 'bar') src_link = os.path.join(tmp_dir, 'baz') dst_link = os.path.join(tmp_dir, 'quux') - write_file(src, 'foo') - write_file(dst, 'foo') + create_file(src, 'foo') + create_file(dst, 'foo') os.symlink(src, src_link) os.symlink(dst, dst_link) os.chmod(src, stat.S_IRWXU|stat.S_IRWXG) @@ -1095,8 +1092,8 @@ def test_copymode_symlink_to_symlink(self): dst = os.path.join(tmp_dir, 'bar') src_link = os.path.join(tmp_dir, 'baz') dst_link = os.path.join(tmp_dir, 'quux') - write_file(src, 'foo') - write_file(dst, 'foo') + create_file(src, 'foo') + create_file(dst, 'foo') os.symlink(src, src_link) os.symlink(dst, dst_link) os.chmod(src, stat.S_IRWXU|stat.S_IRWXG) @@ -1126,8 +1123,8 @@ def test_copymode_symlink_to_symlink_wo_lchmod(self): dst = os.path.join(tmp_dir, 'bar') src_link = os.path.join(tmp_dir, 'baz') dst_link = os.path.join(tmp_dir, 'quux') - write_file(src, 'foo') - write_file(dst, 'foo') + create_file(src, 'foo') + create_file(dst, 'foo') os.symlink(src, src_link) os.symlink(dst, dst_link) shutil.copymode(src_link, dst_link, follow_symlinks=False) # silent fail @@ -1141,11 +1138,11 @@ def test_copystat_symlinks(self): dst = os.path.join(tmp_dir, 'bar') src_link = os.path.join(tmp_dir, 'baz') dst_link = os.path.join(tmp_dir, 'qux') - write_file(src, 'foo') + create_file(src, 'foo') src_stat = os.stat(src) os.utime(src, (src_stat.st_atime, src_stat.st_mtime - 42.0)) # ensure different mtimes - write_file(dst, 'bar') + create_file(dst, 'bar') self.assertNotEqual(os.stat(src).st_mtime, os.stat(dst).st_mtime) os.symlink(src, src_link) os.symlink(dst, dst_link) @@ -1185,8 +1182,8 @@ def test_copystat_handles_harmless_chflags_errors(self): tmpdir = self.mkdtemp() file1 = os.path.join(tmpdir, 'file1') file2 = os.path.join(tmpdir, 'file2') - write_file(file1, 'xxx') - write_file(file2, 'xxx') + create_file(file1, 'xxx') + create_file(file2, 'xxx') def make_chflags_raiser(err): ex = OSError() @@ -1212,9 +1209,9 @@ def _chflags_raiser(path, flags, *, follow_symlinks=True): def test_copyxattr(self): tmp_dir = self.mkdtemp() src = os.path.join(tmp_dir, 'foo') - write_file(src, 'foo') + create_file(src, 'foo') dst = os.path.join(tmp_dir, 'bar') - write_file(dst, 'bar') + create_file(dst, 'bar') # no xattr == no problem shutil._copyxattr(src, dst) @@ -1228,7 +1225,7 @@ def test_copyxattr(self): os.getxattr(dst, 'user.foo')) # check errors don't affect other attrs os.remove(dst) - write_file(dst, 'bar') + create_file(dst, 'bar') os_error = OSError(errno.EPERM, 'EPERM') def _raise_on_user_foo(fname, attr, val, **kwargs): @@ -1258,15 +1255,15 @@ def _raise_on_src(fname, *, follow_symlinks=True): # test that shutil.copystat copies xattrs src = os.path.join(tmp_dir, 'the_original') srcro = os.path.join(tmp_dir, 'the_original_ro') - write_file(src, src) - write_file(srcro, srcro) + create_file(src, src) + create_file(srcro, srcro) os.setxattr(src, 'user.the_value', b'fiddly') os.setxattr(srcro, 'user.the_value', b'fiddly') os.chmod(srcro, 0o444) dst = os.path.join(tmp_dir, 'the_copy') dstro = os.path.join(tmp_dir, 'the_copy_ro') - write_file(dst, dst) - write_file(dstro, dstro) + create_file(dst, dst) + create_file(dstro, dstro) shutil.copystat(src, dst) shutil.copystat(srcro, dstro) self.assertEqual(os.getxattr(dst, 'user.the_value'), b'fiddly') @@ -1282,13 +1279,13 @@ def test_copyxattr_symlinks(self): tmp_dir = self.mkdtemp() src = os.path.join(tmp_dir, 'foo') src_link = os.path.join(tmp_dir, 'baz') - write_file(src, 'foo') + create_file(src, 'foo') os.symlink(src, src_link) os.setxattr(src, 'trusted.foo', b'42') os.setxattr(src_link, 'trusted.foo', b'43', follow_symlinks=False) dst = os.path.join(tmp_dir, 'bar') dst_link = os.path.join(tmp_dir, 'qux') - write_file(dst, 'bar') + create_file(dst, 'bar') os.symlink(dst, dst_link) shutil._copyxattr(src_link, dst_link, follow_symlinks=False) self.assertEqual(os.getxattr(dst_link, 'trusted.foo', follow_symlinks=False), b'43') @@ -1301,7 +1298,7 @@ def test_copyxattr_symlinks(self): def _copy_file(self, method): fname = 'test.txt' tmpdir = self.mkdtemp() - write_file((tmpdir, fname), 'xxx') + create_file((tmpdir, fname), 'xxx') file1 = os.path.join(tmpdir, fname) tmpdir2 = self.mkdtemp() method(file1, tmpdir2) @@ -1320,7 +1317,7 @@ def test_copy_symlinks(self): src = os.path.join(tmp_dir, 'foo') dst = os.path.join(tmp_dir, 'bar') src_link = os.path.join(tmp_dir, 'baz') - write_file(src, 'foo') + create_file(src, 'foo') os.symlink(src, src_link) if hasattr(os, 'lchmod'): os.lchmod(src_link, stat.S_IRWXU | stat.S_IRWXO) @@ -1362,7 +1359,7 @@ def test_copy2_symlinks(self): src = os.path.join(tmp_dir, 'foo') dst = os.path.join(tmp_dir, 'bar') src_link = os.path.join(tmp_dir, 'baz') - write_file(src, 'foo') + create_file(src, 'foo') os.symlink(src, src_link) if hasattr(os, 'lchmod'): os.lchmod(src_link, stat.S_IRWXU | stat.S_IRWXO) @@ -1396,7 +1393,7 @@ def test_copy2_xattr(self): tmp_dir = self.mkdtemp() src = os.path.join(tmp_dir, 'foo') dst = os.path.join(tmp_dir, 'bar') - write_file(src, 'foo') + create_file(src, 'foo') os.setxattr(src, 'user.foo', b'42') shutil.copy2(src, dst) self.assertEqual( @@ -1410,7 +1407,7 @@ def test_copy_return_value(self): src_dir = self.mkdtemp() dst_dir = self.mkdtemp() src = os.path.join(src_dir, 'foo') - write_file(src, 'foo') + create_file(src, 'foo') rv = fn(src, dst_dir) self.assertEqual(rv, os.path.join(dst_dir, 'foo')) rv = fn(src, os.path.join(dst_dir, 'bar')) @@ -1427,7 +1424,7 @@ def _test_copy_dir(self, copy_func): src_file = os.path.join(src_dir, 'foo') dir2 = self.mkdtemp() dst = os.path.join(src_dir, 'does_not_exist/') - write_file(src_file, 'foo') + create_file(src_file, 'foo') if sys.platform == "win32": err = PermissionError else: @@ -1447,7 +1444,7 @@ def test_copyfile_symlinks(self): dst = os.path.join(tmp_dir, 'dst') dst_link = os.path.join(tmp_dir, 'dst_link') link = os.path.join(tmp_dir, 'link') - write_file(src, 'foo') + create_file(src, 'foo') os.symlink(src, link) # don't follow shutil.copyfile(link, dst_link, follow_symlinks=False) @@ -1464,8 +1461,7 @@ def test_dont_copy_file_onto_link_to_itself(self): src = os.path.join(TESTFN, 'cheese') dst = os.path.join(TESTFN, 'shop') try: - with open(src, 'w', encoding='utf-8') as f: - f.write('cheddar') + create_file(src, 'cheddar') try: os.link(src, dst) except PermissionError as e: @@ -1484,8 +1480,7 @@ def test_dont_copy_file_onto_symlink_to_itself(self): src = os.path.join(TESTFN, 'cheese') dst = os.path.join(TESTFN, 'shop') try: - with open(src, 'w', encoding='utf-8') as f: - f.write('cheddar') + create_file(src, 'cheddar') # Using `src` here would mean we end up with a symlink pointing # to TESTFN/TESTFN/cheese, while it should point at # TESTFN/cheese. @@ -1520,7 +1515,7 @@ def test_copyfile_return_value(self): dst_dir = self.mkdtemp() dst_file = os.path.join(dst_dir, 'bar') src_file = os.path.join(src_dir, 'foo') - write_file(src_file, 'foo') + create_file(src_file, 'foo') rv = shutil.copyfile(src_file, dst_file) self.assertTrue(os.path.exists(rv)) self.assertEqual(read_file(src_file), read_file(dst_file)) @@ -1530,7 +1525,7 @@ def test_copyfile_same_file(self): # are the same. src_dir = self.mkdtemp() src_file = os.path.join(src_dir, 'foo') - write_file(src_file, 'foo') + create_file(src_file, 'foo') self.assertRaises(SameFileError, shutil.copyfile, src_file, src_file) # But Error should work too, to stay backward compatible. self.assertRaises(Error, shutil.copyfile, src_file, src_file) @@ -1547,7 +1542,7 @@ def test_copyfile_nonexistent_dir(self): src_dir = self.mkdtemp() src_file = os.path.join(src_dir, 'foo') dst = os.path.join(src_dir, 'does_not_exist/') - write_file(src_file, 'foo') + create_file(src_file, 'foo') self.assertRaises(FileNotFoundError, shutil.copyfile, src_file, dst) def test_copyfile_copy_dir(self): @@ -1558,7 +1553,7 @@ def test_copyfile_copy_dir(self): src_file = os.path.join(src_dir, 'foo') dir2 = self.mkdtemp() dst = os.path.join(src_dir, 'does_not_exist/') - write_file(src_file, 'foo') + create_file(src_file, 'foo') if sys.platform == "win32": err = PermissionError else: @@ -1584,13 +1579,13 @@ def _create_files(self, base_dir='dist'): root_dir = self.mkdtemp() dist = os.path.join(root_dir, base_dir) os.makedirs(dist, exist_ok=True) - write_file((dist, 'file1'), 'xxx') - write_file((dist, 'file2'), 'xxx') + create_file((dist, 'file1'), 'xxx') + create_file((dist, 'file2'), 'xxx') os.mkdir(os.path.join(dist, 'sub')) - write_file((dist, 'sub', 'file3'), 'xxx') + create_file((dist, 'sub', 'file3'), 'xxx') os.mkdir(os.path.join(dist, 'sub2')) if base_dir: - write_file((root_dir, 'outer'), 'xxx') + create_file((root_dir, 'outer'), 'xxx') return root_dir, base_dir @support.requires_zlib() @@ -2170,7 +2165,7 @@ def test_disk_usage(self): def test_chown(self): dirname = self.mkdtemp() filename = tempfile.mktemp(dir=dirname) - write_file(filename, 'testing chown function') + create_file(filename, 'testing chown function') with self.assertRaises(ValueError): shutil.chown(filename) @@ -2231,37 +2226,41 @@ def check_chown(path, uid=None, gid=None): class TestWhich(BaseTest, unittest.TestCase): def setUp(self): - self.temp_dir = self.mkdtemp(prefix="Tmp") + temp_dir = self.mkdtemp(prefix="Tmp") + base_dir = os.path.join(temp_dir, TESTFN + '-basedir') + os.mkdir(base_dir) + self.dir = os.path.join(base_dir, TESTFN + '-dir') + os.mkdir(self.dir) + self.other_dir = os.path.join(base_dir, TESTFN + '-dir2') + os.mkdir(self.other_dir) # Give the temp_file an ".exe" suffix for all. # It's needed on Windows and not harmful on other platforms. - self.temp_file = tempfile.NamedTemporaryFile(dir=self.temp_dir, - prefix="Tmp", - suffix=".Exe") - os.chmod(self.temp_file.name, stat.S_IXUSR) - self.addCleanup(self.temp_file.close) - self.dir, self.file = os.path.split(self.temp_file.name) + self.file = TESTFN + '.Exe' + self.filepath = os.path.join(self.dir, self.file) + self.create_file(self.filepath) self.env_path = self.dir self.curdir = os.curdir self.ext = ".EXE" - def to_text_type(self, s): - ''' - In this class we're testing with str, so convert s to a str - ''' - if isinstance(s, bytes): - return s.decode() - return s + to_text_type = staticmethod(os.fsdecode) + + def create_file(self, path): + create_file(path) + os.chmod(path, 0o755) + + def assertNormEqual(self, actual, expected): + self.assertEqual(os.path.normcase(actual), os.path.normcase(expected)) def test_basic(self): # Given an EXE in a directory, it should be returned. rv = shutil.which(self.file, path=self.dir) - self.assertEqual(rv, self.temp_file.name) + self.assertEqual(rv, self.filepath) def test_absolute_cmd(self): # When given the fully qualified path to an executable that exists, # it should be returned. - rv = shutil.which(self.temp_file.name, path=self.temp_dir) - self.assertEqual(rv, self.temp_file.name) + rv = shutil.which(self.filepath, path=self.other_dir) + self.assertEqual(rv, self.filepath) def test_relative_cmd(self): # When given the relative path with a directory part to an executable @@ -2269,7 +2268,7 @@ def test_relative_cmd(self): base_dir, tail_dir = os.path.split(self.dir) relpath = os.path.join(tail_dir, self.file) with os_helper.change_cwd(path=base_dir): - rv = shutil.which(relpath, path=self.temp_dir) + rv = shutil.which(relpath, path=self.other_dir) self.assertEqual(rv, relpath) # But it shouldn't be searched in PATH directories (issue #16957). with os_helper.change_cwd(path=self.dir): @@ -2280,9 +2279,8 @@ def test_relative_cmd(self): "test is for non win32") def test_cwd_non_win32(self): # Issue #16957 - base_dir = os.path.dirname(self.dir) with os_helper.change_cwd(path=self.dir): - rv = shutil.which(self.file, path=base_dir) + rv = shutil.which(self.file, path=self.other_dir) # non-win32: shouldn't match in the current directory. self.assertIsNone(rv) @@ -2292,57 +2290,32 @@ def test_cwd_win32(self): base_dir = os.path.dirname(self.dir) with os_helper.change_cwd(path=self.dir): with unittest.mock.patch('shutil._win_path_needs_curdir', return_value=True): - rv = shutil.which(self.file, path=base_dir) + rv = shutil.which(self.file, path=self.other_dir) # Current directory implicitly on PATH self.assertEqual(rv, os.path.join(self.curdir, self.file)) with unittest.mock.patch('shutil._win_path_needs_curdir', return_value=False): - rv = shutil.which(self.file, path=base_dir) + rv = shutil.which(self.file, path=self.other_dir) # Current directory not on PATH self.assertIsNone(rv) @unittest.skipUnless(sys.platform == "win32", "test is for win32") def test_cwd_win32_added_before_all_other_path(self): - base_dir = pathlib.Path(os.fsdecode(self.dir)) - - elsewhere_in_path_dir = base_dir / 'dir1' - elsewhere_in_path_dir.mkdir() - match_elsewhere_in_path = elsewhere_in_path_dir / 'hello.exe' - match_elsewhere_in_path.touch() - - exe_in_cwd = base_dir / 'hello.exe' - exe_in_cwd.touch() - - with os_helper.change_cwd(path=base_dir): - with unittest.mock.patch('shutil._win_path_needs_curdir', return_value=True): - rv = shutil.which('hello.exe', path=elsewhere_in_path_dir) - - self.assertEqual(os.path.abspath(rv), os.path.abspath(exe_in_cwd)) - - @unittest.skipUnless(sys.platform == "win32", - "test is for win32") - def test_pathext_match_before_path_full_match(self): - base_dir = pathlib.Path(os.fsdecode(self.dir)) - dir1 = base_dir / 'dir1' - dir2 = base_dir / 'dir2' - dir1.mkdir() - dir2.mkdir() - - pathext_match = dir1 / 'hello.com.exe' - path_match = dir2 / 'hello.com' - pathext_match.touch() - path_match.touch() - - test_path = os.pathsep.join([str(dir1), str(dir2)]) - assert os.path.basename(shutil.which( - 'hello.com', path=test_path, mode = os.F_OK - )).lower() == 'hello.com.exe' + other_file_path = os.path.join(self.other_dir, self.file) + self.create_file(other_file_path) + with unittest.mock.patch('shutil._win_path_needs_curdir', return_value=True): + with os_helper.change_cwd(path=self.dir): + rv = shutil.which(self.file, path=self.other_dir) + self.assertEqual(rv, os.path.join(self.curdir, self.file)) + with os_helper.change_cwd(path=self.other_dir): + rv = shutil.which(self.file, path=self.dir) + self.assertEqual(rv, os.path.join(self.curdir, self.file)) @os_helper.skip_if_dac_override def test_non_matching_mode(self): # Set the file read-only and ask for writeable files. - os.chmod(self.temp_file.name, stat.S_IREAD) - if os.access(self.temp_file.name, os.W_OK): + os.chmod(self.filepath, stat.S_IREAD) + if os.access(self.filepath, os.W_OK): self.skipTest("can't set the file read-only") rv = shutil.which(self.file, path=self.dir, mode=os.W_OK) self.assertIsNone(rv) @@ -2364,13 +2337,13 @@ def test_pathext_checking(self): # Ask for the file without the ".exe" extension, then ensure that # it gets found properly with the extension. rv = shutil.which(self.file[:-4], path=self.dir) - self.assertEqual(rv, self.temp_file.name[:-4] + self.ext) + self.assertEqual(rv, self.filepath[:-4] + self.ext) def test_environ_path(self): with os_helper.EnvironmentVarGuard() as env: env['PATH'] = self.env_path rv = shutil.which(self.file) - self.assertEqual(rv, self.temp_file.name) + self.assertEqual(rv, self.filepath) def test_environ_path_empty(self): # PATH='': no match @@ -2384,12 +2357,9 @@ def test_environ_path_empty(self): self.assertIsNone(rv) def test_environ_path_cwd(self): - expected_cwd = os.path.basename(self.temp_file.name) + expected_cwd = self.file if sys.platform == "win32": - curdir = os.curdir - if isinstance(expected_cwd, bytes): - curdir = os.fsencode(curdir) - expected_cwd = os.path.join(curdir, expected_cwd) + expected_cwd = os.path.join(self.curdir, expected_cwd) # PATH=':': explicitly looks in the current directory with os_helper.EnvironmentVarGuard() as env: @@ -2414,14 +2384,14 @@ def test_environ_path_missing(self): create=True), \ support.swap_attr(os, 'defpath', self.dir): rv = shutil.which(self.file) - self.assertEqual(rv, self.temp_file.name) + self.assertEqual(rv, self.filepath) # with confstr with unittest.mock.patch('os.confstr', return_value=self.dir, \ create=True), \ support.swap_attr(os, 'defpath', ''): rv = shutil.which(self.file) - self.assertEqual(rv, self.temp_file.name) + self.assertEqual(rv, self.filepath) def test_empty_path(self): base_dir = os.path.dirname(self.dir) @@ -2439,50 +2409,88 @@ def test_empty_path_no_PATH(self): @unittest.skipUnless(sys.platform == "win32", 'test specific to Windows') def test_pathext(self): - ext = self.to_text_type(".xyz") - temp_filexyz = tempfile.NamedTemporaryFile(dir=self.temp_dir, - prefix=self.to_text_type("Tmp2"), suffix=ext) - os.chmod(temp_filexyz.name, stat.S_IXUSR) - self.addCleanup(temp_filexyz.close) - - # strip path and extension - program = os.path.basename(temp_filexyz.name) - program = os.path.splitext(program)[0] - + ext = '.xyz' + cmd = self.to_text_type(TESTFN2) + cmdext = cmd + self.to_text_type(ext) + filepath = os.path.join(self.dir, cmdext) + self.create_file(filepath) with os_helper.EnvironmentVarGuard() as env: - env['PATHEXT'] = ext if isinstance(ext, str) else ext.decode() - rv = shutil.which(program, path=self.temp_dir) - self.assertEqual(rv, temp_filexyz.name) + env['PATHEXT'] = ext + self.assertEqual(shutil.which(cmd, path=self.dir), filepath) + self.assertEqual(shutil.which(cmdext, path=self.dir), filepath) # Issue 40592: See https://bugs.python.org/issue40592 @unittest.skipUnless(sys.platform == "win32", 'test specific to Windows') def test_pathext_with_empty_str(self): - ext = self.to_text_type(".xyz") - temp_filexyz = tempfile.NamedTemporaryFile(dir=self.temp_dir, - prefix=self.to_text_type("Tmp2"), suffix=ext) - self.addCleanup(temp_filexyz.close) + ext = '.xyz' + cmd = self.to_text_type(TESTFN2) + cmdext = cmd + self.to_text_type(ext) + filepath = os.path.join(self.dir, cmdext) + self.create_file(filepath) + with os_helper.EnvironmentVarGuard() as env: + env['PATHEXT'] = ext + ';' # note the ; + self.assertEqual(shutil.which(cmd, path=self.dir), filepath) + self.assertEqual(shutil.which(cmdext, path=self.dir), filepath) - # strip path and extension - program = os.path.basename(temp_filexyz.name) - program = os.path.splitext(program)[0] + @unittest.skipUnless(sys.platform == "win32", 'test specific to Windows') + def test_pathext_with_multidot_extension(self): + ext = '.foo.bar' + cmd = self.to_text_type(TESTFN2) + cmdext = cmd + self.to_text_type(ext) + filepath = os.path.join(self.dir, cmdext) + self.create_file(filepath) + with os_helper.EnvironmentVarGuard() as env: + env['PATHEXT'] = ext + self.assertEqual(shutil.which(cmd, path=self.dir), filepath) + self.assertEqual(shutil.which(cmdext, path=self.dir), filepath) + @unittest.skipUnless(sys.platform == "win32", 'test specific to Windows') + def test_pathext_with_null_extension(self): + cmd = self.to_text_type(TESTFN2) + cmddot = cmd + self.to_text_type('.') + filepath = os.path.join(self.dir, cmd) + self.create_file(filepath) with os_helper.EnvironmentVarGuard() as env: - env['PATHEXT'] = f"{ext if isinstance(ext, str) else ext.decode()};" # note the ; - rv = shutil.which(program, path=self.temp_dir) - self.assertEqual(rv, temp_filexyz.name) + env['PATHEXT'] = '.xyz' + self.assertIsNone(shutil.which(cmd, path=self.dir)) + self.assertIsNone(shutil.which(cmddot, path=self.dir)) + env['PATHEXT'] = '.xyz;.' # note the . + self.assertEqual(shutil.which(cmd, path=self.dir), filepath) + self.assertEqual(shutil.which(cmddot, path=self.dir), + filepath + self.to_text_type('.')) + env['PATHEXT'] = '.xyz;..' # multiple dots + self.assertEqual(shutil.which(cmd, path=self.dir), filepath) + self.assertEqual(shutil.which(cmddot, path=self.dir), + filepath + self.to_text_type('.')) + + @unittest.skipUnless(sys.platform == "win32", 'test specific to Windows') + def test_pathext_extension_ends_with_dot(self): + ext = '.xyz' + cmd = self.to_text_type(TESTFN2) + cmdext = cmd + self.to_text_type(ext) + dot = self.to_text_type('.') + filepath = os.path.join(self.dir, cmdext) + self.create_file(filepath) + with os_helper.EnvironmentVarGuard() as env: + env['PATHEXT'] = ext + '.' + self.assertEqual(shutil.which(cmd, path=self.dir), filepath) # cmd.exe hangs here + self.assertEqual(shutil.which(cmdext, path=self.dir), filepath) + self.assertIsNone(shutil.which(cmd + dot, path=self.dir)) + self.assertIsNone(shutil.which(cmdext + dot, path=self.dir)) # See GH-75586 @unittest.skipUnless(sys.platform == "win32", 'test specific to Windows') def test_pathext_applied_on_files_in_path(self): + ext = '.xyz' + cmd = self.to_text_type(TESTFN2) + cmdext = cmd + self.to_text_type(ext) + filepath = os.path.join(self.dir, cmdext) + self.create_file(filepath) with os_helper.EnvironmentVarGuard() as env: - env["PATH"] = self.temp_dir if isinstance(self.temp_dir, str) else self.temp_dir.decode() - env["PATHEXT"] = ".test" - - test_path = os.path.join(self.temp_dir, self.to_text_type("test_program.test")) - open(test_path, 'w').close() - os.chmod(test_path, 0o755) - - self.assertEqual(shutil.which(self.to_text_type("test_program")), test_path) + env["PATH"] = os.fsdecode(self.dir) + env["PATHEXT"] = ext + self.assertEqual(shutil.which(cmd), filepath) + self.assertEqual(shutil.which(cmdext), filepath) # See GH-75586 @unittest.skipUnless(sys.platform == "win32", 'test specific to Windows') @@ -2498,49 +2506,107 @@ def test_win_path_needs_curdir(self): self.assertFalse(shutil._win_path_needs_curdir('dontcare', os.X_OK)) need_curdir_mock.assert_called_once_with('dontcare') - # See GH-109590 @unittest.skipUnless(sys.platform == "win32", 'test specific to Windows') - def test_pathext_preferred_for_execute(self): - with os_helper.EnvironmentVarGuard() as env: - env["PATH"] = self.temp_dir if isinstance(self.temp_dir, str) else self.temp_dir.decode() - env["PATHEXT"] = ".test" - - exe = os.path.join(self.temp_dir, self.to_text_type("test.exe")) - open(exe, 'w').close() - os.chmod(exe, 0o755) + def test_same_dir_with_pathext_extension(self): + cmd = self.file # with .exe extension + # full match + self.assertNormEqual(shutil.which(cmd, path=self.dir), self.filepath) + self.assertNormEqual(shutil.which(cmd, path=self.dir, mode=os.F_OK), + self.filepath) + + cmd2 = cmd + self.to_text_type('.com') # with .exe.com extension + other_file_path = os.path.join(self.dir, cmd2) + self.create_file(other_file_path) + + # full match + self.assertNormEqual(shutil.which(cmd, path=self.dir), self.filepath) + self.assertNormEqual(shutil.which(cmd, path=self.dir, mode=os.F_OK), + self.filepath) + self.assertNormEqual(shutil.which(cmd2, path=self.dir), other_file_path) + self.assertNormEqual(shutil.which(cmd2, path=self.dir, mode=os.F_OK), + other_file_path) - # default behavior allows a direct match if nothing in PATHEXT matches - self.assertEqual(shutil.which(self.to_text_type("test.exe")), exe) - - dot_test = os.path.join(self.temp_dir, self.to_text_type("test.exe.test")) - open(dot_test, 'w').close() - os.chmod(dot_test, 0o755) - - # now we have a PATHEXT match, so it take precedence - self.assertEqual(shutil.which(self.to_text_type("test.exe")), dot_test) - - # but if we don't use os.X_OK we don't change the order based off PATHEXT - # and therefore get the direct match. - self.assertEqual(shutil.which(self.to_text_type("test.exe"), mode=os.F_OK), exe) - - # See GH-109590 @unittest.skipUnless(sys.platform == "win32", 'test specific to Windows') - def test_pathext_given_extension_preferred(self): - with os_helper.EnvironmentVarGuard() as env: - env["PATH"] = self.temp_dir if isinstance(self.temp_dir, str) else self.temp_dir.decode() - env["PATHEXT"] = ".exe2;.exe" + def test_same_dir_without_pathext_extension(self): + cmd = self.file[:-4] # without .exe extension + # pathext match + self.assertNormEqual(shutil.which(cmd, path=self.dir), self.filepath) + self.assertNormEqual(shutil.which(cmd, path=self.dir, mode=os.F_OK), + self.filepath) + + # without extension + other_file_path = os.path.join(self.dir, cmd) + self.create_file(other_file_path) + + # pathext match if mode contains X_OK + self.assertNormEqual(shutil.which(cmd, path=self.dir), self.filepath) + # full match + self.assertNormEqual(shutil.which(cmd, path=self.dir, mode=os.F_OK), + other_file_path) + self.assertNormEqual(shutil.which(self.file, path=self.dir), self.filepath) + self.assertNormEqual(shutil.which(self.file, path=self.dir, mode=os.F_OK), + self.filepath) - exe = os.path.join(self.temp_dir, self.to_text_type("test.exe")) - open(exe, 'w').close() - os.chmod(exe, 0o755) - - exe2 = os.path.join(self.temp_dir, self.to_text_type("test.exe2")) - open(exe2, 'w').close() - os.chmod(exe2, 0o755) + @unittest.skipUnless(sys.platform == "win32", 'test specific to Windows') + def test_dir_order_with_pathext_extension(self): + cmd = self.file # with .exe extension + search_path = os.pathsep.join([os.fsdecode(self.other_dir), + os.fsdecode(self.dir)]) + # full match in the second directory + self.assertNormEqual(shutil.which(cmd, path=search_path), self.filepath) + self.assertNormEqual(shutil.which(cmd, path=search_path, mode=os.F_OK), + self.filepath) + + cmd2 = cmd + self.to_text_type('.com') # with .exe.com extension + other_file_path = os.path.join(self.other_dir, cmd2) + self.create_file(other_file_path) + + # pathext match in the first directory + self.assertNormEqual(shutil.which(cmd, path=search_path), other_file_path) + self.assertNormEqual(shutil.which(cmd, path=search_path, mode=os.F_OK), + other_file_path) + # full match in the first directory + self.assertNormEqual(shutil.which(cmd2, path=search_path), other_file_path) + self.assertNormEqual(shutil.which(cmd2, path=search_path, mode=os.F_OK), + other_file_path) + + # full match in the first directory + search_path = os.pathsep.join([os.fsdecode(self.dir), + os.fsdecode(self.other_dir)]) + self.assertEqual(shutil.which(cmd, path=search_path), self.filepath) + self.assertEqual(shutil.which(cmd, path=search_path, mode=os.F_OK), + self.filepath) - # even though .exe2 is preferred in PATHEXT, we matched directly to test.exe - self.assertEqual(shutil.which(self.to_text_type("test.exe")), exe) - self.assertEqual(shutil.which(self.to_text_type("test")), exe2) + @unittest.skipUnless(sys.platform == "win32", 'test specific to Windows') + def test_dir_order_without_pathext_extension(self): + cmd = self.file[:-4] # without .exe extension + search_path = os.pathsep.join([os.fsdecode(self.other_dir), + os.fsdecode(self.dir)]) + # pathext match in the second directory + self.assertNormEqual(shutil.which(cmd, path=search_path), self.filepath) + self.assertNormEqual(shutil.which(cmd, path=search_path, mode=os.F_OK), + self.filepath) + + # without extension + other_file_path = os.path.join(self.other_dir, cmd) + self.create_file(other_file_path) + + # pathext match in the second directory + self.assertNormEqual(shutil.which(cmd, path=search_path), self.filepath) + # full match in the first directory + self.assertNormEqual(shutil.which(cmd, path=search_path, mode=os.F_OK), + other_file_path) + # full match in the second directory + self.assertNormEqual(shutil.which(self.file, path=search_path), self.filepath) + self.assertNormEqual(shutil.which(self.file, path=search_path, mode=os.F_OK), + self.filepath) + + # pathext match in the first directory + search_path = os.pathsep.join([os.fsdecode(self.dir), + os.fsdecode(self.other_dir)]) + self.assertNormEqual(shutil.which(cmd, path=search_path), self.filepath) + self.assertNormEqual(shutil.which(cmd, path=search_path, mode=os.F_OK), + self.filepath) class TestWhichBytes(TestWhich): @@ -2548,18 +2614,12 @@ def setUp(self): TestWhich.setUp(self) self.dir = os.fsencode(self.dir) self.file = os.fsencode(self.file) - self.temp_file.name = os.fsencode(self.temp_file.name) - self.temp_dir = os.fsencode(self.temp_dir) + self.filepath = os.fsencode(self.filepath) + self.other_dir = os.fsencode(self.other_dir) self.curdir = os.fsencode(self.curdir) self.ext = os.fsencode(self.ext) - def to_text_type(self, s): - ''' - In this class we're testing with bytes, so convert s to a bytes - ''' - if isinstance(s, str): - return s.encode() - return s + to_text_type = staticmethod(os.fsencode) class TestMove(BaseTest, unittest.TestCase): @@ -2570,8 +2630,7 @@ def setUp(self): self.dst_dir = self.mkdtemp() self.src_file = os.path.join(self.src_dir, filename) self.dst_file = os.path.join(self.dst_dir, filename) - with open(self.src_file, "wb") as f: - f.write(b"spam") + create_file(self.src_file, b"spam") def _check_move_file(self, src, dst, real_dst): with open(src, "rb") as f: @@ -2649,8 +2708,7 @@ def test_move_dir_altsep_to_dir(self): def test_existing_file_inside_dest_dir(self): # A file with the same name inside the destination dir already exists. - with open(self.dst_file, "wb"): - pass + create_file(self.dst_file) self.assertRaises(shutil.Error, shutil.move, self.src_file, self.dst_dir) def test_dont_move_dir_in_itself(self): @@ -3065,8 +3123,7 @@ def test_empty_file(self): dstname = TESTFN + 'dst' self.addCleanup(lambda: os_helper.unlink(srcname)) self.addCleanup(lambda: os_helper.unlink(dstname)) - with open(srcname, "wb"): - pass + create_file(srcname) with open(srcname, "rb") as src: with open(dstname, "wb") as dst: @@ -3189,7 +3246,7 @@ def test_blocksize_arg(self): self.assertEqual(blocksize, os.path.getsize(TESTFN)) # ...unless we're dealing with a small file. os_helper.unlink(TESTFN2) - write_file(TESTFN2, b"hello", binary=True) + create_file(TESTFN2, b"hello") self.addCleanup(os_helper.unlink, TESTFN2 + '3') self.assertRaises(ZeroDivisionError, shutil.copyfile, TESTFN2, TESTFN2 + '3') diff --git a/Misc/NEWS.d/next/Library/2024-11-22-10-42-34.gh-issue-127035.UnbDlr.rst b/Misc/NEWS.d/next/Library/2024-11-22-10-42-34.gh-issue-127035.UnbDlr.rst new file mode 100644 index 00000000000000..6bb7abfdd50040 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-11-22-10-42-34.gh-issue-127035.UnbDlr.rst @@ -0,0 +1,4 @@ +Fix :mod:`shutil.which` on Windows. Now it looks at direct match if and only +if the command ends with a PATHEXT extension or X_OK is not in mode. Support +extensionless files if "." is in PATHEXT. Support PATHEXT extensions that end +with a dot. From f1e74248025b36a0c5d12f72c4ab713f4682f523 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Fri, 22 Nov 2024 21:56:39 +0200 Subject: [PATCH 231/269] [3.12] gh-109746: Make _thread.start_new_thread delete state of new thread on its startup failure (GH-109761) (GH-127173) If Python fails to start newly created thread due to failure of underlying PyThread_start_new_thread() call, its state should be removed from interpreter' thread states list to avoid its double cleanup. (cherry picked from commit ca3ea9ad05c3d876a58463595e5b4228fda06936) Co-authored-by: Radislav Chugunov <52372310+chgnrdv@users.noreply.github.com> --- Lib/test/test_threading.py | 35 +++++++++++++++++++ ...-09-22-21-01-56.gh-issue-109746.32MHt9.rst | 1 + Modules/_threadmodule.c | 1 + Python/pystate.c | 4 ++- 4 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2023-09-22-21-01-56.gh-issue-109746.32MHt9.rst diff --git a/Lib/test/test_threading.py b/Lib/test/test_threading.py index 2e4b860b975ff8..183af7c1f0836f 100644 --- a/Lib/test/test_threading.py +++ b/Lib/test/test_threading.py @@ -1150,6 +1150,41 @@ def __del__(self): self.assertEqual(out.strip(), b"OK") self.assertIn(b"can't create new thread at interpreter shutdown", err) + def test_start_new_thread_failed(self): + # gh-109746: if Python fails to start newly created thread + # due to failure of underlying PyThread_start_new_thread() call, + # its state should be removed from interpreter' thread states list + # to avoid its double cleanup + try: + from resource import setrlimit, RLIMIT_NPROC + except ImportError as err: + self.skipTest(err) # RLIMIT_NPROC is specific to Linux and BSD + code = """if 1: + import resource + import _thread + + def f(): + print("shouldn't be printed") + + limits = resource.getrlimit(resource.RLIMIT_NPROC) + [_, hard] = limits + resource.setrlimit(resource.RLIMIT_NPROC, (0, hard)) + + try: + _thread.start_new_thread(f, ()) + except RuntimeError: + print('ok') + else: + print('skip') + """ + _, out, err = assert_python_ok("-u", "-c", code) + out = out.strip() + if out == b'skip': + self.skipTest('RLIMIT_NPROC had no effect; probably superuser') + self.assertEqual(out, b'ok') + self.assertEqual(err, b'') + + class ThreadJoinOnShutdown(BaseTestCase): def _run_and_join(self, script): diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2023-09-22-21-01-56.gh-issue-109746.32MHt9.rst b/Misc/NEWS.d/next/Core_and_Builtins/2023-09-22-21-01-56.gh-issue-109746.32MHt9.rst new file mode 100644 index 00000000000000..2d350c33aa6975 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2023-09-22-21-01-56.gh-issue-109746.32MHt9.rst @@ -0,0 +1 @@ +If :func:`!_thread.start_new_thread` fails to start a new thread, it deletes its state from interpreter and thus avoids its repeated cleanup on finalization. diff --git a/Modules/_threadmodule.c b/Modules/_threadmodule.c index 365f4460088aab..518c246e98caf6 100644 --- a/Modules/_threadmodule.c +++ b/Modules/_threadmodule.c @@ -1219,6 +1219,7 @@ thread_PyThread_start_new_thread(PyObject *self, PyObject *fargs) if (ident == PYTHREAD_INVALID_THREAD_ID) { PyErr_SetString(ThreadError, "can't start new thread"); PyThreadState_Clear(boot->tstate); + PyThreadState_Delete(boot->tstate); thread_bootstate_free(boot, 1); return NULL; } diff --git a/Python/pystate.c b/Python/pystate.c index d0651fbd592f43..f0e0d4117e4259 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -1593,7 +1593,9 @@ tstate_delete_common(PyThreadState *tstate) if (tstate->_status.bound_gilstate) { unbind_gilstate_tstate(tstate); } - unbind_tstate(tstate); + if (tstate->_status.bound) { + unbind_tstate(tstate); + } // XXX Move to PyThreadState_Clear()? clear_datastack(tstate); From 6d2821c8331315a42cd79d677c409418f6599294 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Sat, 23 Nov 2024 20:55:55 +0100 Subject: [PATCH 232/269] [3.12] Doc: C API: Fix `Py_NewInterpreterFromConfig` example code (GH-126667) (#127202) Doc: C API: Fix `Py_NewInterpreterFromConfig` example code (GH-126667) (cherry picked from commit e3038e976b25a58f512d8c7083a752c89436eb0d) Co-authored-by: Richard Hansen --- Doc/c-api/init.rst | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Doc/c-api/init.rst b/Doc/c-api/init.rst index bec075038f06e0..aacbab0b3532e7 100644 --- a/Doc/c-api/init.rst +++ b/Doc/c-api/init.rst @@ -1644,7 +1644,11 @@ function. You can create and destroy them using the following functions: .check_multi_interp_extensions = 1, .gil = PyInterpreterConfig_OWN_GIL, }; - PyThreadState *tstate = Py_NewInterpreterFromConfig(&config); + PyThreadState *tstate = NULL; + PyStatus status = Py_NewInterpreterFromConfig(&tstate, &config); + if (PyStatus_Exception(status)) { + Py_ExitStatusException(status); + } Note that the config is used only briefly and does not get modified. During initialization the config's values are converted into various From e26ba9670f31df3db471365ddef7b0c293ef6871 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Sat, 23 Nov 2024 22:45:47 +0100 Subject: [PATCH 233/269] [3.12] Fix "useable" typo in docs (GH-127200) (#127206) Fix "useable" typo in docs (GH-127200) Fix typo in docs (cherry picked from commit dbd23790dbd662169905be6300259992639d4e69) Co-authored-by: Stan U <89152624+StanFromIreland@users.noreply.github.com> --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index f7fbe2083d8774..0e8da6088322bd 100644 --- a/README.rst +++ b/README.rst @@ -64,7 +64,7 @@ the executable is called ``python.exe``; elsewhere it's just ``python``. Building a complete Python installation requires the use of various additional third-party libraries, depending on your build platform and configure options. Not all standard library modules are buildable or -useable on all platforms. Refer to the +usable on all platforms. Refer to the `Install dependencies `_ section of the `Developer Guide`_ for current detailed information on dependencies for various Linux distributions and macOS. From b52ab48a10276ea332cd80e5fcc0c0dd2a4b7e02 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Sun, 24 Nov 2024 18:48:12 +0100 Subject: [PATCH 234/269] [3.12] Improve `pathname2url()` and `url2pathname()` docs (GH-127125) (#127233) Improve `pathname2url()` and `url2pathname()` docs (GH-127125) These functions have long sown confusion among Python developers. The existing documentation says they deal with URL path components, but that doesn't fit the evidence on Windows: >>> pathname2url(r'C:\foo') '///C:/foo' >>> pathname2url(r'\\server\share') '////server/share' # or '//server/share' as of quite recently If these were URL path components, they would imply complete URLs like `file://///C:/foo` and `file://////server/share`. Clearly this isn't right. Yet the implementation in `nturl2path` is deliberate, and the `url2pathname()` function correctly inverts it. On non-Windows platforms, the behaviour until quite recently is to simply quote/unquote the path without adding or removing any leading slashes. This behaviour is compatible with *both* interpretations -- 1) the value is a URL path component (existing docs), and 2) the value is everything following `file:` (this commit) The conclusion I draw is that these functions operate on everything after the `file:` prefix, which may include an authority section. This is the only explanation that fits both the Windows and non-Windows behaviour. It's also a better match for the function names. (cherry picked from commit 307c63358681d669ae39e5ecd814bded4a93443a) Co-authored-by: Barney Gale --- Doc/library/urllib.request.rst | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/Doc/library/urllib.request.rst b/Doc/library/urllib.request.rst index d7de8a16438110..0c5aa0458c6ebe 100644 --- a/Doc/library/urllib.request.rst +++ b/Doc/library/urllib.request.rst @@ -160,16 +160,28 @@ The :mod:`urllib.request` module defines the following functions: .. function:: pathname2url(path) - Convert the pathname *path* from the local syntax for a path to the form used in - the path component of a URL. This does not produce a complete URL. The return - value will already be quoted using the :func:`~urllib.parse.quote` function. + Convert the given local path to a ``file:`` URL. This function uses + :func:`~urllib.parse.quote` function to encode the path. For historical + reasons, the return value omits the ``file:`` scheme prefix. This example + shows the function being used on Windows:: + >>> from urllib.request import pathname2url + >>> path = 'C:\\Program Files' + >>> 'file:' + pathname2url(path) + 'file:///C:/Program%20Files' -.. function:: url2pathname(path) - Convert the path component *path* from a percent-encoded URL to the local syntax for a - path. This does not accept a complete URL. This function uses - :func:`~urllib.parse.unquote` to decode *path*. +.. function:: url2pathname(url) + + Convert the given ``file:`` URL to a local path. This function uses + :func:`~urllib.parse.unquote` to decode the URL. For historical reasons, + the given value *must* omit the ``file:`` scheme prefix. This example shows + the function being used on Windows:: + + >>> from urllib.request import url2pathname + >>> url = 'file:///C:/Program%20Files' + >>> url2pathname(url.removeprefix('file:')) + 'C:\\Program Files' .. function:: getproxies() From 5bfacd26e84c2f2b09e06634954970781dfc3c35 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Sun, 24 Nov 2024 19:13:23 +0100 Subject: [PATCH 235/269] [3.12] gh-127217: Fix pathname2url() for paths starting with multiple slashes on Posix (GH-127218) (GH-127231) (cherry picked from commit 97b2ceaaaf88a73a45254912a0e972412879ccbf) Co-authored-by: Serhiy Storchaka --- Lib/test/test_urllib.py | 3 +++ Lib/urllib/request.py | 4 ++++ .../Library/2024-11-24-12-41-31.gh-issue-127217.UAXGFr.rst | 2 ++ 3 files changed, 9 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2024-11-24-12-41-31.gh-issue-127217.UAXGFr.rst diff --git a/Lib/test/test_urllib.py b/Lib/test/test_urllib.py index 4bdbc19c1ffb7a..0710950e25a7cd 100644 --- a/Lib/test/test_urllib.py +++ b/Lib/test/test_urllib.py @@ -1565,6 +1565,9 @@ def test_pathname2url_posix(self): fn = urllib.request.pathname2url self.assertEqual(fn('/'), '/') self.assertEqual(fn('/a/b.c'), '/a/b.c') + self.assertEqual(fn('//a/b.c'), '////a/b.c') + self.assertEqual(fn('///a/b.c'), '/////a/b.c') + self.assertEqual(fn('////a/b.c'), '//////a/b.c') self.assertEqual(fn('/a/b%#c'), '/a/b%25%23c') @unittest.skipUnless(os_helper.FS_NONASCII, 'need os_helper.FS_NONASCII') diff --git a/Lib/urllib/request.py b/Lib/urllib/request.py index c521d96d953883..9a559f44152be5 100644 --- a/Lib/urllib/request.py +++ b/Lib/urllib/request.py @@ -1695,6 +1695,10 @@ def url2pathname(pathname): def pathname2url(pathname): """OS-specific conversion from a file system path to a relative URL of the 'file' scheme; not recommended for general use.""" + if pathname[:2] == '//': + # Add explicitly empty authority to avoid interpreting the path + # as authority. + pathname = '//' + pathname encoding = sys.getfilesystemencoding() errors = sys.getfilesystemencodeerrors() return quote(pathname, encoding=encoding, errors=errors) diff --git a/Misc/NEWS.d/next/Library/2024-11-24-12-41-31.gh-issue-127217.UAXGFr.rst b/Misc/NEWS.d/next/Library/2024-11-24-12-41-31.gh-issue-127217.UAXGFr.rst new file mode 100644 index 00000000000000..3139e33302f378 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-11-24-12-41-31.gh-issue-127217.UAXGFr.rst @@ -0,0 +1,2 @@ +Fix :func:`urllib.request.pathname2url` for paths starting with multiple +slashes on Posix. From 7e901cbdd851b82a32942eda87937e3eda666ee2 Mon Sep 17 00:00:00 2001 From: Yuki Kobayashi Date: Mon, 25 Nov 2024 17:51:48 +0900 Subject: [PATCH 236/269] [3.12] gh-101100: Fix sphinx warnings of removed opcodes (GH-127222) (#127240) --- Doc/whatsnew/3.4.rst | 2 +- Doc/whatsnew/3.6.rst | 8 ++++---- Doc/whatsnew/3.7.rst | 4 ++-- Doc/whatsnew/3.8.rst | 10 +++++----- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Doc/whatsnew/3.4.rst b/Doc/whatsnew/3.4.rst index 33534ff2c93f9e..6213915cc7413f 100644 --- a/Doc/whatsnew/3.4.rst +++ b/Doc/whatsnew/3.4.rst @@ -1979,7 +1979,7 @@ Other Improvements now works correctly (previously it silently returned the first python module in the file). (Contributed by Václav Šmilauer in :issue:`16421`.) -* A new opcode, :opcode:`LOAD_CLASSDEREF`, has been added to fix a bug in the +* A new opcode, :opcode:`!LOAD_CLASSDEREF`, has been added to fix a bug in the loading of free variables in class bodies that could be triggered by certain uses of :ref:`__prepare__ `. (Contributed by Benjamin Peterson in :issue:`17853`.) diff --git a/Doc/whatsnew/3.6.rst b/Doc/whatsnew/3.6.rst index 22bcaef33a277e..aa72cf4fdbf031 100644 --- a/Doc/whatsnew/3.6.rst +++ b/Doc/whatsnew/3.6.rst @@ -2377,16 +2377,16 @@ There have been several major changes to the :term:`bytecode` in Python 3.6. * The function call opcodes have been heavily reworked for better performance and simpler implementation. - The :opcode:`MAKE_FUNCTION`, :opcode:`CALL_FUNCTION`, - :opcode:`CALL_FUNCTION_KW` and :opcode:`BUILD_MAP_UNPACK_WITH_CALL` opcodes + The :opcode:`MAKE_FUNCTION`, :opcode:`!CALL_FUNCTION`, + :opcode:`!CALL_FUNCTION_KW` and :opcode:`!BUILD_MAP_UNPACK_WITH_CALL` opcodes have been modified, the new :opcode:`CALL_FUNCTION_EX` and - :opcode:`BUILD_TUPLE_UNPACK_WITH_CALL` have been added, and + :opcode:`!BUILD_TUPLE_UNPACK_WITH_CALL` have been added, and ``CALL_FUNCTION_VAR``, ``CALL_FUNCTION_VAR_KW`` and ``MAKE_CLOSURE`` opcodes have been removed. (Contributed by Demur Rumed in :issue:`27095`, and Serhiy Storchaka in :issue:`27213`, :issue:`28257`.) -* The new :opcode:`SETUP_ANNOTATIONS` and :opcode:`STORE_ANNOTATION` opcodes +* The new :opcode:`SETUP_ANNOTATIONS` and :opcode:`!STORE_ANNOTATION` opcodes have been added to support the new :term:`variable annotation` syntax. (Contributed by Ivan Levkivskyi in :issue:`27985`.) diff --git a/Doc/whatsnew/3.7.rst b/Doc/whatsnew/3.7.rst index 996d0a93cae5fc..7e4b189dfe44a8 100644 --- a/Doc/whatsnew/3.7.rst +++ b/Doc/whatsnew/3.7.rst @@ -2476,10 +2476,10 @@ avoiding possible problems use new functions :c:func:`PySlice_Unpack` and CPython bytecode changes ------------------------ -There are two new opcodes: :opcode:`LOAD_METHOD` and :opcode:`CALL_METHOD`. +There are two new opcodes: :opcode:`LOAD_METHOD` and :opcode:`!CALL_METHOD`. (Contributed by Yury Selivanov and INADA Naoki in :issue:`26110`.) -The :opcode:`STORE_ANNOTATION` opcode has been removed. +The :opcode:`!STORE_ANNOTATION` opcode has been removed. (Contributed by Mark Shannon in :issue:`32550`.) diff --git a/Doc/whatsnew/3.8.rst b/Doc/whatsnew/3.8.rst index 4d0b9f2c886f2b..c1f3717b6b74a8 100644 --- a/Doc/whatsnew/3.8.rst +++ b/Doc/whatsnew/3.8.rst @@ -2152,11 +2152,11 @@ CPython bytecode changes cleaning-up code for :keyword:`break`, :keyword:`continue` and :keyword:`return`. - Removed opcodes :opcode:`BREAK_LOOP`, :opcode:`CONTINUE_LOOP`, - :opcode:`SETUP_LOOP` and :opcode:`SETUP_EXCEPT`. Added new opcodes - :opcode:`ROT_FOUR`, :opcode:`BEGIN_FINALLY`, :opcode:`CALL_FINALLY` and - :opcode:`POP_FINALLY`. Changed the behavior of :opcode:`END_FINALLY` - and :opcode:`WITH_CLEANUP_START`. + Removed opcodes :opcode:`!BREAK_LOOP`, :opcode:`!CONTINUE_LOOP`, + :opcode:`!SETUP_LOOP` and :opcode:`!SETUP_EXCEPT`. Added new opcodes + :opcode:`!ROT_FOUR`, :opcode:`!BEGIN_FINALLY`, :opcode:`!CALL_FINALLY` and + :opcode:`!POP_FINALLY`. Changed the behavior of :opcode:`!END_FINALLY` + and :opcode:`!WITH_CLEANUP_START`. (Contributed by Mark Shannon, Antoine Pitrou and Serhiy Storchaka in :issue:`17611`.) From f7e587d15c868d4e08397320d556980ee2a9a4ae Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Mon, 25 Nov 2024 13:18:35 +0100 Subject: [PATCH 237/269] [3.12] gh-126384: Add tests to verify the behavior of basic COM methods. (GH-126610) (GH-127160) (cherry picked from commit 7725c0371a93be3ccfaff4871014a80bdf0ea274) Co-authored-by: Jun Komoda <45822440+junkmd@users.noreply.github.com> --- .../test_win32_com_foreign_func.py | 188 ++++++++++++++++++ 1 file changed, 188 insertions(+) create mode 100644 Lib/test/test_ctypes/test_win32_com_foreign_func.py diff --git a/Lib/test/test_ctypes/test_win32_com_foreign_func.py b/Lib/test/test_ctypes/test_win32_com_foreign_func.py new file mode 100644 index 00000000000000..651c9277d59af9 --- /dev/null +++ b/Lib/test/test_ctypes/test_win32_com_foreign_func.py @@ -0,0 +1,188 @@ +import ctypes +import gc +import sys +import unittest +from ctypes import POINTER, byref, c_void_p +from ctypes.wintypes import BYTE, DWORD, WORD + +if sys.platform != "win32": + raise unittest.SkipTest("Windows-specific test") + + +from _ctypes import COMError +from ctypes import HRESULT + + +COINIT_APARTMENTTHREADED = 0x2 +CLSCTX_SERVER = 5 +S_OK = 0 +OUT = 2 +TRUE = 1 +E_NOINTERFACE = -2147467262 + + +class GUID(ctypes.Structure): + # https://learn.microsoft.com/en-us/windows/win32/api/guiddef/ns-guiddef-guid + _fields_ = [ + ("Data1", DWORD), + ("Data2", WORD), + ("Data3", WORD), + ("Data4", BYTE * 8), + ] + + +def create_proto_com_method(name, index, restype, *argtypes): + proto = ctypes.WINFUNCTYPE(restype, *argtypes) + + def make_method(*args): + foreign_func = proto(index, name, *args) + + def call(self, *args, **kwargs): + return foreign_func(self, *args, **kwargs) + + return call + + return make_method + + +def create_guid(name): + guid = GUID() + # https://learn.microsoft.com/en-us/windows/win32/api/combaseapi/nf-combaseapi-clsidfromstring + ole32.CLSIDFromString(name, byref(guid)) + return guid + + +def is_equal_guid(guid1, guid2): + # https://learn.microsoft.com/en-us/windows/win32/api/objbase/nf-objbase-isequalguid + return ole32.IsEqualGUID(byref(guid1), byref(guid2)) + + +ole32 = ctypes.oledll.ole32 + +IID_IUnknown = create_guid("{00000000-0000-0000-C000-000000000046}") +IID_IStream = create_guid("{0000000C-0000-0000-C000-000000000046}") +IID_IPersist = create_guid("{0000010C-0000-0000-C000-000000000046}") +CLSID_ShellLink = create_guid("{00021401-0000-0000-C000-000000000046}") + +# https://learn.microsoft.com/en-us/windows/win32/api/unknwn/nf-unknwn-iunknown-queryinterface(refiid_void) +proto_query_interface = create_proto_com_method( + "QueryInterface", 0, HRESULT, POINTER(GUID), POINTER(c_void_p) +) +# https://learn.microsoft.com/en-us/windows/win32/api/unknwn/nf-unknwn-iunknown-addref +proto_add_ref = create_proto_com_method("AddRef", 1, ctypes.c_long) +# https://learn.microsoft.com/en-us/windows/win32/api/unknwn/nf-unknwn-iunknown-release +proto_release = create_proto_com_method("Release", 2, ctypes.c_long) +# https://learn.microsoft.com/en-us/windows/win32/api/objidl/nf-objidl-ipersist-getclassid +proto_get_class_id = create_proto_com_method( + "GetClassID", 3, HRESULT, POINTER(GUID) +) + + +class ForeignFunctionsThatWillCallComMethodsTests(unittest.TestCase): + def setUp(self): + # https://learn.microsoft.com/en-us/windows/win32/api/combaseapi/nf-combaseapi-coinitializeex + ole32.CoInitializeEx(None, COINIT_APARTMENTTHREADED) + + def tearDown(self): + # https://learn.microsoft.com/en-us/windows/win32/api/combaseapi/nf-combaseapi-couninitialize + ole32.CoUninitialize() + gc.collect() + + @staticmethod + def create_shelllink_persist(typ): + ppst = typ() + # https://learn.microsoft.com/en-us/windows/win32/api/combaseapi/nf-combaseapi-cocreateinstance + ole32.CoCreateInstance( + byref(CLSID_ShellLink), + None, + CLSCTX_SERVER, + byref(IID_IPersist), + byref(ppst), + ) + return ppst + + def test_without_paramflags_and_iid(self): + class IUnknown(c_void_p): + QueryInterface = proto_query_interface() + AddRef = proto_add_ref() + Release = proto_release() + + class IPersist(IUnknown): + GetClassID = proto_get_class_id() + + ppst = self.create_shelllink_persist(IPersist) + + clsid = GUID() + hr_getclsid = ppst.GetClassID(byref(clsid)) + self.assertEqual(S_OK, hr_getclsid) + self.assertEqual(TRUE, is_equal_guid(CLSID_ShellLink, clsid)) + + self.assertEqual(2, ppst.AddRef()) + self.assertEqual(3, ppst.AddRef()) + + punk = IUnknown() + hr_qi = ppst.QueryInterface(IID_IUnknown, punk) + self.assertEqual(S_OK, hr_qi) + self.assertEqual(3, punk.Release()) + + with self.assertRaises(OSError) as e: + punk.QueryInterface(IID_IStream, IUnknown()) + self.assertEqual(E_NOINTERFACE, e.exception.winerror) + + self.assertEqual(2, ppst.Release()) + self.assertEqual(1, ppst.Release()) + self.assertEqual(0, ppst.Release()) + + def test_with_paramflags_and_without_iid(self): + class IUnknown(c_void_p): + QueryInterface = proto_query_interface(None) + AddRef = proto_add_ref() + Release = proto_release() + + class IPersist(IUnknown): + GetClassID = proto_get_class_id(((OUT, "pClassID"),)) + + ppst = self.create_shelllink_persist(IPersist) + + clsid = ppst.GetClassID() + self.assertEqual(TRUE, is_equal_guid(CLSID_ShellLink, clsid)) + + punk = IUnknown() + hr_qi = ppst.QueryInterface(IID_IUnknown, punk) + self.assertEqual(S_OK, hr_qi) + self.assertEqual(1, punk.Release()) + + with self.assertRaises(OSError) as e: + ppst.QueryInterface(IID_IStream, IUnknown()) + self.assertEqual(E_NOINTERFACE, e.exception.winerror) + + self.assertEqual(0, ppst.Release()) + + def test_with_paramflags_and_iid(self): + class IUnknown(c_void_p): + QueryInterface = proto_query_interface(None, IID_IUnknown) + AddRef = proto_add_ref() + Release = proto_release() + + class IPersist(IUnknown): + GetClassID = proto_get_class_id(((OUT, "pClassID"),), IID_IPersist) + + ppst = self.create_shelllink_persist(IPersist) + + clsid = ppst.GetClassID() + self.assertEqual(TRUE, is_equal_guid(CLSID_ShellLink, clsid)) + + punk = IUnknown() + hr_qi = ppst.QueryInterface(IID_IUnknown, punk) + self.assertEqual(S_OK, hr_qi) + self.assertEqual(1, punk.Release()) + + with self.assertRaises(COMError) as e: + ppst.QueryInterface(IID_IStream, IUnknown()) + self.assertEqual(E_NOINTERFACE, e.exception.hresult) + + self.assertEqual(0, ppst.Release()) + + +if __name__ == '__main__': + unittest.main() From a4d6b905dd46eedaa118b95d1ed052a7171cd608 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Mon, 25 Nov 2024 18:51:21 +0100 Subject: [PATCH 238/269] [3.12] gh-127182: Fix `io.StringIO.__setstate__` crash when `None` is the first value (GH-127219) (#127263) gh-127182: Fix `io.StringIO.__setstate__` crash when `None` is the first value (GH-127219) (cherry picked from commit a2ee89968299fc4f0da4b5a4165025b941213ba5) Co-authored-by: sobolevn Co-authored-by: Victor Stinner --- Lib/test/test_io.py | 15 ++++++++++ ...-11-24-14-20-17.gh-issue-127182.WmfY2g.rst | 2 ++ Modules/_io/stringio.c | 30 ++++++++++--------- 3 files changed, 33 insertions(+), 14 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-11-24-14-20-17.gh-issue-127182.WmfY2g.rst diff --git a/Lib/test/test_io.py b/Lib/test/test_io.py index adabf6d6ed7aca..d85040a3083373 100644 --- a/Lib/test/test_io.py +++ b/Lib/test/test_io.py @@ -1154,6 +1154,21 @@ def test_disallow_instantiation(self): _io = self._io support.check_disallow_instantiation(self, _io._BytesIOBuffer) + def test_stringio_setstate(self): + # gh-127182: Calling __setstate__() with invalid arguments must not crash + obj = self._io.StringIO() + with self.assertRaisesRegex( + TypeError, + 'initial_value must be str or None, not int', + ): + obj.__setstate__((1, '', 0, {})) + + obj.__setstate__((None, '', 0, {})) # should not crash + self.assertEqual(obj.getvalue(), '') + + obj.__setstate__(('', '', 0, {})) + self.assertEqual(obj.getvalue(), '') + class PyIOTest(IOTest): pass diff --git a/Misc/NEWS.d/next/Library/2024-11-24-14-20-17.gh-issue-127182.WmfY2g.rst b/Misc/NEWS.d/next/Library/2024-11-24-14-20-17.gh-issue-127182.WmfY2g.rst new file mode 100644 index 00000000000000..2cc46ca3d33977 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-11-24-14-20-17.gh-issue-127182.WmfY2g.rst @@ -0,0 +1,2 @@ +Fix :meth:`!io.StringIO.__setstate__` crash, when :const:`None` was passed as +the first value. diff --git a/Modules/_io/stringio.c b/Modules/_io/stringio.c index 568d0bd7097b63..ed9cf0da910077 100644 --- a/Modules/_io/stringio.c +++ b/Modules/_io/stringio.c @@ -884,23 +884,25 @@ stringio_setstate(stringio *self, PyObject *state) once by __init__. So we do not take any chance and replace object's buffer completely. */ { - PyObject *item; - Py_UCS4 *buf; - Py_ssize_t bufsize; - - item = PyTuple_GET_ITEM(state, 0); - buf = PyUnicode_AsUCS4Copy(item); - if (buf == NULL) - return NULL; - bufsize = PyUnicode_GET_LENGTH(item); + PyObject *item = PyTuple_GET_ITEM(state, 0); + if (PyUnicode_Check(item)) { + Py_UCS4 *buf = PyUnicode_AsUCS4Copy(item); + if (buf == NULL) + return NULL; + Py_ssize_t bufsize = PyUnicode_GET_LENGTH(item); - if (resize_buffer(self, bufsize) < 0) { + if (resize_buffer(self, bufsize) < 0) { + PyMem_Free(buf); + return NULL; + } + memcpy(self->buf, buf, bufsize * sizeof(Py_UCS4)); PyMem_Free(buf); - return NULL; + self->string_size = bufsize; + } + else { + assert(item == Py_None); + self->string_size = 0; } - memcpy(self->buf, buf, bufsize * sizeof(Py_UCS4)); - PyMem_Free(buf); - self->string_size = bufsize; } /* Set carefully the position value. Alternatively, we could use the seek From 3806772b3d30a574eb26bcc3a1e0ceae49ad4401 Mon Sep 17 00:00:00 2001 From: Rafael Fontenelle Date: Mon, 25 Nov 2024 16:48:00 -0300 Subject: [PATCH 239/269] [3.12] Fix a few typos found in the docs (GH-127126) (GH-127185) (cherry picked from commit 39e60aeb3837f1f23d8b7f30d3b8d9faf805ef88) --- Doc/library/importlib.metadata.rst | 2 +- Doc/library/multiprocessing.rst | 4 ++-- Doc/library/socket.rst | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Doc/library/importlib.metadata.rst b/Doc/library/importlib.metadata.rst index fe898cf4af8bd5..289e02ccdc153c 100644 --- a/Doc/library/importlib.metadata.rst +++ b/Doc/library/importlib.metadata.rst @@ -133,7 +133,7 @@ Entry points Details of a collection of installed entry points. - Also provides a ``.groups`` attribute that reports all identifed entry + Also provides a ``.groups`` attribute that reports all identified entry point groups, and a ``.names`` attribute that reports all identified entry point names. diff --git a/Doc/library/multiprocessing.rst b/Doc/library/multiprocessing.rst index f2a9ada85e235a..3fec86080c5ead 100644 --- a/Doc/library/multiprocessing.rst +++ b/Doc/library/multiprocessing.rst @@ -282,7 +282,7 @@ processes: of corruption from processes using different ends of the pipe at the same time. - The :meth:`~Connection.send` method serializes the the object and + The :meth:`~Connection.send` method serializes the object and :meth:`~Connection.recv` re-creates the object. Synchronization between processes @@ -819,7 +819,7 @@ For an example of the usage of queues for interprocess communication see used for receiving messages and ``conn2`` can only be used for sending messages. - The :meth:`~multiprocessing.Connection.send` method serializes the the object using + The :meth:`~multiprocessing.Connection.send` method serializes the object using :mod:`pickle` and the :meth:`~multiprocessing.Connection.recv` re-creates the object. .. class:: Queue([maxsize]) diff --git a/Doc/library/socket.rst b/Doc/library/socket.rst index ab0ede803a0509..dd4c2f8317d120 100644 --- a/Doc/library/socket.rst +++ b/Doc/library/socket.rst @@ -966,7 +966,7 @@ The :mod:`socket` module also offers various network-related services: These addresses should generally be tried in order until a connection succeeds (possibly tried in parallel, for example, using a `Happy Eyeballs`_ algorithm). In these cases, limiting the *type* and/or *proto* can help eliminate - unsuccessful or unusable connecton attempts. + unsuccessful or unusable connection attempts. Some systems will, however, only return a single address. (For example, this was reported on Solaris and AIX configurations.) From 619037af1c0f6daf6b682e974f8e6ffaaf4c01d9 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Mon, 25 Nov 2024 22:42:34 +0100 Subject: [PATCH 240/269] [3.12] gh-127265: Remove single quotes from 'arrow's in tutorial/errors.rst (GH-127268) (cherry picked from commit 26ff32b30553e1f7b0cc822835ad2da8890c180c) Co-authored-by: funkyrailroad --- Doc/tutorial/errors.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/tutorial/errors.rst b/Doc/tutorial/errors.rst index 981b14f5a4212b..247b2f6407f8f7 100644 --- a/Doc/tutorial/errors.rst +++ b/Doc/tutorial/errors.rst @@ -23,7 +23,7 @@ complaint you get while you are still learning Python:: ^^^^^ SyntaxError: invalid syntax -The parser repeats the offending line and displays little 'arrow's pointing +The parser repeats the offending line and displays little arrows pointing at the token in the line where the error was detected. The error may be caused by the absence of a token *before* the indicated token. In the example, the error is detected at the function :func:`print`, since a colon From 08531ac83fc5d844ccbba589913f7393e925dc79 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Tue, 26 Nov 2024 11:13:29 +0100 Subject: [PATCH 241/269] [3.12] gh-127183: Add `_ctypes.CopyComPointer` tests (GH-127184) (GH-127252) gh-127183: Add `_ctypes.CopyComPointer` tests (GH-127184) * Make `create_shelllink_persist` top level function. * Add `CopyComPointerTests`. * Add more tests. * Update tests. * Add assertions for `Release`'s return value. (cherry picked from commit c7f1e3e150ca181f4b4bd1e5b59d492749f00be6) Co-authored-by: Jun Komoda <45822440+junkmd@users.noreply.github.com> --- .../test_win32_com_foreign_func.py | 132 +++++++++++++++--- 1 file changed, 115 insertions(+), 17 deletions(-) diff --git a/Lib/test/test_ctypes/test_win32_com_foreign_func.py b/Lib/test/test_ctypes/test_win32_com_foreign_func.py index 651c9277d59af9..8d217fc17efa02 100644 --- a/Lib/test/test_ctypes/test_win32_com_foreign_func.py +++ b/Lib/test/test_ctypes/test_win32_com_foreign_func.py @@ -9,7 +9,7 @@ raise unittest.SkipTest("Windows-specific test") -from _ctypes import COMError +from _ctypes import COMError, CopyComPointer from ctypes import HRESULT @@ -78,6 +78,19 @@ def is_equal_guid(guid1, guid2): ) +def create_shelllink_persist(typ): + ppst = typ() + # https://learn.microsoft.com/en-us/windows/win32/api/combaseapi/nf-combaseapi-cocreateinstance + ole32.CoCreateInstance( + byref(CLSID_ShellLink), + None, + CLSCTX_SERVER, + byref(IID_IPersist), + byref(ppst), + ) + return ppst + + class ForeignFunctionsThatWillCallComMethodsTests(unittest.TestCase): def setUp(self): # https://learn.microsoft.com/en-us/windows/win32/api/combaseapi/nf-combaseapi-coinitializeex @@ -88,19 +101,6 @@ def tearDown(self): ole32.CoUninitialize() gc.collect() - @staticmethod - def create_shelllink_persist(typ): - ppst = typ() - # https://learn.microsoft.com/en-us/windows/win32/api/combaseapi/nf-combaseapi-cocreateinstance - ole32.CoCreateInstance( - byref(CLSID_ShellLink), - None, - CLSCTX_SERVER, - byref(IID_IPersist), - byref(ppst), - ) - return ppst - def test_without_paramflags_and_iid(self): class IUnknown(c_void_p): QueryInterface = proto_query_interface() @@ -110,7 +110,7 @@ class IUnknown(c_void_p): class IPersist(IUnknown): GetClassID = proto_get_class_id() - ppst = self.create_shelllink_persist(IPersist) + ppst = create_shelllink_persist(IPersist) clsid = GUID() hr_getclsid = ppst.GetClassID(byref(clsid)) @@ -142,7 +142,7 @@ class IUnknown(c_void_p): class IPersist(IUnknown): GetClassID = proto_get_class_id(((OUT, "pClassID"),)) - ppst = self.create_shelllink_persist(IPersist) + ppst = create_shelllink_persist(IPersist) clsid = ppst.GetClassID() self.assertEqual(TRUE, is_equal_guid(CLSID_ShellLink, clsid)) @@ -167,7 +167,7 @@ class IUnknown(c_void_p): class IPersist(IUnknown): GetClassID = proto_get_class_id(((OUT, "pClassID"),), IID_IPersist) - ppst = self.create_shelllink_persist(IPersist) + ppst = create_shelllink_persist(IPersist) clsid = ppst.GetClassID() self.assertEqual(TRUE, is_equal_guid(CLSID_ShellLink, clsid)) @@ -184,5 +184,103 @@ class IPersist(IUnknown): self.assertEqual(0, ppst.Release()) +class CopyComPointerTests(unittest.TestCase): + def setUp(self): + ole32.CoInitializeEx(None, COINIT_APARTMENTTHREADED) + + class IUnknown(c_void_p): + QueryInterface = proto_query_interface(None, IID_IUnknown) + AddRef = proto_add_ref() + Release = proto_release() + + class IPersist(IUnknown): + GetClassID = proto_get_class_id(((OUT, "pClassID"),), IID_IPersist) + + self.IUnknown = IUnknown + self.IPersist = IPersist + + def tearDown(self): + ole32.CoUninitialize() + gc.collect() + + def test_both_are_null(self): + src = self.IPersist() + dst = self.IPersist() + + hr = CopyComPointer(src, byref(dst)) + + self.assertEqual(S_OK, hr) + + self.assertIsNone(src.value) + self.assertIsNone(dst.value) + + def test_src_is_nonnull_and_dest_is_null(self): + # The reference count of the COM pointer created by `CoCreateInstance` + # is initially 1. + src = create_shelllink_persist(self.IPersist) + dst = self.IPersist() + + # `CopyComPointer` calls `AddRef` explicitly in the C implementation. + # The refcount of `src` is incremented from 1 to 2 here. + hr = CopyComPointer(src, byref(dst)) + + self.assertEqual(S_OK, hr) + self.assertEqual(src.value, dst.value) + + # This indicates that the refcount was 2 before the `Release` call. + self.assertEqual(1, src.Release()) + + clsid = dst.GetClassID() + self.assertEqual(TRUE, is_equal_guid(CLSID_ShellLink, clsid)) + + self.assertEqual(0, dst.Release()) + + def test_src_is_null_and_dest_is_nonnull(self): + src = self.IPersist() + dst_orig = create_shelllink_persist(self.IPersist) + dst = self.IPersist() + CopyComPointer(dst_orig, byref(dst)) + self.assertEqual(1, dst_orig.Release()) + + clsid = dst.GetClassID() + self.assertEqual(TRUE, is_equal_guid(CLSID_ShellLink, clsid)) + + # This does NOT affects the refcount of `dst_orig`. + hr = CopyComPointer(src, byref(dst)) + + self.assertEqual(S_OK, hr) + self.assertIsNone(dst.value) + + with self.assertRaises(ValueError): + dst.GetClassID() # NULL COM pointer access + + # This indicates that the refcount was 1 before the `Release` call. + self.assertEqual(0, dst_orig.Release()) + + def test_both_are_nonnull(self): + src = create_shelllink_persist(self.IPersist) + dst_orig = create_shelllink_persist(self.IPersist) + dst = self.IPersist() + CopyComPointer(dst_orig, byref(dst)) + self.assertEqual(1, dst_orig.Release()) + + self.assertEqual(dst.value, dst_orig.value) + self.assertNotEqual(src.value, dst.value) + + hr = CopyComPointer(src, byref(dst)) + + self.assertEqual(S_OK, hr) + self.assertEqual(src.value, dst.value) + self.assertNotEqual(dst.value, dst_orig.value) + + self.assertEqual(1, src.Release()) + + clsid = dst.GetClassID() + self.assertEqual(TRUE, is_equal_guid(CLSID_ShellLink, clsid)) + + self.assertEqual(0, dst.Release()) + self.assertEqual(0, dst_orig.Release()) + + if __name__ == '__main__': unittest.main() From 16e899d58915b978bb491de5d51c1c47f69a590d Mon Sep 17 00:00:00 2001 From: Yuki Kobayashi Date: Wed, 27 Nov 2024 18:46:42 +0900 Subject: [PATCH 242/269] [3.12] gh-101100: Fix sphinx warnings in `howto/*` (GH-127084) (#127311) --- Doc/howto/descriptor.rst | 100 +++++++++++++++++------------------ Doc/howto/enum.rst | 109 ++++++++++++++++++++------------------- Doc/tools/.nitignore | 2 - 3 files changed, 105 insertions(+), 106 deletions(-) diff --git a/Doc/howto/descriptor.rst b/Doc/howto/descriptor.rst index 985e2639d874cd..ac73fcb3b01966 100644 --- a/Doc/howto/descriptor.rst +++ b/Doc/howto/descriptor.rst @@ -42,7 +42,7 @@ add new capabilities one by one. Simple example: A descriptor that returns a constant ---------------------------------------------------- -The :class:`Ten` class is a descriptor whose :meth:`__get__` method always +The :class:`!Ten` class is a descriptor whose :meth:`~object.__get__` method always returns the constant ``10``: .. testcode:: @@ -120,10 +120,10 @@ different, updated answers each time:: 2 Besides showing how descriptors can run computations, this example also -reveals the purpose of the parameters to :meth:`__get__`. The *self* +reveals the purpose of the parameters to :meth:`~object.__get__`. The *self* parameter is *size*, an instance of *DirectorySize*. The *obj* parameter is either *g* or *s*, an instance of *Directory*. It is the *obj* parameter that -lets the :meth:`__get__` method learn the target directory. The *objtype* +lets the :meth:`~object.__get__` method learn the target directory. The *objtype* parameter is the class *Directory*. @@ -133,7 +133,7 @@ Managed attributes A popular use for descriptors is managing access to instance data. The descriptor is assigned to a public attribute in the class dictionary while the actual data is stored as a private attribute in the instance dictionary. The -descriptor's :meth:`__get__` and :meth:`__set__` methods are triggered when +descriptor's :meth:`~object.__get__` and :meth:`~object.__set__` methods are triggered when the public attribute is accessed. In the following example, *age* is the public attribute and *_age* is the @@ -215,9 +215,9 @@ Customized names When a class uses descriptors, it can inform each descriptor about which variable name was used. -In this example, the :class:`Person` class has two descriptor instances, -*name* and *age*. When the :class:`Person` class is defined, it makes a -callback to :meth:`__set_name__` in *LoggedAccess* so that the field names can +In this example, the :class:`!Person` class has two descriptor instances, +*name* and *age*. When the :class:`!Person` class is defined, it makes a +callback to :meth:`~object.__set_name__` in *LoggedAccess* so that the field names can be recorded, giving each descriptor its own *public_name* and *private_name*: .. testcode:: @@ -253,8 +253,8 @@ be recorded, giving each descriptor its own *public_name* and *private_name*: def birthday(self): self.age += 1 -An interactive session shows that the :class:`Person` class has called -:meth:`__set_name__` so that the field names would be recorded. Here +An interactive session shows that the :class:`!Person` class has called +:meth:`~object.__set_name__` so that the field names would be recorded. Here we call :func:`vars` to look up the descriptor without triggering it: .. doctest:: @@ -294,10 +294,10 @@ The two *Person* instances contain only the private names: Closing thoughts ---------------- -A :term:`descriptor` is what we call any object that defines :meth:`__get__`, -:meth:`__set__`, or :meth:`__delete__`. +A :term:`descriptor` is what we call any object that defines :meth:`~object.__get__`, +:meth:`~object.__set__`, or :meth:`~object.__delete__`. -Optionally, descriptors can have a :meth:`__set_name__` method. This is only +Optionally, descriptors can have a :meth:`~object.__set_name__` method. This is only used in cases where a descriptor needs to know either the class where it was created or the name of class variable it was assigned to. (This method, if present, is called even if the class is not a descriptor.) @@ -337,7 +337,7 @@ any data, it verifies that the new value meets various type and range restrictions. If those restrictions aren't met, it raises an exception to prevent data corruption at its source. -This :class:`Validator` class is both an :term:`abstract base class` and a +This :class:`!Validator` class is both an :term:`abstract base class` and a managed attribute descriptor: .. testcode:: @@ -360,8 +360,8 @@ managed attribute descriptor: def validate(self, value): pass -Custom validators need to inherit from :class:`Validator` and must supply a -:meth:`validate` method to test various restrictions as needed. +Custom validators need to inherit from :class:`!Validator` and must supply a +:meth:`!validate` method to test various restrictions as needed. Custom validators @@ -369,13 +369,13 @@ Custom validators Here are three practical data validation utilities: -1) :class:`OneOf` verifies that a value is one of a restricted set of options. +1) :class:`!OneOf` verifies that a value is one of a restricted set of options. -2) :class:`Number` verifies that a value is either an :class:`int` or +2) :class:`!Number` verifies that a value is either an :class:`int` or :class:`float`. Optionally, it verifies that a value is between a given minimum or maximum. -3) :class:`String` verifies that a value is a :class:`str`. Optionally, it +3) :class:`!String` verifies that a value is a :class:`str`. Optionally, it validates a given minimum or maximum length. It can validate a user-defined `predicate `_ as well. @@ -498,8 +498,8 @@ Definition and introduction --------------------------- In general, a descriptor is an attribute value that has one of the methods in -the descriptor protocol. Those methods are :meth:`__get__`, :meth:`__set__`, -and :meth:`__delete__`. If any of those methods are defined for an +the descriptor protocol. Those methods are :meth:`~object.__get__`, :meth:`~object.__set__`, +and :meth:`~object.__delete__`. If any of those methods are defined for an attribute, it is said to be a :term:`descriptor`. The default behavior for attribute access is to get, set, or delete the @@ -531,8 +531,8 @@ That is all there is to it. Define any of these methods and an object is considered a descriptor and can override default behavior upon being looked up as an attribute. -If an object defines :meth:`__set__` or :meth:`__delete__`, it is considered -a data descriptor. Descriptors that only define :meth:`__get__` are called +If an object defines :meth:`~object.__set__` or :meth:`~object.__delete__`, it is considered +a data descriptor. Descriptors that only define :meth:`~object.__get__` are called non-data descriptors (they are often used for methods but other uses are possible). @@ -542,9 +542,9 @@ has an entry with the same name as a data descriptor, the data descriptor takes precedence. If an instance's dictionary has an entry with the same name as a non-data descriptor, the dictionary entry takes precedence. -To make a read-only data descriptor, define both :meth:`__get__` and -:meth:`__set__` with the :meth:`__set__` raising an :exc:`AttributeError` when -called. Defining the :meth:`__set__` method with an exception raising +To make a read-only data descriptor, define both :meth:`~object.__get__` and +:meth:`~object.__set__` with the :meth:`~object.__set__` raising an :exc:`AttributeError` when +called. Defining the :meth:`~object.__set__` method with an exception raising placeholder is enough to make it a data descriptor. @@ -571,7 +571,7 @@ Invocation from an instance Instance lookup scans through a chain of namespaces giving data descriptors the highest priority, followed by instance variables, then non-data -descriptors, then class variables, and lastly :meth:`__getattr__` if it is +descriptors, then class variables, and lastly :meth:`~object.__getattr__` if it is provided. If a descriptor is found for ``a.x``, then it is invoked with: @@ -716,12 +716,12 @@ a pure Python equivalent: >>> object_getattribute(u2, 'x') == u2.x == (D1, u2, U2) True -Note, there is no :meth:`__getattr__` hook in the :meth:`__getattribute__` -code. That is why calling :meth:`__getattribute__` directly or with -``super().__getattribute__`` will bypass :meth:`__getattr__` entirely. +Note, there is no :meth:`~object.__getattr__` hook in the :meth:`~object.__getattribute__` +code. That is why calling :meth:`~object.__getattribute__` directly or with +``super().__getattribute__`` will bypass :meth:`~object.__getattr__` entirely. Instead, it is the dot operator and the :func:`getattr` function that are -responsible for invoking :meth:`__getattr__` whenever :meth:`__getattribute__` +responsible for invoking :meth:`~object.__getattr__` whenever :meth:`~object.__getattribute__` raises an :exc:`AttributeError`. Their logic is encapsulated in a helper function: @@ -773,8 +773,8 @@ Invocation from a class ----------------------- The logic for a dotted lookup such as ``A.x`` is in -:meth:`type.__getattribute__`. The steps are similar to those for -:meth:`object.__getattribute__` but the instance dictionary lookup is replaced +:meth:`!type.__getattribute__`. The steps are similar to those for +:meth:`!object.__getattribute__` but the instance dictionary lookup is replaced by a search through the class's :term:`method resolution order`. If a descriptor is found, it is invoked with ``desc.__get__(None, A)``. @@ -786,7 +786,7 @@ The full C implementation can be found in :c:func:`!type_getattro` and Invocation from super --------------------- -The logic for super's dotted lookup is in the :meth:`__getattribute__` method for +The logic for super's dotted lookup is in the :meth:`~object.__getattribute__` method for object returned by :func:`super`. A dotted lookup such as ``super(A, obj).m`` searches ``obj.__class__.__mro__`` @@ -803,21 +803,21 @@ The full C implementation can be found in :c:func:`!super_getattro` in Summary of invocation logic --------------------------- -The mechanism for descriptors is embedded in the :meth:`__getattribute__` +The mechanism for descriptors is embedded in the :meth:`~object.__getattribute__` methods for :class:`object`, :class:`type`, and :func:`super`. The important points to remember are: -* Descriptors are invoked by the :meth:`__getattribute__` method. +* Descriptors are invoked by the :meth:`~object.__getattribute__` method. * Classes inherit this machinery from :class:`object`, :class:`type`, or :func:`super`. -* Overriding :meth:`__getattribute__` prevents automatic descriptor calls +* Overriding :meth:`~object.__getattribute__` prevents automatic descriptor calls because all the descriptor logic is in that method. -* :meth:`object.__getattribute__` and :meth:`type.__getattribute__` make - different calls to :meth:`__get__`. The first includes the instance and may +* :meth:`!object.__getattribute__` and :meth:`!type.__getattribute__` make + different calls to :meth:`~object.__get__`. The first includes the instance and may include the class. The second puts in ``None`` for the instance and always includes the class. @@ -832,16 +832,16 @@ Automatic name notification Sometimes it is desirable for a descriptor to know what class variable name it was assigned to. When a new class is created, the :class:`type` metaclass scans the dictionary of the new class. If any of the entries are descriptors -and if they define :meth:`__set_name__`, that method is called with two +and if they define :meth:`~object.__set_name__`, that method is called with two arguments. The *owner* is the class where the descriptor is used, and the *name* is the class variable the descriptor was assigned to. The implementation details are in :c:func:`!type_new` and :c:func:`!set_names` in :source:`Objects/typeobject.c`. -Since the update logic is in :meth:`type.__new__`, notifications only take +Since the update logic is in :meth:`!type.__new__`, notifications only take place at the time of class creation. If descriptors are added to the class -afterwards, :meth:`__set_name__` will need to be called manually. +afterwards, :meth:`~object.__set_name__` will need to be called manually. ORM example @@ -870,7 +870,7 @@ care of lookups or updates: conn.execute(self.store, [value, obj.key]) conn.commit() -We can use the :class:`Field` class to define `models +We can use the :class:`!Field` class to define `models `_ that describe the schema for each table in a database: @@ -1150,7 +1150,7 @@ to wrap access to the value attribute in a property data descriptor: self.recalc() return self._value -Either the built-in :func:`property` or our :func:`Property` equivalent would +Either the built-in :func:`property` or our :func:`!Property` equivalent would work in this example. @@ -1183,7 +1183,7 @@ roughly equivalent to: return func(obj, *args, **kwargs) To support automatic creation of methods, functions include the -:meth:`__get__` method for binding methods during attribute access. This +:meth:`~object.__get__` method for binding methods during attribute access. This means that functions are non-data descriptors that return bound methods during dotted lookup from an instance. Here's how it works: @@ -1215,19 +1215,19 @@ The function has a :term:`qualified name` attribute to support introspection: 'D.f' Accessing the function through the class dictionary does not invoke -:meth:`__get__`. Instead, it just returns the underlying function object:: +:meth:`~object.__get__`. Instead, it just returns the underlying function object:: >>> D.__dict__['f'] -Dotted access from a class calls :meth:`__get__` which just returns the +Dotted access from a class calls :meth:`~object.__get__` which just returns the underlying function unchanged:: >>> D.f The interesting behavior occurs during dotted access from an instance. The -dotted lookup calls :meth:`__get__` which returns a bound method object:: +dotted lookup calls :meth:`~object.__get__` which returns a bound method object:: >>> d = D() >>> d.f @@ -1252,7 +1252,7 @@ Kinds of methods Non-data descriptors provide a simple mechanism for variations on the usual patterns of binding functions into methods. -To recap, functions have a :meth:`__get__` method so that they can be converted +To recap, functions have a :meth:`~object.__get__` method so that they can be converted to a method when accessed as attributes. The non-data descriptor transforms an ``obj.f(*args)`` call into ``f(obj, *args)``. Calling ``cls.f(*args)`` becomes ``f(*args)``. @@ -1682,7 +1682,7 @@ by member descriptors: 'Emulate member_repr() in Objects/descrobject.c' return f'' -The :meth:`type.__new__` method takes care of adding member objects to class +The :meth:`!type.__new__` method takes care of adding member objects to class variables: .. testcode:: @@ -1733,7 +1733,7 @@ Python: ) super().__delattr__(name) -To use the simulation in a real class, just inherit from :class:`Object` and +To use the simulation in a real class, just inherit from :class:`!Object` and set the :term:`metaclass` to :class:`Type`: .. testcode:: diff --git a/Doc/howto/enum.rst b/Doc/howto/enum.rst index 6d43202aca9c0d..9b6bb613749909 100644 --- a/Doc/howto/enum.rst +++ b/Doc/howto/enum.rst @@ -62,12 +62,12 @@ The *type* of an enumeration member is the enum it belongs to:: >>> isinstance(Weekday.FRIDAY, Weekday) True -Enum members have an attribute that contains just their :attr:`name`:: +Enum members have an attribute that contains just their :attr:`!name`:: >>> print(Weekday.TUESDAY.name) TUESDAY -Likewise, they have an attribute for their :attr:`value`:: +Likewise, they have an attribute for their :attr:`!value`:: >>> Weekday.WEDNESDAY.value @@ -75,17 +75,18 @@ Likewise, they have an attribute for their :attr:`value`:: Unlike many languages that treat enumerations solely as name/value pairs, Python Enums can have behavior added. For example, :class:`datetime.date` -has two methods for returning the weekday: :meth:`weekday` and :meth:`isoweekday`. +has two methods for returning the weekday: +:meth:`~datetime.date.weekday` and :meth:`~datetime.date.isoweekday`. The difference is that one of them counts from 0-6 and the other from 1-7. -Rather than keep track of that ourselves we can add a method to the :class:`Weekday` -enum to extract the day from the :class:`date` instance and return the matching +Rather than keep track of that ourselves we can add a method to the :class:`!Weekday` +enum to extract the day from the :class:`~datetime.date` instance and return the matching enum member:: @classmethod def from_date(cls, date): return cls(date.isoweekday()) -The complete :class:`Weekday` enum now looks like this:: +The complete :class:`!Weekday` enum now looks like this:: >>> class Weekday(Enum): ... MONDAY = 1 @@ -108,7 +109,7 @@ Now we can find out what today is! Observe:: Of course, if you're reading this on some other day, you'll see that day instead. -This :class:`Weekday` enum is great if our variable only needs one day, but +This :class:`!Weekday` enum is great if our variable only needs one day, but what if we need several? Maybe we're writing a function to plot chores during a week, and don't want to use a :class:`list` -- we could use a different type of :class:`Enum`:: @@ -126,7 +127,7 @@ of :class:`Enum`:: We've changed two things: we're inherited from :class:`Flag`, and the values are all powers of 2. -Just like the original :class:`Weekday` enum above, we can have a single selection:: +Just like the original :class:`!Weekday` enum above, we can have a single selection:: >>> first_week_day = Weekday.MONDAY >>> first_week_day @@ -201,7 +202,7 @@ If you want to access enum members by *name*, use item access:: >>> Color['GREEN'] -If you have an enum member and need its :attr:`name` or :attr:`value`:: +If you have an enum member and need its :attr:`!name` or :attr:`!value`:: >>> member = Color.RED >>> member.name @@ -282,7 +283,7 @@ If the exact value is unimportant you can use :class:`auto`:: >>> [member.value for member in Color] [1, 2, 3] -The values are chosen by :func:`_generate_next_value_`, which can be +The values are chosen by :func:`~Enum._generate_next_value_`, which can be overridden:: >>> class AutoName(Enum): @@ -301,7 +302,7 @@ overridden:: .. note:: - The :meth:`_generate_next_value_` method must be defined before any members. + The :meth:`~Enum._generate_next_value_` method must be defined before any members. Iteration --------- @@ -422,18 +423,18 @@ Then:: The rules for what is allowed are as follows: names that start and end with a single underscore are reserved by enum and cannot be used; all other attributes defined within an enumeration will become members of this -enumeration, with the exception of special methods (:meth:`__str__`, -:meth:`__add__`, etc.), descriptors (methods are also descriptors), and -variable names listed in :attr:`_ignore_`. +enumeration, with the exception of special methods (:meth:`~object.__str__`, +:meth:`~object.__add__`, etc.), descriptors (methods are also descriptors), and +variable names listed in :attr:`~Enum._ignore_`. -Note: if your enumeration defines :meth:`__new__` and/or :meth:`__init__`, +Note: if your enumeration defines :meth:`~object.__new__` and/or :meth:`~object.__init__`, any value(s) given to the enum member will be passed into those methods. See `Planet`_ for an example. .. note:: - The :meth:`__new__` method, if defined, is used during creation of the Enum - members; it is then replaced by Enum's :meth:`__new__` which is used after + The :meth:`~object.__new__` method, if defined, is used during creation of the Enum + members; it is then replaced by Enum's :meth:`~object.__new__` which is used after class creation for lookup of existing members. See :ref:`new-vs-init` for more details. @@ -525,7 +526,7 @@ from that module. nested in other classes. It is possible to modify how enum members are pickled/unpickled by defining -:meth:`__reduce_ex__` in the enumeration class. The default method is by-value, +:meth:`~object.__reduce_ex__` in the enumeration class. The default method is by-value, but enums with complicated values may want to use by-name:: >>> import enum @@ -561,7 +562,7 @@ values. The last two options enable assigning arbitrary values to enumerations; the others auto-assign increasing integers starting with 1 (use the ``start`` parameter to specify a different starting value). A new class derived from :class:`Enum` is returned. In other words, the above -assignment to :class:`Animal` is equivalent to:: +assignment to :class:`!Animal` is equivalent to:: >>> class Animal(Enum): ... ANT = 1 @@ -872,7 +873,7 @@ simple to implement independently:: pass This demonstrates how similar derived enumerations can be defined; for example -a :class:`FloatEnum` that mixes in :class:`float` instead of :class:`int`. +a :class:`!FloatEnum` that mixes in :class:`float` instead of :class:`int`. Some rules: @@ -886,32 +887,32 @@ Some rules: additional type, all the members must have values of that type, e.g. :class:`int` above. This restriction does not apply to mix-ins which only add methods and don't specify another type. -4. When another data type is mixed in, the :attr:`value` attribute is *not the +4. When another data type is mixed in, the :attr:`~Enum.value` attribute is *not the same* as the enum member itself, although it is equivalent and will compare equal. -5. A ``data type`` is a mixin that defines :meth:`__new__`, or a +5. A ``data type`` is a mixin that defines :meth:`~object.__new__`, or a :class:`~dataclasses.dataclass` 6. %-style formatting: ``%s`` and ``%r`` call the :class:`Enum` class's - :meth:`__str__` and :meth:`__repr__` respectively; other codes (such as + :meth:`~object.__str__` and :meth:`~object.__repr__` respectively; other codes (such as ``%i`` or ``%h`` for IntEnum) treat the enum member as its mixed-in type. 7. :ref:`Formatted string literals `, :meth:`str.format`, - and :func:`format` will use the enum's :meth:`__str__` method. + and :func:`format` will use the enum's :meth:`~object.__str__` method. .. note:: Because :class:`IntEnum`, :class:`IntFlag`, and :class:`StrEnum` are designed to be drop-in replacements for existing constants, their - :meth:`__str__` method has been reset to their data types' - :meth:`__str__` method. + :meth:`~object.__str__` method has been reset to their data types' + :meth:`~object.__str__` method. .. _new-vs-init: -When to use :meth:`__new__` vs. :meth:`__init__` ------------------------------------------------- +When to use :meth:`~object.__new__` vs. :meth:`~object.__init__` +---------------------------------------------------------------- -:meth:`__new__` must be used whenever you want to customize the actual value of +:meth:`~object.__new__` must be used whenever you want to customize the actual value of the :class:`Enum` member. Any other modifications may go in either -:meth:`__new__` or :meth:`__init__`, with :meth:`__init__` being preferred. +:meth:`~object.__new__` or :meth:`~object.__init__`, with :meth:`~object.__init__` being preferred. For example, if you want to pass several items to the constructor, but only want one of them to be the value:: @@ -950,28 +951,28 @@ Finer Points Supported ``__dunder__`` names """""""""""""""""""""""""""""" -:attr:`__members__` is a read-only ordered mapping of ``member_name``:``member`` +:attr:`~enum.EnumType.__members__` is a read-only ordered mapping of ``member_name``:``member`` items. It is only available on the class. -:meth:`__new__`, if specified, must create and return the enum members; it is -also a very good idea to set the member's :attr:`_value_` appropriately. Once +:meth:`~object.__new__`, if specified, must create and return the enum members; it is +also a very good idea to set the member's :attr:`~Enum._value_` appropriately. Once all the members are created it is no longer used. Supported ``_sunder_`` names """""""""""""""""""""""""""" -- ``_name_`` -- name of the member -- ``_value_`` -- value of the member; can be set / modified in ``__new__`` +- :attr:`~Enum._name_` -- name of the member +- :attr:`~Enum._value_` -- value of the member; can be set / modified in ``__new__`` -- ``_missing_`` -- a lookup function used when a value is not found; may be +- :meth:`~Enum._missing_` -- a lookup function used when a value is not found; may be overridden -- ``_ignore_`` -- a list of names, either as a :class:`list` or a :class:`str`, +- :attr:`~Enum._ignore_` -- a list of names, either as a :class:`list` or a :class:`str`, that will not be transformed into members, and will be removed from the final class -- ``_order_`` -- used in Python 2/3 code to ensure member order is consistent +- :attr:`~Enum._order_` -- used in Python 2/3 code to ensure member order is consistent (class attribute, removed during class creation) -- ``_generate_next_value_`` -- used by the `Functional API`_ and by +- :meth:`~Enum._generate_next_value_` -- used by the `Functional API`_ and by :class:`auto` to get an appropriate value for an enum member; may be overridden @@ -986,7 +987,7 @@ Supported ``_sunder_`` names .. versionadded:: 3.6 ``_missing_``, ``_order_``, ``_generate_next_value_`` .. versionadded:: 3.7 ``_ignore_`` -To help keep Python 2 / Python 3 code in sync an :attr:`_order_` attribute can +To help keep Python 2 / Python 3 code in sync an :attr:`~Enum._order_` attribute can be provided. It will be checked against the actual order of the enumeration and raise an error if the two do not match:: @@ -1004,7 +1005,7 @@ and raise an error if the two do not match:: .. note:: - In Python 2 code the :attr:`_order_` attribute is necessary as definition + In Python 2 code the :attr:`~Enum._order_` attribute is necessary as definition order is lost before it can be recorded. @@ -1193,12 +1194,12 @@ Enum Classes ^^^^^^^^^^^^ The :class:`EnumType` metaclass is responsible for providing the -:meth:`__contains__`, :meth:`__dir__`, :meth:`__iter__` and other methods that +:meth:`~object.__contains__`, :meth:`~object.__dir__`, :meth:`~object.__iter__` and other methods that allow one to do things with an :class:`Enum` class that fail on a typical class, such as ``list(Color)`` or ``some_enum_var in Color``. :class:`EnumType` is responsible for ensuring that various other methods on the final :class:`Enum` -class are correct (such as :meth:`__new__`, :meth:`__getnewargs__`, -:meth:`__str__` and :meth:`__repr__`). +class are correct (such as :meth:`~object.__new__`, :meth:`~object.__getnewargs__`, +:meth:`~object.__str__` and :meth:`~object.__repr__`). Flag Classes ^^^^^^^^^^^^ @@ -1213,7 +1214,7 @@ Enum Members (aka instances) The most interesting thing about enum members is that they are singletons. :class:`EnumType` creates them all while it is creating the enum class itself, -and then puts a custom :meth:`__new__` in place to ensure that no new ones are +and then puts a custom :meth:`~object.__new__` in place to ensure that no new ones are ever instantiated by returning only the existing member instances. Flag Members @@ -1261,7 +1262,7 @@ is. There are several ways to define this type of simple enumeration: - use instances of :class:`auto` for the value - use instances of :class:`object` as the value - use a descriptive string as the value -- use a tuple as the value and a custom :meth:`__new__` to replace the +- use a tuple as the value and a custom :meth:`~object.__new__` to replace the tuple with an :class:`int` value Using any of these methods signifies to the user that these values are not @@ -1297,7 +1298,7 @@ Using :class:`object` would look like:: > This is also a good example of why you might want to write your own -:meth:`__repr__`:: +:meth:`~object.__repr__`:: >>> class Color(Enum): ... RED = object() @@ -1325,10 +1326,10 @@ Using a string as the value would look like:: -Using a custom :meth:`__new__` -"""""""""""""""""""""""""""""" +Using a custom :meth:`~object.__new__` +"""""""""""""""""""""""""""""""""""""" -Using an auto-numbering :meth:`__new__` would look like:: +Using an auto-numbering :meth:`~object.__new__` would look like:: >>> class AutoNumber(Enum): ... def __new__(cls): @@ -1374,8 +1375,8 @@ to handle any extra arguments:: .. note:: - The :meth:`__new__` method, if defined, is used during creation of the Enum - members; it is then replaced by Enum's :meth:`__new__` which is used after + The :meth:`~object.__new__` method, if defined, is used during creation of the Enum + members; it is then replaced by Enum's :meth:`~object.__new__` which is used after class creation for lookup of existing members. .. warning:: @@ -1458,7 +1459,7 @@ alias:: Planet ^^^^^^ -If :meth:`__new__` or :meth:`__init__` is defined, the value of the enum member +If :meth:`~object.__new__` or :meth:`~object.__init__` is defined, the value of the enum member will be passed to those methods:: >>> class Planet(Enum): @@ -1489,7 +1490,7 @@ will be passed to those methods:: TimePeriod ^^^^^^^^^^ -An example to show the :attr:`_ignore_` attribute in use:: +An example to show the :attr:`~Enum._ignore_` attribute in use:: >>> from datetime import timedelta >>> class Period(timedelta, Enum): diff --git a/Doc/tools/.nitignore b/Doc/tools/.nitignore index 108ad3938a135b..a0fff8e4a87d35 100644 --- a/Doc/tools/.nitignore +++ b/Doc/tools/.nitignore @@ -14,8 +14,6 @@ Doc/c-api/type.rst Doc/c-api/typeobj.rst Doc/extending/extending.rst Doc/glossary.rst -Doc/howto/descriptor.rst -Doc/howto/enum.rst Doc/library/2to3.rst Doc/library/aifc.rst Doc/library/ast.rst From 97ed21636cd204df4295cbc4f9c27053b6fd8a45 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Wed, 27 Nov 2024 13:22:32 +0100 Subject: [PATCH 243/269] [3.12] gh-109746: Fix race condition in test_start_new_thread_failed (GH-127299) (GH-127324) (cherry picked from commit 83926d3b4c7847394b5e2531e9566d7fc9fbea0f) Co-authored-by: Serhiy Storchaka --- Lib/test/test_threading.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_threading.py b/Lib/test/test_threading.py index 183af7c1f0836f..75a56f7830bead 100644 --- a/Lib/test/test_threading.py +++ b/Lib/test/test_threading.py @@ -1175,11 +1175,11 @@ def f(): except RuntimeError: print('ok') else: - print('skip') + print('!skip!') """ _, out, err = assert_python_ok("-u", "-c", code) out = out.strip() - if out == b'skip': + if b'!skip!' in out: self.skipTest('RLIMIT_NPROC had no effect; probably superuser') self.assertEqual(out, b'ok') self.assertEqual(err, b'') From c3bb32de9df7cb22b40b06098d2f956a8ff1e4d0 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Wed, 27 Nov 2024 15:00:30 +0100 Subject: [PATCH 244/269] [3.12] gh-124008: Fix calculation of the number of written bytes for the Windows console (GH-124059) (GH-127326) Since MultiByteToWideChar()/WideCharToMultiByte() is not reversible if the data contains invalid UTF-8 sequences, use binary search to calculate the number of written bytes from the number of written characters. Also fix writing incomplete UTF-8 sequences. Also fix handling of memory allocation failures. (cherry picked from commit 3cf83d91a5baf3600dd60f7aaaf4fb6d73c4b8a9) Co-authored-by: Serhiy Storchaka --- Lib/test/test_winconsoleio.py | 23 ++++ ...-09-13-18-24-27.gh-issue-124008.XaiPQx.rst | 2 + Modules/_io/winconsoleio.c | 118 +++++++++++++----- 3 files changed, 115 insertions(+), 28 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-09-13-18-24-27.gh-issue-124008.XaiPQx.rst diff --git a/Lib/test/test_winconsoleio.py b/Lib/test/test_winconsoleio.py index cf8b105823b262..459d219290e432 100644 --- a/Lib/test/test_winconsoleio.py +++ b/Lib/test/test_winconsoleio.py @@ -126,6 +126,29 @@ def test_write_empty_data(self): with ConIO('CONOUT$', 'w') as f: self.assertEqual(f.write(b''), 0) + @requires_resource('console') + def test_write(self): + testcases = [] + with ConIO('CONOUT$', 'w') as f: + for a in [ + b'', + b'abc', + b'\xc2\xa7\xe2\x98\x83\xf0\x9f\x90\x8d', + b'\xff'*10, + ]: + for b in b'\xc2\xa7', b'\xe2\x98\x83', b'\xf0\x9f\x90\x8d': + testcases.append(a + b) + for i in range(1, len(b)): + data = a + b[:i] + testcases.append(data + b'z') + testcases.append(data + b'\xff') + # incomplete multibyte sequence + with self.subTest(data=data): + self.assertEqual(f.write(data), len(a)) + for data in testcases: + with self.subTest(data=data): + self.assertEqual(f.write(data), len(data)) + def assertStdinRoundTrip(self, text): stdin = open('CONIN$', 'r') old_stdin = sys.stdin diff --git a/Misc/NEWS.d/next/Library/2024-09-13-18-24-27.gh-issue-124008.XaiPQx.rst b/Misc/NEWS.d/next/Library/2024-09-13-18-24-27.gh-issue-124008.XaiPQx.rst new file mode 100644 index 00000000000000..cd6dd9a7a97e90 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-09-13-18-24-27.gh-issue-124008.XaiPQx.rst @@ -0,0 +1,2 @@ +Fix possible crash (in debug build), incorrect output or returning incorrect +value from raw binary ``write()`` when writing to console on Windows. diff --git a/Modules/_io/winconsoleio.c b/Modules/_io/winconsoleio.c index c2c365e0807f0a..da4907ec2db2a8 100644 --- a/Modules/_io/winconsoleio.c +++ b/Modules/_io/winconsoleio.c @@ -135,19 +135,67 @@ char _PyIO_get_console_type(PyObject *path_or_fd) { } static DWORD -_find_last_utf8_boundary(const char *buf, DWORD len) +_find_last_utf8_boundary(const unsigned char *buf, DWORD len) { - /* This function never returns 0, returns the original len instead */ - DWORD count = 1; - if (len == 0 || (buf[len - 1] & 0x80) == 0) { - return len; - } - for (;; count++) { - if (count > 3 || count >= len) { + for (DWORD count = 1; count < 4 && count <= len; count++) { + unsigned char c = buf[len - count]; + if (c < 0x80) { + /* No starting byte found. */ return len; } - if ((buf[len - count] & 0xc0) != 0x80) { - return len - count; + if (c >= 0xc0) { + if (c < 0xe0 /* 2-bytes sequence */ ? count < 2 : + c < 0xf0 /* 3-bytes sequence */ ? count < 3 : + c < 0xf8 /* 4-bytes sequence */) + { + /* Incomplete multibyte sequence. */ + return len - count; + } + /* Either complete or invalid sequence. */ + return len; + } + } + /* Either complete 4-bytes sequence or invalid sequence. */ + return len; +} + +/* Find the number of UTF-8 bytes that corresponds to the specified number of + * wchars. + * I.e. find x <= len so that MultiByteToWideChar(CP_UTF8, 0, s, x, NULL, 0) == n. + * + * WideCharToMultiByte() cannot be used for this, because the UTF-8 -> wchar + * conversion is not reversible (invalid UTF-8 byte produces \ufffd which + * will be converted back to 3-bytes UTF-8 sequence \xef\xbf\xbd). + * So we need to use binary search. + */ +static DWORD +_wchar_to_utf8_count(const unsigned char *s, DWORD len, DWORD n) +{ + DWORD start = 0; + while (1) { + DWORD mid = 0; + for (DWORD i = len / 2; i <= len; i++) { + mid = _find_last_utf8_boundary(s, i); + if (mid != 0) { + break; + } + /* The middle could split the first multibytes sequence. */ + } + if (mid == len) { + return start + len; + } + if (mid == 0) { + mid = len > 1 ? len - 1 : 1; + } + DWORD wlen = MultiByteToWideChar(CP_UTF8, 0, s, mid, NULL, 0); + if (wlen <= n) { + s += mid; + start += mid; + len -= mid; + n -= wlen; + } + else { + len = mid; } } } @@ -556,8 +604,10 @@ read_console_w(HANDLE handle, DWORD maxlen, DWORD *readlen) { int err = 0, sig = 0; wchar_t *buf = (wchar_t*)PyMem_Malloc(maxlen * sizeof(wchar_t)); - if (!buf) + if (!buf) { + PyErr_NoMemory(); goto error; + } *readlen = 0; @@ -615,6 +665,7 @@ read_console_w(HANDLE handle, DWORD maxlen, DWORD *readlen) { Py_UNBLOCK_THREADS if (!newbuf) { sig = -1; + PyErr_NoMemory(); break; } buf = newbuf; @@ -638,8 +689,10 @@ read_console_w(HANDLE handle, DWORD maxlen, DWORD *readlen) { if (*readlen > 0 && buf[0] == L'\x1a') { PyMem_Free(buf); buf = (wchar_t *)PyMem_Malloc(sizeof(wchar_t)); - if (!buf) + if (!buf) { + PyErr_NoMemory(); goto error; + } buf[0] = L'\0'; *readlen = 0; } @@ -817,8 +870,10 @@ _io__WindowsConsoleIO_readall_impl(winconsoleio *self) bufsize = BUFSIZ; buf = (wchar_t*)PyMem_Malloc((bufsize + 1) * sizeof(wchar_t)); - if (buf == NULL) + if (buf == NULL) { + PyErr_NoMemory(); return NULL; + } while (1) { wchar_t *subbuf; @@ -840,6 +895,7 @@ _io__WindowsConsoleIO_readall_impl(winconsoleio *self) (bufsize + 1) * sizeof(wchar_t)); if (tmp == NULL) { PyMem_Free(buf); + PyErr_NoMemory(); return NULL; } buf = tmp; @@ -1015,43 +1071,49 @@ _io__WindowsConsoleIO_write_impl(winconsoleio *self, PyTypeObject *cls, len = (DWORD)b->len; Py_BEGIN_ALLOW_THREADS - wlen = MultiByteToWideChar(CP_UTF8, 0, b->buf, len, NULL, 0); - /* issue11395 there is an unspecified upper bound on how many bytes can be written at once. We cap at 32k - the caller will have to handle partial writes. Since we don't know how many input bytes are being ignored, we have to reduce and recalculate. */ - while (wlen > 32766 / sizeof(wchar_t)) { - len /= 2; + const DWORD max_wlen = 32766 / sizeof(wchar_t); + /* UTF-8 to wchar ratio is at most 3:1. */ + len = Py_MIN(len, max_wlen * 3); + while (1) { /* Fix for github issues gh-110913 and gh-82052. */ len = _find_last_utf8_boundary(b->buf, len); wlen = MultiByteToWideChar(CP_UTF8, 0, b->buf, len, NULL, 0); + if (wlen <= max_wlen) { + break; + } + len /= 2; } Py_END_ALLOW_THREADS - if (!wlen) - return PyErr_SetFromWindowsErr(0); + if (!wlen) { + return PyLong_FromLong(0); + } wbuf = (wchar_t*)PyMem_Malloc(wlen * sizeof(wchar_t)); + if (!wbuf) { + PyErr_NoMemory(); + return NULL; + } Py_BEGIN_ALLOW_THREADS wlen = MultiByteToWideChar(CP_UTF8, 0, b->buf, len, wbuf, wlen); if (wlen) { res = WriteConsoleW(handle, wbuf, wlen, &n, NULL); +#ifdef Py_DEBUG + if (res) { +#else if (res && n < wlen) { +#endif /* Wrote fewer characters than expected, which means our * len value may be wrong. So recalculate it from the - * characters that were written. As this could potentially - * result in a different value, we also validate that value. + * characters that were written. */ - len = WideCharToMultiByte(CP_UTF8, 0, wbuf, n, - NULL, 0, NULL, NULL); - if (len) { - wlen = MultiByteToWideChar(CP_UTF8, 0, b->buf, len, - NULL, 0); - assert(wlen == len); - } + len = _wchar_to_utf8_count(b->buf, len, n); } } else res = 0; From f4c9f391650ebca213905c1308f87abac670033e Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Thu, 28 Nov 2024 14:57:35 +0100 Subject: [PATCH 245/269] [3.12] gh-123967: Fix faulthandler for trampoline frames (#127329) (#127363) gh-123967: Fix faulthandler for trampoline frames (#127329) If the top-most frame is a trampoline frame, skip it. (cherry picked from commit 58e334e1431b2ed6b70ee42501ea73e08084e769) --- ...-11-27-14-06-35.gh-issue-123967.wxUmnW.rst | 2 ++ Python/traceback.c | 23 +++++++++++-------- 2 files changed, 16 insertions(+), 9 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-11-27-14-06-35.gh-issue-123967.wxUmnW.rst diff --git a/Misc/NEWS.d/next/Library/2024-11-27-14-06-35.gh-issue-123967.wxUmnW.rst b/Misc/NEWS.d/next/Library/2024-11-27-14-06-35.gh-issue-123967.wxUmnW.rst new file mode 100644 index 00000000000000..788fe0c78ef257 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-11-27-14-06-35.gh-issue-123967.wxUmnW.rst @@ -0,0 +1,2 @@ +Fix faulthandler for trampoline frames. If the top-most frame is a +trampoline frame, skip it. Patch by Victor Stinner. diff --git a/Python/traceback.c b/Python/traceback.c index fdaf19d37074dd..fba3594e97ceac 100644 --- a/Python/traceback.c +++ b/Python/traceback.c @@ -1242,6 +1242,8 @@ _Py_DumpASCII(int fd, PyObject *text) static void dump_frame(int fd, _PyInterpreterFrame *frame) { + assert(frame->owner != FRAME_OWNED_BY_CSTACK); + PyCodeObject *code = frame->f_code; PUTS(fd, " File "); if (code->co_filename != NULL @@ -1315,24 +1317,27 @@ dump_traceback(int fd, PyThreadState *tstate, int write_header) unsigned int depth = 0; while (1) { + if (frame->owner == FRAME_OWNED_BY_CSTACK) { + /* Trampoline frame */ + frame = frame->previous; + if (frame == NULL) { + break; + } + + /* Can't have more than one shim frame in a row */ + assert(frame->owner != FRAME_OWNED_BY_CSTACK); + } + if (MAX_FRAME_DEPTH <= depth) { PUTS(fd, " ...\n"); break; } + dump_frame(fd, frame); frame = frame->previous; if (frame == NULL) { break; } - if (frame->owner == FRAME_OWNED_BY_CSTACK) { - /* Trampoline frame */ - frame = frame->previous; - } - if (frame == NULL) { - break; - } - /* Can't have more than one shim frame in a row */ - assert(frame->owner != FRAME_OWNED_BY_CSTACK); depth++; } } From 7d175caf212565b3b89cf3c537c38adab52306a5 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Thu, 28 Nov 2024 18:32:50 +0100 Subject: [PATCH 246/269] [3.12] gh-127190: Fix local_setattro() error handling (GH-127366) (#127368) gh-127190: Fix local_setattro() error handling (GH-127366) Don't make the assumption that the 'name' argument is a string. Use repr() to format the 'name' argument instead. (cherry picked from commit 20657fbdb14d50ca4ec115da0cbef155871d8d33) Co-authored-by: Victor Stinner --- Lib/test/test_threading_local.py | 15 +++++++++++++++ Modules/_threadmodule.c | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_threading_local.py b/Lib/test/test_threading_local.py index f0b829a978feb5..3a58afd8194a32 100644 --- a/Lib/test/test_threading_local.py +++ b/Lib/test/test_threading_local.py @@ -208,6 +208,21 @@ def test_threading_local_clear_race(self): _testcapi.join_temporary_c_thread() + @support.cpython_only + def test_error(self): + class Loop(self._local): + attr = 1 + + # Trick the "if name == '__dict__':" test of __setattr__() + # to always be true + class NameCompareTrue: + def __eq__(self, other): + return True + + loop = Loop() + with self.assertRaisesRegex(AttributeError, 'Loop.*read-only'): + loop.__setattr__(NameCompareTrue(), 2) + class ThreadLocalTest(unittest.TestCase, BaseLocalTest): _local = _thread._local diff --git a/Modules/_threadmodule.c b/Modules/_threadmodule.c index 518c246e98caf6..366ee6186de0d5 100644 --- a/Modules/_threadmodule.c +++ b/Modules/_threadmodule.c @@ -961,7 +961,7 @@ local_setattro(localobject *self, PyObject *name, PyObject *v) } if (r == 1) { PyErr_Format(PyExc_AttributeError, - "'%.100s' object attribute '%U' is read-only", + "'%.100s' object attribute %R is read-only", Py_TYPE(self)->tp_name, name); return -1; } From 91399c39845b7ca4a48b5feaec030a349ff3f68e Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Fri, 29 Nov 2024 10:22:27 +0100 Subject: [PATCH 247/269] [3.12] gh-127303: Add docs for token.EXACT_TOKEN_TYPES (GH-127304) (#127391) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit gh-127303: Add docs for token.EXACT_TOKEN_TYPES (GH-127304) --------- (cherry picked from commit dd3a87d2a8f8750978359a99de2c5cb2168351d1) Co-authored-by: Илья Любавский <100635212+lubaskinc0de@users.noreply.github.com> Co-authored-by: Terry Jan Reedy Co-authored-by: Tomas R. Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> --- Doc/library/token.rst | 7 +++++++ Lib/token.py | 3 ++- Misc/ACKS | 1 + .../Library/2024-11-27-16-06-10.gh-issue-127303.asqkgh.rst | 1 + Tools/build/generate_token.py | 3 ++- 5 files changed, 13 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-11-27-16-06-10.gh-issue-127303.asqkgh.rst diff --git a/Doc/library/token.rst b/Doc/library/token.rst index e27a3b96d8fade..6d06ae8205dc01 100644 --- a/Doc/library/token.rst +++ b/Doc/library/token.rst @@ -79,6 +79,13 @@ the :mod:`tokenize` module. ``type_comments=True``. +.. data:: EXACT_TOKEN_TYPES + + A dictionary mapping the string representation of a token to its numeric code. + + .. versionadded:: 3.8 + + .. versionchanged:: 3.5 Added :data:`AWAIT` and :data:`ASYNC` tokens. diff --git a/Lib/token.py b/Lib/token.py index 487f6edd3c951c..e26d36bd64e3c1 100644 --- a/Lib/token.py +++ b/Lib/token.py @@ -1,7 +1,8 @@ """Token constants.""" # Auto-generated by Tools/build/generate_token.py -__all__ = ['tok_name', 'ISTERMINAL', 'ISNONTERMINAL', 'ISEOF'] +__all__ = ['tok_name', 'ISTERMINAL', 'ISNONTERMINAL', 'ISEOF', + 'EXACT_TOKEN_TYPES'] ENDMARKER = 0 NAME = 1 diff --git a/Misc/ACKS b/Misc/ACKS index cb9de23eeeac11..0f8b79f26fbf6b 100644 --- a/Misc/ACKS +++ b/Misc/ACKS @@ -1139,6 +1139,7 @@ Mark Lutz Taras Lyapun Jim Lynch Mikael Lyngvig +Ilya Lyubavski Jeff MacDonald John Machin Andrew I MacIntyre diff --git a/Misc/NEWS.d/next/Library/2024-11-27-16-06-10.gh-issue-127303.asqkgh.rst b/Misc/NEWS.d/next/Library/2024-11-27-16-06-10.gh-issue-127303.asqkgh.rst new file mode 100644 index 00000000000000..58ebf5d0abe141 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-11-27-16-06-10.gh-issue-127303.asqkgh.rst @@ -0,0 +1 @@ +Publicly expose :data:`~token.EXACT_TOKEN_TYPES` in :attr:`!token.__all__`. diff --git a/Tools/build/generate_token.py b/Tools/build/generate_token.py index 3bd307c1733867..6d27bc41ba2663 100755 --- a/Tools/build/generate_token.py +++ b/Tools/build/generate_token.py @@ -226,7 +226,8 @@ def make_rst(infile, outfile='Doc/library/token-list.inc'): # {AUTO_GENERATED_BY_SCRIPT} ''' token_py_template += ''' -__all__ = ['tok_name', 'ISTERMINAL', 'ISNONTERMINAL', 'ISEOF'] +__all__ = ['tok_name', 'ISTERMINAL', 'ISNONTERMINAL', 'ISEOF', + 'EXACT_TOKEN_TYPES'] %s N_TOKENS = %d From fc6c2de6de787b99ed41610dda09eb227d5e2726 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Fri, 29 Nov 2024 11:59:22 +0100 Subject: [PATCH 248/269] [3.12] gh-127258: Fix asyncio test_staggered_race_with_eager_tasks() (GH-127358) (#127402) gh-127258: Fix asyncio test_staggered_race_with_eager_tasks() (GH-127358) Replace the sleep(2) with a task which is blocked forever. (cherry picked from commit bfabf96b50b7d6a9c15b298a86ba3633b05a1fd7) Co-authored-by: Victor Stinner --- Lib/test/test_asyncio/test_eager_task_factory.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_asyncio/test_eager_task_factory.py b/Lib/test/test_asyncio/test_eager_task_factory.py index b06832e02f00d6..e2a54dd8d768c8 100644 --- a/Lib/test/test_asyncio/test_eager_task_factory.py +++ b/Lib/test/test_asyncio/test_eager_task_factory.py @@ -225,10 +225,14 @@ async def fail(): await asyncio.sleep(0) raise ValueError("no good") + async def blocked(): + fut = asyncio.Future() + await fut + async def run(): winner, index, excs = await asyncio.staggered.staggered_race( [ - lambda: asyncio.sleep(2, result="sleep2"), + lambda: blocked(), lambda: asyncio.sleep(1, result="sleep1"), lambda: fail() ], From fc0564b3658f4bda9e808042cb9752fb56853bc1 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Fri, 29 Nov 2024 13:02:41 +0200 Subject: [PATCH 249/269] [3.12] gh-127359: Pin Tcl/Tk to 8 (8.6) for testing macOS (GH-127365) (#127394) --- .github/workflows/reusable-macos.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/reusable-macos.yml b/.github/workflows/reusable-macos.yml index 6df272c8839f16..acaab45e5b5e12 100644 --- a/.github/workflows/reusable-macos.yml +++ b/.github/workflows/reusable-macos.yml @@ -36,7 +36,10 @@ jobs: path: config.cache key: ${{ github.job }}-${{ inputs.os }}-${{ env.IMAGE_VERSION }}-${{ inputs.config_hash }} - name: Install Homebrew dependencies - run: brew install pkg-config openssl@3.0 xz gdbm tcl-tk + run: | + brew install pkg-config openssl@3.0 xz gdbm tcl-tk@8 + # Because alternate versions are not symlinked into place by default: + brew link tcl-tk@8 - name: Configure CPython run: | GDBM_CFLAGS="-I$(brew --prefix gdbm)/include" \ From e7ab97862deb8ad69caccbc5c6e1f7b4e670afbd Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Fri, 29 Nov 2024 17:03:24 +0100 Subject: [PATCH 250/269] [3.12] gh-127208: Reject null character in _imp.create_dynamic() (#127400) (#127419) gh-127208: Reject null character in _imp.create_dynamic() (#127400) _imp.create_dynamic() now rejects embedded null characters in the path and in the module name. Backport also the _PyUnicode_AsUTF8NoNUL() function. (cherry picked from commit b14fdadc6c620875a20b7ccc3c9b069e85d8557a) --- Include/internal/pycore_unicodeobject.h | 3 +++ Lib/test/test_import/__init__.py | 13 +++++++++++++ Objects/unicodeobject.c | 12 ++++++++++++ Python/import.c | 8 +++++--- 4 files changed, 33 insertions(+), 3 deletions(-) diff --git a/Include/internal/pycore_unicodeobject.h b/Include/internal/pycore_unicodeobject.h index 44eccdea55beb0..cecdabe4155063 100644 --- a/Include/internal/pycore_unicodeobject.h +++ b/Include/internal/pycore_unicodeobject.h @@ -76,6 +76,9 @@ struct _Py_unicode_state { extern void _PyUnicode_ClearInterned(PyInterpreterState *interp); +// Like PyUnicode_AsUTF8(), but check for embedded null characters. +extern const char* _PyUnicode_AsUTF8NoNUL(PyObject *); + #ifdef __cplusplus } diff --git a/Lib/test/test_import/__init__.py b/Lib/test/test_import/__init__.py index c6accc4183a67f..81b2f33a840f1d 100644 --- a/Lib/test/test_import/__init__.py +++ b/Lib/test/test_import/__init__.py @@ -787,6 +787,19 @@ def test_issue105979(self): self.assertIn("Frozen object named 'x' is invalid", str(cm.exception)) + def test_create_dynamic_null(self): + with self.assertRaisesRegex(ValueError, 'embedded null character'): + class Spec: + name = "a\x00b" + origin = "abc" + _imp.create_dynamic(Spec()) + + with self.assertRaisesRegex(ValueError, 'embedded null character'): + class Spec2: + name = "abc" + origin = "a\x00b" + _imp.create_dynamic(Spec2()) + @skip_if_dont_write_bytecode class FilePermissionTests(unittest.TestCase): diff --git a/Objects/unicodeobject.c b/Objects/unicodeobject.c index 7bd4b221c83cf7..5f3dd26ad7b762 100644 --- a/Objects/unicodeobject.c +++ b/Objects/unicodeobject.c @@ -3991,6 +3991,18 @@ PyUnicode_AsUTF8(PyObject *unicode) return PyUnicode_AsUTF8AndSize(unicode, NULL); } +const char * +_PyUnicode_AsUTF8NoNUL(PyObject *unicode) +{ + Py_ssize_t size; + const char *s = PyUnicode_AsUTF8AndSize(unicode, &size); + if (s && strlen(s) != (size_t)size) { + PyErr_SetString(PyExc_ValueError, "embedded null character"); + return NULL; + } + return s; +} + /* PyUnicode_GetSize() has been deprecated since Python 3.3 because it returned length of Py_UNICODE. diff --git a/Python/import.c b/Python/import.c index 4d0b7fd95569b3..66391c04a0baaa 100644 --- a/Python/import.c +++ b/Python/import.c @@ -917,12 +917,14 @@ extensions_lock_release(void) static void * hashtable_key_from_2_strings(PyObject *str1, PyObject *str2, const char sep) { - Py_ssize_t str1_len, str2_len; - const char *str1_data = PyUnicode_AsUTF8AndSize(str1, &str1_len); - const char *str2_data = PyUnicode_AsUTF8AndSize(str2, &str2_len); + const char *str1_data = _PyUnicode_AsUTF8NoNUL(str1); + const char *str2_data = _PyUnicode_AsUTF8NoNUL(str2); if (str1_data == NULL || str2_data == NULL) { return NULL; } + Py_ssize_t str1_len = strlen(str1_data); + Py_ssize_t str2_len = strlen(str2_data); + /* Make sure sep and the NULL byte won't cause an overflow. */ assert(SIZE_MAX - str1_len - str2_len > 2); size_t size = str1_len + 1 + str2_len + 1; From 7c7ed49b518f986806edf2f3d67198cb516b7a98 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Sat, 30 Nov 2024 10:05:46 +0100 Subject: [PATCH 251/269] [3.12] gh-88110: Clear concurrent.futures.thread._threads_queues after fork to avoid joining parent process' threads (GH-126098) (GH-127164) Threads are gone after fork, so clear the queues too. Otherwise the child process (here created via multiprocessing.Process) crashes on interpreter exit. (cherry picked from commit 1848ce61f349533ae5892a8c24c2e0e3c364fc8a) Co-authored-by: Andrei Bodrov Co-authored-by: Serhiy Storchaka --- Lib/concurrent/futures/thread.py | 1 + .../test_thread_pool.py | 19 +++++++++++++++++++ ...3-02-15-23-54-42.gh-issue-88110.KU6erv.rst | 2 ++ 3 files changed, 22 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2023-02-15-23-54-42.gh-issue-88110.KU6erv.rst diff --git a/Lib/concurrent/futures/thread.py b/Lib/concurrent/futures/thread.py index 3b3a36a5093336..61dbff8a485bcd 100644 --- a/Lib/concurrent/futures/thread.py +++ b/Lib/concurrent/futures/thread.py @@ -41,6 +41,7 @@ def _python_exit(): os.register_at_fork(before=_global_shutdown_lock.acquire, after_in_child=_global_shutdown_lock._at_fork_reinit, after_in_parent=_global_shutdown_lock.release) + os.register_at_fork(after_in_child=_threads_queues.clear) class _WorkItem: diff --git a/Lib/test/test_concurrent_futures/test_thread_pool.py b/Lib/test/test_concurrent_futures/test_thread_pool.py index 812f989d8f3ad2..6e4a4b7caff481 100644 --- a/Lib/test/test_concurrent_futures/test_thread_pool.py +++ b/Lib/test/test_concurrent_futures/test_thread_pool.py @@ -64,6 +64,25 @@ def submit(pool): with futures.ProcessPoolExecutor(1, mp_context=mp.get_context('fork')) as workers: workers.submit(tuple) + @support.requires_fork() + @unittest.skipUnless(hasattr(os, 'register_at_fork'), 'need os.register_at_fork') + def test_process_fork_from_a_threadpool(self): + # bpo-43944: clear concurrent.futures.thread._threads_queues after fork, + # otherwise child process will try to join parent thread + def fork_process_and_return_exitcode(): + # Ignore the warning about fork with threads. + with self.assertWarnsRegex(DeprecationWarning, + r"use of fork\(\) may lead to deadlocks in the child"): + p = mp.get_context('fork').Process(target=lambda: 1) + p.start() + p.join() + return p.exitcode + + with futures.ThreadPoolExecutor(1) as pool: + process_exitcode = pool.submit(fork_process_and_return_exitcode).result() + + self.assertEqual(process_exitcode, 0) + def test_executor_map_current_future_cancel(self): stop_event = threading.Event() log = [] diff --git a/Misc/NEWS.d/next/Library/2023-02-15-23-54-42.gh-issue-88110.KU6erv.rst b/Misc/NEWS.d/next/Library/2023-02-15-23-54-42.gh-issue-88110.KU6erv.rst new file mode 100644 index 00000000000000..42a83edc3ba68d --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-02-15-23-54-42.gh-issue-88110.KU6erv.rst @@ -0,0 +1,2 @@ +Fixed :class:`multiprocessing.Process` reporting a ``.exitcode`` of 1 even on success when +using the ``"fork"`` start method while using a :class:`concurrent.futures.ThreadPoolExecutor`. From ea8a85bb9eb54b9edee1a815cff797310ec13f13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Sat, 30 Nov 2024 11:02:07 +0100 Subject: [PATCH 252/269] [3.12] Link to correct class methods in asyncio primitives docs (GH-127270) (#127438) --- Doc/library/asyncio-sync.rst | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Doc/library/asyncio-sync.rst b/Doc/library/asyncio-sync.rst index 05bdf5488af143..4caa8b9b47564a 100644 --- a/Doc/library/asyncio-sync.rst +++ b/Doc/library/asyncio-sync.rst @@ -262,8 +262,9 @@ Condition Wait until a predicate becomes *true*. The predicate must be a callable which result will be - interpreted as a boolean value. The final value is the - return value. + interpreted as a boolean value. The method will repeatedly + :meth:`~Condition.wait` until the predicate evaluates to *true*. + The final value is the return value. Semaphore @@ -428,7 +429,7 @@ Barrier .. coroutinemethod:: abort() Put the barrier into a broken state. This causes any active or future - calls to :meth:`wait` to fail with the :class:`BrokenBarrierError`. + calls to :meth:`~Barrier.wait` to fail with the :class:`BrokenBarrierError`. Use this for example if one of the tasks needs to abort, to avoid infinite waiting tasks. From f08c82ecad5106a32be0c1b65c4d5be3677ab2cd Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Sun, 1 Dec 2024 08:50:33 +0100 Subject: [PATCH 253/269] [3.12] Add the missing `f` on an f-string error message in multiprocessing. (GH-127462) (#127465) Add the missing `f` on an f-string error message in multiprocessing. (GH-127462) (cherry picked from commit 11c01092d5fa8f02c867a7f1f3c135ce63db4838) Co-authored-by: Gregory P. Smith --- Lib/multiprocessing/connection.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/multiprocessing/connection.py b/Lib/multiprocessing/connection.py index d0582e3cd5406a..fdbc3bda7db7e0 100644 --- a/Lib/multiprocessing/connection.py +++ b/Lib/multiprocessing/connection.py @@ -956,7 +956,7 @@ def answer_challenge(connection, authkey: bytes): f'Protocol error, expected challenge: {message=}') message = message[len(_CHALLENGE):] if len(message) < _MD5ONLY_MESSAGE_LENGTH: - raise AuthenticationError('challenge too short: {len(message)} bytes') + raise AuthenticationError(f'challenge too short: {len(message)} bytes') digest = _create_response(authkey, message) connection.send_bytes(digest) response = connection.recv_bytes(256) # reject large message From 4000cbd46a1f83536eeda690733b81c535b225ed Mon Sep 17 00:00:00 2001 From: Yuki Kobayashi Date: Sun, 1 Dec 2024 17:51:46 +0900 Subject: [PATCH 254/269] [3.12] Docs: Fix incorrect indents in `c-api/type.rst` (GH-127449) (#127461) (cherry picked from commit 33ce8dcf791721fea563715f681dc1593a35b83b) --- Doc/c-api/type.rst | 39 +++++++++++++++++++-------------------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/Doc/c-api/type.rst b/Doc/c-api/type.rst index 94c7a9a7b41981..1564fa94efa9bf 100644 --- a/Doc/c-api/type.rst +++ b/Doc/c-api/type.rst @@ -465,19 +465,19 @@ The following functions and structs are used to create The following “offset” fields cannot be set using :c:type:`PyType_Slot`: - * :c:member:`~PyTypeObject.tp_weaklistoffset` - (use :c:macro:`Py_TPFLAGS_MANAGED_WEAKREF` instead if possible) - * :c:member:`~PyTypeObject.tp_dictoffset` - (use :c:macro:`Py_TPFLAGS_MANAGED_DICT` instead if possible) - * :c:member:`~PyTypeObject.tp_vectorcall_offset` - (use ``"__vectorcalloffset__"`` in - :ref:`PyMemberDef `) - - If it is not possible to switch to a ``MANAGED`` flag (for example, - for vectorcall or to support Python older than 3.12), specify the - offset in :c:member:`Py_tp_members `. - See :ref:`PyMemberDef documentation ` - for details. + * :c:member:`~PyTypeObject.tp_weaklistoffset` + (use :c:macro:`Py_TPFLAGS_MANAGED_WEAKREF` instead if possible) + * :c:member:`~PyTypeObject.tp_dictoffset` + (use :c:macro:`Py_TPFLAGS_MANAGED_DICT` instead if possible) + * :c:member:`~PyTypeObject.tp_vectorcall_offset` + (use ``"__vectorcalloffset__"`` in + :ref:`PyMemberDef `) + + If it is not possible to switch to a ``MANAGED`` flag (for example, + for vectorcall or to support Python older than 3.12), specify the + offset in :c:member:`Py_tp_members `. + See :ref:`PyMemberDef documentation ` + for details. The following fields cannot be set at all when creating a heap type: @@ -497,14 +497,13 @@ The following functions and structs are used to create To avoid issues, use the *bases* argument of :c:func:`PyType_FromSpecWithBases` instead. - .. versionchanged:: 3.9 + .. versionchanged:: 3.9 + Slots in :c:type:`PyBufferProcs` may be set in the unlimited API. - Slots in :c:type:`PyBufferProcs` may be set in the unlimited API. - - .. versionchanged:: 3.11 - :c:member:`~PyBufferProcs.bf_getbuffer` and - :c:member:`~PyBufferProcs.bf_releasebuffer` are now available - under the :ref:`limited API `. + .. versionchanged:: 3.11 + :c:member:`~PyBufferProcs.bf_getbuffer` and + :c:member:`~PyBufferProcs.bf_releasebuffer` are now available + under the :ref:`limited API `. .. c:member:: void *pfunc From 87b712228efa87245a8b64092262d9e3a0193985 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Sun, 1 Dec 2024 10:24:18 +0100 Subject: [PATCH 255/269] [3.12] gh-127356: Fix prepend doctrees directory for gettext target (GH-127357) (#127471) Co-authored-by: Rafael Fontenelle --- Doc/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/Makefile b/Doc/Makefile index 22e43ee3e542ee..4a704ad58b33d3 100644 --- a/Doc/Makefile +++ b/Doc/Makefile @@ -144,7 +144,7 @@ pydoc-topics: build .PHONY: gettext gettext: BUILDER = gettext -gettext: SPHINXOPTS += -d build/doctrees-gettext +gettext: override SPHINXOPTS := -d build/doctrees-gettext $(SPHINXOPTS) gettext: build .PHONY: htmlview From 828db9016bf7d13cb935af079fad26adf4e2b49d Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Mon, 2 Dec 2024 09:29:34 +0100 Subject: [PATCH 256/269] [3.12] gh-127443: Fix some entries in `Doc/data/refcounts.dat` (GH-127451) (#127497) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit gh-127443: Fix some entries in `Doc/data/refcounts.dat` (GH-127451) Fix incorrect entries in `Doc/data/refcounts.dat` (cherry picked from commit 1f8267b85dda655282922ba20df90d0ac6bea634) Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> --- Doc/data/refcounts.dat | 40 +++++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/Doc/data/refcounts.dat b/Doc/data/refcounts.dat index b5d953247f3c46..28532620ba603d 100644 --- a/Doc/data/refcounts.dat +++ b/Doc/data/refcounts.dat @@ -180,7 +180,7 @@ PyCapsule_IsValid:const char*:name:: PyCapsule_New:PyObject*::+1: PyCapsule_New:void*:pointer:: PyCapsule_New:const char *:name:: -PyCapsule_New::void (* destructor)(PyObject* ):: +PyCapsule_New:void (*)(PyObject *):destructor:: PyCapsule_SetContext:int::: PyCapsule_SetContext:PyObject*:self:0: @@ -349,11 +349,11 @@ PyComplex_CheckExact:int::: PyComplex_CheckExact:PyObject*:p:0: PyComplex_FromCComplex:PyObject*::+1: -PyComplex_FromCComplex::Py_complex v:: +PyComplex_FromCComplex:Py_complex:v:: PyComplex_FromDoubles:PyObject*::+1: -PyComplex_FromDoubles::double real:: -PyComplex_FromDoubles::double imag:: +PyComplex_FromDoubles:double:real:: +PyComplex_FromDoubles:double:imag:: PyComplex_ImagAsDouble:double::: PyComplex_ImagAsDouble:PyObject*:op:0: @@ -622,7 +622,9 @@ PyErr_GetExcInfo:PyObject**:pvalue:+1: PyErr_GetExcInfo:PyObject**:ptraceback:+1: PyErr_GetRaisedException:PyObject*::+1: -PyErr_SetRaisedException:::: + +PyErr_SetRaisedException:void::: +PyErr_SetRaisedException:PyObject *:exc:0:stolen PyErr_GivenExceptionMatches:int::: PyErr_GivenExceptionMatches:PyObject*:given:0: @@ -642,9 +644,9 @@ PyErr_NewExceptionWithDoc:PyObject*:dict:0: PyErr_NoMemory:PyObject*::null: PyErr_NormalizeException:void::: -PyErr_NormalizeException:PyObject**:exc::??? -PyErr_NormalizeException:PyObject**:val::??? -PyErr_NormalizeException:PyObject**:tb::??? +PyErr_NormalizeException:PyObject**:exc:+1:??? +PyErr_NormalizeException:PyObject**:val:+1:??? +PyErr_NormalizeException:PyObject**:tb:+1:??? PyErr_Occurred:PyObject*::0: @@ -1268,7 +1270,7 @@ PyMapping_GetItemString:const char*:key:: PyMapping_HasKey:int::: PyMapping_HasKey:PyObject*:o:0: -PyMapping_HasKey:PyObject*:key:: +PyMapping_HasKey:PyObject*:key:0: PyMapping_HasKeyString:int::: PyMapping_HasKeyString:PyObject*:o:0: @@ -1428,7 +1430,7 @@ PyModule_GetState:void*::: PyModule_GetState:PyObject*:module:0: PyModule_New:PyObject*::+1: -PyModule_New::char* name:: +PyModule_New:char*:name:: PyModule_NewObject:PyObject*::+1: PyModule_NewObject:PyObject*:name:+1: @@ -1438,7 +1440,7 @@ PyModule_SetDocString:PyObject*:module:0: PyModule_SetDocString:const char*:docstring:: PyModuleDef_Init:PyObject*::0: -PyModuleDef_Init:PyModuleDef*:def:0: +PyModuleDef_Init:PyModuleDef*:def:: PyNumber_Absolute:PyObject*::+1: PyNumber_Absolute:PyObject*:o:0: @@ -1950,10 +1952,10 @@ PyRun_StringFlags:PyObject*:locals:0: PyRun_StringFlags:PyCompilerFlags*:flags:: PySeqIter_Check:int::: -PySeqIter_Check::op:: +PySeqIter_Check:PyObject *:op:0: PySeqIter_New:PyObject*::+1: -PySeqIter_New:PyObject*:seq:: +PySeqIter_New:PyObject*:seq:0: PySequence_Check:int::: PySequence_Check:PyObject*:o:0: @@ -2393,7 +2395,7 @@ PyUnicode_GET_DATA_SIZE:PyObject*:o:0: PyUnicode_KIND:int::: PyUnicode_KIND:PyObject*:o:0: -PyUnicode_MAX_CHAR_VALUE:::: +PyUnicode_MAX_CHAR_VALUE:Py_UCS4::: PyUnicode_MAX_CHAR_VALUE:PyObject*:o:0: PyUnicode_AS_UNICODE:Py_UNICODE*::: @@ -2480,7 +2482,7 @@ PyUnicode_FromWideChar:const wchar_t*:w:: PyUnicode_FromWideChar:Py_ssize_t:size:: PyUnicode_AsWideChar:Py_ssize_t::: -PyUnicode_AsWideChar:PyObject*:*unicode:0: +PyUnicode_AsWideChar:PyObject*:unicode:0: PyUnicode_AsWideChar:wchar_t*:w:: PyUnicode_AsWideChar:Py_ssize_t:size:: @@ -2533,7 +2535,7 @@ PyUnicode_AsUTF8String:PyObject*:unicode:0: PyUnicode_AsUTF8AndSize:const char*::: PyUnicode_AsUTF8AndSize:PyObject*:unicode:0: -PyUnicode_AsUTF8AndSize:Py_ssize_t*:size:0: +PyUnicode_AsUTF8AndSize:Py_ssize_t*:size:: PyUnicode_AsUTF8:const char*::: PyUnicode_AsUTF8:PyObject*:unicode:0: @@ -2856,13 +2858,13 @@ PyUnicodeDecodeError_SetStart:PyObject*:exc:0: PyUnicodeDecodeError_SetStart:Py_ssize_t:start:: PyWeakref_Check:int::: -PyWeakref_Check:PyObject*:ob:: +PyWeakref_Check:PyObject*:ob:0: PyWeakref_CheckProxy:int::: -PyWeakref_CheckProxy:PyObject*:ob:: +PyWeakref_CheckProxy:PyObject*:ob:0: PyWeakref_CheckRef:int::: -PyWeakref_CheckRef:PyObject*:ob:: +PyWeakref_CheckRef:PyObject*:ob:0: PyWeakref_GET_OBJECT:PyObject*::0: PyWeakref_GET_OBJECT:PyObject*:ref:0: From 9f0d6b71d677363f305c63cb5601c1410310435e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Mon, 2 Dec 2024 13:24:26 +0100 Subject: [PATCH 257/269] [3.12] Fix Unicode encode_wstr_utf8() (#127420) (#127504) Fix Unicode encode_wstr_utf8() (#127420) Raise RuntimeError instead of RuntimeWarning. Co-authored-by: Victor Stinner --- Objects/unicodeobject.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Objects/unicodeobject.c b/Objects/unicodeobject.c index 5f3dd26ad7b762..ba11351f003007 100644 --- a/Objects/unicodeobject.c +++ b/Objects/unicodeobject.c @@ -15446,7 +15446,7 @@ encode_wstr_utf8(wchar_t *wstr, char **str, const char *name) int res; res = _Py_EncodeUTF8Ex(wstr, str, NULL, NULL, 1, _Py_ERROR_STRICT); if (res == -2) { - PyErr_Format(PyExc_RuntimeWarning, "cannot decode %s", name); + PyErr_Format(PyExc_RuntimeError, "cannot encode %s", name); return -1; } if (res < 0) { From 22f8e430f81b8b8966bff0efe65786515a132b7e Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Mon, 2 Dec 2024 14:52:49 +0100 Subject: [PATCH 258/269] [3.12] gh-99880: document rounding mode for new-style formatting (GH-121481) (#126335) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The CPython uses _Py_dg_dtoa(), which does rounding to nearest with half to even tie-breaking rule. If that functions is unavailable, PyOS_double_to_string() fallbacks to system snprintf(). Since CPython 3.12, build requirements include C11 compiler *and* support for IEEE 754 floating point numbers (Annex F). This means that FE_TONEAREST macro is available and, per default, printf-like functions should use same rounding mode as _Py_dg_dtoa(). (cherry picked from commit 7d7d56d8b1147a6b85e1c09d01b164df7c5c4942) Co-authored-by: Sergey B Kirpichev Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> --- Doc/library/string.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Doc/library/string.rst b/Doc/library/string.rst index 9e8e44a8abe770..a000bb49f14800 100644 --- a/Doc/library/string.rst +++ b/Doc/library/string.rst @@ -589,6 +589,11 @@ The available presentation types for :class:`float` and | | as altered by the other format modifiers. | +---------+----------------------------------------------------------+ +The result should be correctly rounded to a given precision ``p`` of digits +after the decimal point. The rounding mode for :class:`float` matches that +of the :func:`round` builtin. For :class:`~decimal.Decimal`, the rounding +mode of the current :ref:`context ` will be used. + The available presentation types for :class:`complex` are the same as those for :class:`float` (``'%'`` is not allowed). Both the real and imaginary components of a complex number are formatted as floating-point numbers, according to the From 9295a1d0334d6b94ff5ae5b13219695954cc6b4c Mon Sep 17 00:00:00 2001 From: Carol Willing Date: Mon, 2 Dec 2024 05:55:31 -0800 Subject: [PATCH 259/269] [3.12] Docs: Miscellaneous corrections to simple statements in the language reference (GH-126720) (#126891) * Replace: The :keyword:`global` -> The :keyword:`global` statement Add :keyword: when it's needed * Replace repeated links with duoble backticks (cherry picked from commit 94a7a4e22fb8f567090514785c69e65298acca42) Co-authored-by: Beomsoo Kim --- Doc/reference/simple_stmts.rst | 33 +++++++++++---------------------- 1 file changed, 11 insertions(+), 22 deletions(-) diff --git a/Doc/reference/simple_stmts.rst b/Doc/reference/simple_stmts.rst index 618664b23f0680..5e3ede69d1e5d7 100644 --- a/Doc/reference/simple_stmts.rst +++ b/Doc/reference/simple_stmts.rst @@ -406,9 +406,9 @@ The extended form, ``assert expression1, expression2``, is equivalent to :: These equivalences assume that :const:`__debug__` and :exc:`AssertionError` refer to the built-in variables with those names. In the current implementation, the -built-in variable :const:`__debug__` is ``True`` under normal circumstances, +built-in variable ``__debug__`` is ``True`` under normal circumstances, ``False`` when optimization is requested (command line option :option:`-O`). The current -code generator emits no code for an assert statement when optimization is +code generator emits no code for an :keyword:`assert` statement when optimization is requested at compile time. Note that it is unnecessary to include the source code for the expression that failed in the error message; it will be displayed as part of the stack trace. @@ -531,8 +531,8 @@ The :keyword:`!yield` statement yield_stmt: `yield_expression` A :keyword:`yield` statement is semantically equivalent to a :ref:`yield -expression `. The yield statement can be used to omit the parentheses -that would otherwise be required in the equivalent yield expression +expression `. The ``yield`` statement can be used to omit the +parentheses that would otherwise be required in the equivalent yield expression statement. For example, the yield statements :: yield @@ -544,7 +544,7 @@ are equivalent to the yield expression statements :: (yield from ) Yield expressions and statements are only used when defining a :term:`generator` -function, and are only used in the body of the generator function. Using yield +function, and are only used in the body of the generator function. Using :keyword:`yield` in a function definition is sufficient to cause that definition to create a generator function instead of a normal function. @@ -964,25 +964,14 @@ The :keyword:`!global` statement .. productionlist:: python-grammar global_stmt: "global" `identifier` ("," `identifier`)* -The :keyword:`global` statement is a declaration which holds for the entire -current code block. It means that the listed identifiers are to be interpreted -as globals. It would be impossible to assign to a global variable without +The :keyword:`global` statement causes the listed identifiers to be interpreted +as globals. It would be impossible to assign to a global variable without :keyword:`!global`, although free variables may refer to globals without being declared global. -Names listed in a :keyword:`global` statement must not be used in the same code -block textually preceding that :keyword:`!global` statement. - -Names listed in a :keyword:`global` statement must not be defined as formal -parameters, or as targets in :keyword:`with` statements or :keyword:`except` clauses, or in a :keyword:`for` target list, :keyword:`class` -definition, function definition, :keyword:`import` statement, or variable -annotation. - -.. impl-detail:: - - The current implementation does not enforce some of these restrictions, but - programs should not abuse this freedom, as future implementations may enforce - them or silently change the meaning of the program. +The :keyword:`global` statement applies to the entire scope of a function or +class body. A :exc:`SyntaxError` is raised if a variable is used or +assigned to prior to its global declaration in the scope. .. index:: pair: built-in function; exec @@ -1018,7 +1007,7 @@ identifiers. If a name is bound in more than one nonlocal scope, the nearest binding is used. If a name is not bound in any nonlocal scope, or if there is no nonlocal scope, a :exc:`SyntaxError` is raised. -The nonlocal statement applies to the entire scope of a function or +The :keyword:`nonlocal` statement applies to the entire scope of a function or class body. A :exc:`SyntaxError` is raised if a variable is used or assigned to prior to its nonlocal declaration in the scope. From 97fd09fba1aa86f77d6fb27c0a37fc10d1655424 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Mon, 2 Dec 2024 14:57:51 +0100 Subject: [PATCH 260/269] [3.12] add missing gc_collect() calls in sqlite3 tests (GH-127446) (#127501) add missing gc_collect() calls in sqlite3 tests (GH-127446) (cherry picked from commit 2a373da7700cf928e0a5ce3998d19351a3565df4) Co-authored-by: CF Bolz-Tereick --- Lib/test/test_sqlite3/test_regression.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Lib/test/test_sqlite3/test_regression.py b/Lib/test/test_sqlite3/test_regression.py index 7e8221e7227e6e..6ea47872627133 100644 --- a/Lib/test/test_sqlite3/test_regression.py +++ b/Lib/test/test_sqlite3/test_regression.py @@ -442,6 +442,7 @@ def test_table_lock_cursor_dealloc(self): con.commit() cur = con.execute("select t from t") del cur + support.gc_collect() con.execute("drop table t") con.commit() @@ -457,6 +458,7 @@ def dup(v): con.create_function("dup", 1, dup) cur = con.execute("select dup(t) from t") del cur + support.gc_collect() con.execute("drop table t") con.commit() From 179b13401405169c63dfd60eb945fe781b0a574a Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Mon, 2 Dec 2024 15:01:46 +0100 Subject: [PATCH 261/269] [3.12] gh-101100: Fix Sphinx warnings about list methods (GH-127054) (#127512) Co-authored-by: Yuki Kobayashi --- Doc/library/collections.rst | 4 ++-- Doc/tools/.nitignore | 1 - Doc/tutorial/datastructures.rst | 10 +++++----- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/Doc/library/collections.rst b/Doc/library/collections.rst index 0cb1a63b614603..c4586afde07103 100644 --- a/Doc/library/collections.rst +++ b/Doc/library/collections.rst @@ -783,10 +783,10 @@ sequence of key-value pairs into a dictionary of lists: When each key is encountered for the first time, it is not already in the mapping; so an entry is automatically created using the :attr:`~defaultdict.default_factory` -function which returns an empty :class:`list`. The :meth:`list.append` +function which returns an empty :class:`list`. The :meth:`!list.append` operation then attaches the value to the new list. When keys are encountered again, the look-up proceeds normally (returning the list for that key) and the -:meth:`list.append` operation adds another value to the list. This technique is +:meth:`!list.append` operation adds another value to the list. This technique is simpler and faster than an equivalent technique using :meth:`dict.setdefault`: >>> d = {} diff --git a/Doc/tools/.nitignore b/Doc/tools/.nitignore index a0fff8e4a87d35..c665ddd1bb9820 100644 --- a/Doc/tools/.nitignore +++ b/Doc/tools/.nitignore @@ -89,7 +89,6 @@ Doc/library/xmlrpc.server.rst Doc/library/zlib.rst Doc/reference/compound_stmts.rst Doc/reference/datamodel.rst -Doc/tutorial/datastructures.rst Doc/using/windows.rst Doc/whatsnew/2.4.rst Doc/whatsnew/2.5.rst diff --git a/Doc/tutorial/datastructures.rst b/Doc/tutorial/datastructures.rst index 31941bc112a135..263b0c2e2815a1 100644 --- a/Doc/tutorial/datastructures.rst +++ b/Doc/tutorial/datastructures.rst @@ -142,8 +142,8 @@ Using Lists as Stacks The list methods make it very easy to use a list as a stack, where the last element added is the first element retrieved ("last-in, first-out"). To add an -item to the top of the stack, use :meth:`~list.append`. To retrieve an item from the -top of the stack, use :meth:`~list.pop` without an explicit index. For example:: +item to the top of the stack, use :meth:`!~list.append`. To retrieve an item from the +top of the stack, use :meth:`!~list.pop` without an explicit index. For example:: >>> stack = [3, 4, 5] >>> stack.append(6) @@ -340,7 +340,7 @@ The :keyword:`!del` statement ============================= There is a way to remove an item from a list given its index instead of its -value: the :keyword:`del` statement. This differs from the :meth:`~list.pop` method +value: the :keyword:`del` statement. This differs from the :meth:`!~list.pop` method which returns a value. The :keyword:`!del` statement can also be used to remove slices from a list or clear the entire list (which we did earlier by assignment of an empty list to the slice). For example:: @@ -500,8 +500,8 @@ any immutable type; strings and numbers can always be keys. Tuples can be used as keys if they contain only strings, numbers, or tuples; if a tuple contains any mutable object either directly or indirectly, it cannot be used as a key. You can't use lists as keys, since lists can be modified in place using index -assignments, slice assignments, or methods like :meth:`~list.append` and -:meth:`~list.extend`. +assignments, slice assignments, or methods like :meth:`!~list.append` and +:meth:`!~list.extend`. It is best to think of a dictionary as a set of *key: value* pairs, with the requirement that the keys are unique (within one dictionary). A pair of From 34137cbd23511269c6315efc082fa34a72e8582f Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Mon, 2 Dec 2024 15:18:53 +0100 Subject: [PATCH 262/269] [3.12] gh-126699: allow AsyncIterator to be used as a base for Protocols (GH-126702) (#126761) gh-126699: allow AsyncIterator to be used as a base for Protocols (GH-126702) (cherry picked from commit feb3e0b19cb03f06364a3f5e970f0861b8883d1c) Co-authored-by: Stephen Morton --- Lib/test/test_typing.py | 3 +++ Lib/typing.py | 3 ++- .../Library/2024-11-11-13-24-22.gh-issue-126699.ONGbMd.rst | 1 + 3 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2024-11-11-13-24-22.gh-issue-126699.ONGbMd.rst diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 106bd81b69e34c..0e533757982d35 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -3998,6 +3998,9 @@ class CustomProtocol(TestCase, Protocol): class CustomContextManager(typing.ContextManager, Protocol): pass + class CustomAsyncIterator(typing.AsyncIterator, Protocol): + pass + def test_non_runtime_protocol_isinstance_check(self): class P(Protocol): x: int diff --git a/Lib/typing.py b/Lib/typing.py index 94c211292ecf9d..a271416d46cc3e 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -1815,7 +1815,8 @@ def _allow_reckless_class_checks(depth=2): _PROTO_ALLOWLIST = { 'collections.abc': [ 'Callable', 'Awaitable', 'Iterable', 'Iterator', 'AsyncIterable', - 'Hashable', 'Sized', 'Container', 'Collection', 'Reversible', 'Buffer', + 'AsyncIterator', 'Hashable', 'Sized', 'Container', 'Collection', + 'Reversible', 'Buffer', ], 'contextlib': ['AbstractContextManager', 'AbstractAsyncContextManager'], } diff --git a/Misc/NEWS.d/next/Library/2024-11-11-13-24-22.gh-issue-126699.ONGbMd.rst b/Misc/NEWS.d/next/Library/2024-11-11-13-24-22.gh-issue-126699.ONGbMd.rst new file mode 100644 index 00000000000000..9741294487d716 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-11-11-13-24-22.gh-issue-126699.ONGbMd.rst @@ -0,0 +1 @@ +Allow :class:`collections.abc.AsyncIterator` to be a base for Protocols. From 34fe4af8a21366de329a3a69d7e87363060bccba Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Mon, 2 Dec 2024 17:19:59 +0300 Subject: [PATCH 263/269] [3.12] gh-126618: fix repr(itertools.count(sys.maxsize)) (GH-127048) (#127510) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit (cherry picked from commit 930ba0ce605eee9e3b992fa368b00a3f2b7dc4c1) Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> --- Lib/test/test_itertools.py | 23 +++++++++++++++++++ ...-11-20-08-54-11.gh-issue-126618.ef_53g.rst | 2 ++ Modules/itertoolsmodule.c | 9 +++----- 3 files changed, 28 insertions(+), 6 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-11-20-08-54-11.gh-issue-126618.ef_53g.rst diff --git a/Lib/test/test_itertools.py b/Lib/test/test_itertools.py index 89eb78ce42994f..5b01f93bffc549 100644 --- a/Lib/test/test_itertools.py +++ b/Lib/test/test_itertools.py @@ -623,6 +623,15 @@ def test_count(self): self.assertEqual(next(c), -8) self.assertEqual(repr(count(10.25)), 'count(10.25)') self.assertEqual(repr(count(10.0)), 'count(10.0)') + + self.assertEqual(repr(count(maxsize)), f'count({maxsize})') + c = count(maxsize - 1) + self.assertEqual(repr(c), f'count({maxsize - 1})') + next(c) # c is now at masize + self.assertEqual(repr(c), f'count({maxsize})') + next(c) + self.assertEqual(repr(c), f'count({maxsize + 1})') + self.assertEqual(type(next(count(10.0))), float) for i in (-sys.maxsize-5, -sys.maxsize+5 ,-10, -1, 0, 10, sys.maxsize-5, sys.maxsize+5): # Test repr @@ -703,6 +712,20 @@ def test_count_with_stride(self): for proto in range(pickle.HIGHEST_PROTOCOL + 1): self.pickletest(proto, count(i, j)) + c = count(maxsize -2, 2) + self.assertEqual(repr(c), f'count({maxsize - 2}, 2)') + next(c) # c is now at masize + self.assertEqual(repr(c), f'count({maxsize}, 2)') + next(c) + self.assertEqual(repr(c), f'count({maxsize + 2}, 2)') + + c = count(maxsize + 1, -1) + self.assertEqual(repr(c), f'count({maxsize + 1}, -1)') + next(c) # c is now at masize + self.assertEqual(repr(c), f'count({maxsize}, -1)') + next(c) + self.assertEqual(repr(c), f'count({maxsize - 1}, -1)') + def test_cycle(self): self.assertEqual(take(10, cycle('abc')), list('abcabcabca')) self.assertEqual(list(cycle('')), []) diff --git a/Misc/NEWS.d/next/Library/2024-11-20-08-54-11.gh-issue-126618.ef_53g.rst b/Misc/NEWS.d/next/Library/2024-11-20-08-54-11.gh-issue-126618.ef_53g.rst new file mode 100644 index 00000000000000..7a0a7b7517b70d --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-11-20-08-54-11.gh-issue-126618.ef_53g.rst @@ -0,0 +1,2 @@ +Fix the representation of :class:`itertools.count` objects when the count +value is :data:`sys.maxsize`. diff --git a/Modules/itertoolsmodule.c b/Modules/itertoolsmodule.c index 17e0eecbad2008..12708f49bdea10 100644 --- a/Modules/itertoolsmodule.c +++ b/Modules/itertoolsmodule.c @@ -3981,7 +3981,7 @@ typedef struct { fast_mode: when cnt an integer < PY_SSIZE_T_MAX and no step is specified. - assert(cnt != PY_SSIZE_T_MAX && long_cnt == NULL && long_step==PyLong(1)); + assert(long_cnt == NULL && long_step==PyLong(1)); Advances with: cnt += 1 When count hits Y_SSIZE_T_MAX, switch to slow_mode. @@ -4037,9 +4037,6 @@ itertools_count_impl(PyTypeObject *type, PyObject *long_cnt, PyErr_Clear(); fast_mode = 0; } - else if (cnt == PY_SSIZE_T_MAX) { - fast_mode = 0; - } } } else { cnt = 0; @@ -4071,7 +4068,7 @@ itertools_count_impl(PyTypeObject *type, PyObject *long_cnt, else cnt = PY_SSIZE_T_MAX; - assert((cnt != PY_SSIZE_T_MAX && long_cnt == NULL && fast_mode) || + assert((long_cnt == NULL && fast_mode) || (cnt == PY_SSIZE_T_MAX && long_cnt != NULL && !fast_mode)); assert(!fast_mode || (PyLong_Check(long_step) && PyLong_AS_LONG(long_step) == 1)); @@ -4143,7 +4140,7 @@ count_next(countobject *lz) static PyObject * count_repr(countobject *lz) { - if (lz->cnt != PY_SSIZE_T_MAX) + if (lz->long_cnt == NULL) return PyUnicode_FromFormat("%s(%zd)", _PyType_Name(Py_TYPE(lz)), lz->cnt); From 8fffbb098259ed413d75dbc2e6d3e540425bd9b9 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Mon, 2 Dec 2024 20:47:42 +0100 Subject: [PATCH 264/269] [3.12] gh-113841: fix possible undefined division by 0 in _Py_c_pow() (GH-127211) (GH-127216) (GH-127530) [3.13] gh-113841: fix possible undefined division by 0 in _Py_c_pow() (GH-127211) (GH-127216) Note, that transformed expression is not an equivalent for original one (1/exp(-x) != exp(x) in general for floating-point numbers). Though, the difference seems to be ~1ULP for good libm implementations. It's more interesting why division was used from beginning. Closest algorithm I've found (no error checks, of course;)) - it's Algorithm 190 from ACM: https://dl.acm.org/doi/10.1145/366663.366679. It uses subtraction in the exponent. (cherry picked from commit f7bb658124aba74be4c13f498bf46cfded710ef9) (cherry picked from commit f41d8d89e79d634895868656f50a0e16e339f9d6) Co-authored-by: Sergey B Kirpichev --- Lib/test/test_complex.py | 5 +++++ .../2024-11-24-07-01-28.gh-issue-113841.WFg-Bu.rst | 2 ++ Objects/complexobject.c | 2 +- 3 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2024-11-24-07-01-28.gh-issue-113841.WFg-Bu.rst diff --git a/Lib/test/test_complex.py b/Lib/test/test_complex.py index 4272c2afcdc274..f12e1c8bd7094a 100644 --- a/Lib/test/test_complex.py +++ b/Lib/test/test_complex.py @@ -300,6 +300,11 @@ def test_pow(self): except OverflowError: pass + # gh-113841: possible undefined division by 0 in _Py_c_pow() + x, y = 9j, 33j**3 + with self.assertRaises(OverflowError): + x**y + def test_pow_with_small_integer_exponents(self): # Check that small integer exponents are handled identically # regardless of their type. diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2024-11-24-07-01-28.gh-issue-113841.WFg-Bu.rst b/Misc/NEWS.d/next/Core_and_Builtins/2024-11-24-07-01-28.gh-issue-113841.WFg-Bu.rst new file mode 100644 index 00000000000000..2b07fdfcc6b527 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2024-11-24-07-01-28.gh-issue-113841.WFg-Bu.rst @@ -0,0 +1,2 @@ +Fix possible undefined behavior division by zero in :class:`complex`'s +:c:func:`_Py_c_pow`. diff --git a/Objects/complexobject.c b/Objects/complexobject.c index 7f7e7e6b08cdca..eff173802984b6 100644 --- a/Objects/complexobject.c +++ b/Objects/complexobject.c @@ -146,7 +146,7 @@ _Py_c_pow(Py_complex a, Py_complex b) at = atan2(a.imag, a.real); phase = at*b.real; if (b.imag != 0.0) { - len /= exp(at*b.imag); + len *= exp(-at*b.imag); phase += b.imag*log(vabs); } r.real = len*cos(phase); From af2d24b5ad896aa4ed464e224d7b205765dad83c Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Mon, 2 Dec 2024 21:03:44 +0100 Subject: [PATCH 265/269] gh-119826: Improved fallback for ntpath.abspath() on Windows (GH-119938) (cherry picked from commit 4b00aba42e4d9440d22e399ec2122fe8601bbe54) Co-authored-by: Nice Zombies --- Lib/ntpath.py | 49 ++++++++++++------- Lib/test/test_ntpath.py | 3 ++ ...-06-02-11-48-19.gh-issue-119826.N1obGa.rst | 1 + 3 files changed, 35 insertions(+), 18 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-06-02-11-48-19.gh-issue-119826.N1obGa.rst diff --git a/Lib/ntpath.py b/Lib/ntpath.py index 2e290dcf9de9da..c05e965fcb91d1 100644 --- a/Lib/ntpath.py +++ b/Lib/ntpath.py @@ -561,28 +561,21 @@ def normpath(path): return prefix + sep.join(comps) -def _abspath_fallback(path): - """Return the absolute version of a path as a fallback function in case - `nt._getfullpathname` is not available or raises OSError. See bpo-31047 for - more. - - """ - - path = os.fspath(path) - if not isabs(path): - if isinstance(path, bytes): - cwd = os.getcwdb() - else: - cwd = os.getcwd() - path = join(cwd, path) - return normpath(path) - # Return an absolute path. try: from nt import _getfullpathname except ImportError: # not running on Windows - mock up something sensible - abspath = _abspath_fallback + def abspath(path): + """Return the absolute version of a path.""" + path = os.fspath(path) + if not isabs(path): + if isinstance(path, bytes): + cwd = os.getcwdb() + else: + cwd = os.getcwd() + path = join(cwd, path) + return normpath(path) else: # use native Windows method on Windows def abspath(path): @@ -590,7 +583,27 @@ def abspath(path): try: return _getfullpathname(normpath(path)) except (OSError, ValueError): - return _abspath_fallback(path) + # See gh-75230, handle outside for cleaner traceback + pass + path = os.fspath(path) + if not isabs(path): + if isinstance(path, bytes): + sep = b'\\' + getcwd = os.getcwdb + else: + sep = '\\' + getcwd = os.getcwd + drive, root, path = splitroot(path) + # Either drive or root can be nonempty, but not both. + if drive or root: + try: + path = join(_getfullpathname(drive + root), path) + except (OSError, ValueError): + # Drive "\0:" cannot exist; use the root directory. + path = drive + sep + path + else: + path = join(getcwd(), path) + return normpath(path) try: from nt import _getfinalpathname, readlink as _nt_readlink diff --git a/Lib/test/test_ntpath.py b/Lib/test/test_ntpath.py index 9c6715b2cc37b4..4924db983b568f 100644 --- a/Lib/test/test_ntpath.py +++ b/Lib/test/test_ntpath.py @@ -743,6 +743,9 @@ def test_abspath(self): tester('ntpath.abspath("C:\\spam. . .")', "C:\\spam") tester('ntpath.abspath("C:/nul")', "\\\\.\\nul") tester('ntpath.abspath("C:\\nul")', "\\\\.\\nul") + self.assertTrue(ntpath.isabs(ntpath.abspath("C:spam"))) + self.assertEqual(ntpath.abspath("C:\x00"), ntpath.join(ntpath.abspath("C:"), "\x00")) + self.assertEqual(ntpath.abspath("\x00:spam"), "\x00:\\spam") tester('ntpath.abspath("//..")', "\\\\") tester('ntpath.abspath("//../")', "\\\\..\\") tester('ntpath.abspath("//../..")', "\\\\..\\") diff --git a/Misc/NEWS.d/next/Library/2024-06-02-11-48-19.gh-issue-119826.N1obGa.rst b/Misc/NEWS.d/next/Library/2024-06-02-11-48-19.gh-issue-119826.N1obGa.rst new file mode 100644 index 00000000000000..6901e7475dd082 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-06-02-11-48-19.gh-issue-119826.N1obGa.rst @@ -0,0 +1 @@ +Always return an absolute path for :func:`os.path.abspath` on Windows. From c40656eeff64bd54bd13c7a146f0b140ffdfdf7b Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Tue, 3 Dec 2024 13:57:58 +0100 Subject: [PATCH 266/269] [3.12] gh-127253: Note that Stable ABI is about ABI stability (GH-127254) (GH-127558) (cherry picked from commit 35d37d6592d1be71ea76042165f6cbfa6c4c3a17) Co-authored-by: Petr Viktorin --- Doc/c-api/stable.rst | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/Doc/c-api/stable.rst b/Doc/c-api/stable.rst index 5b9e43874c7f2b..124e58cf950b7a 100644 --- a/Doc/c-api/stable.rst +++ b/Doc/c-api/stable.rst @@ -66,7 +66,7 @@ Limited C API Python 3.2 introduced the *Limited API*, a subset of Python's C API. Extensions that only use the Limited API can be -compiled once and work with multiple versions of Python. +compiled once and be loaded on multiple versions of Python. Contents of the Limited API are :ref:`listed below `. .. c:macro:: Py_LIMITED_API @@ -76,7 +76,7 @@ Contents of the Limited API are :ref:`listed below `. Define ``Py_LIMITED_API`` to the value of :c:macro:`PY_VERSION_HEX` corresponding to the lowest Python version your extension supports. - The extension will work without recompilation with all Python 3 releases + The extension will be ABI-compatible with all Python 3 releases from the specified one onward, and can use Limited API introduced up to that version. @@ -94,7 +94,15 @@ Stable ABI ---------- To enable this, Python provides a *Stable ABI*: a set of symbols that will -remain compatible across Python 3.x versions. +remain ABI-compatible across Python 3.x versions. + +.. note:: + + The Stable ABI prevents ABI issues, like linker errors due to missing + symbols or data corruption due to changes in structure layouts or function + signatures. + However, other changes in Python can change the *behavior* of extensions. + See Python's Backwards Compatibility Policy (:pep:`387`) for details. The Stable ABI contains symbols exposed in the :ref:`Limited API `, but also other ones – for example, functions necessary to From b49e902b81a0ead84e2002f94a2a2b2ae9b09ada Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Tue, 3 Dec 2024 15:12:59 +0100 Subject: [PATCH 267/269] [3.12] gh-126876: Fix socket internal_select() for large timeout (GH-126968) (#127003) gh-126876: Fix socket internal_select() for large timeout (GH-126968) If the timeout is larger than INT_MAX, replace it with INT_MAX, in the poll() code path. Add an unit test. (cherry picked from commit b3687ad454c4ac54c8599a10f3ace8a13ca48915) Co-authored-by: Victor Stinner --- Lib/test/test_socket.py | 30 ++++++++++++++++++++++++++++++ Modules/socketmodule.c | 5 ++++- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py index fd328a74134bcf..b40ad28f7a7dad 100644 --- a/Lib/test/test_socket.py +++ b/Lib/test/test_socket.py @@ -5059,6 +5059,36 @@ def _testRecv(self): # send data: recv() will no longer block self.cli.sendall(MSG) + @support.cpython_only + def testLargeTimeout(self): + # gh-126876: Check that a timeout larger than INT_MAX is replaced with + # INT_MAX in the poll() code path. The following assertion must not + # fail: assert(INT_MIN <= ms && ms <= INT_MAX). + import _testcapi + large_timeout = _testcapi.INT_MAX + 1 + + # test recv() with large timeout + conn, addr = self.serv.accept() + self.addCleanup(conn.close) + try: + conn.settimeout(large_timeout) + except OverflowError: + # On Windows, settimeout() fails with OverflowError, whereas + # we want to test recv(). Just give up silently. + return + msg = conn.recv(len(MSG)) + + def _testLargeTimeout(self): + # test sendall() with large timeout + import _testcapi + large_timeout = _testcapi.INT_MAX + 1 + self.cli.connect((HOST, self.port)) + try: + self.cli.settimeout(large_timeout) + except OverflowError: + return + self.cli.sendall(MSG) + class FileObjectClassTestCase(SocketConnectedTest): """Unit tests for the object returned by socket.makefile() diff --git a/Modules/socketmodule.c b/Modules/socketmodule.c index 97248792c0f090..7f2ebba9884f98 100644 --- a/Modules/socketmodule.c +++ b/Modules/socketmodule.c @@ -816,7 +816,9 @@ internal_select(PySocketSockObject *s, int writing, _PyTime_t interval, /* s->sock_timeout is in seconds, timeout in ms */ ms = _PyTime_AsMilliseconds(interval, _PyTime_ROUND_CEILING); - assert(ms <= INT_MAX); + if (ms > INT_MAX) { + ms = INT_MAX; + } /* On some OSes, typically BSD-based ones, the timeout parameter of the poll() syscall, when negative, must be exactly INFTIM, where defined, @@ -828,6 +830,7 @@ internal_select(PySocketSockObject *s, int writing, _PyTime_t interval, ms = -1; #endif } + assert(INT_MIN <= ms && ms <= INT_MAX); Py_BEGIN_ALLOW_THREADS; n = poll(&pollfd, 1, (int)ms); From 49da170709dd47c49bfc1e5d4b5d46122a049a3b Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Tue, 3 Dec 2024 18:26:25 +0100 Subject: [PATCH 268/269] [3.12] gh-116510: Fix a Crash Due to Shared Immortal Interned Strings (gh-125205) Fix a crash caused by immortal interned strings being shared between sub-interpreters that use basic single-phase init. In that case, the string can be used by an interpreter that outlives the interpreter that created and interned it. For interpreters that share obmalloc state, also share the interned dict with the main interpreter. This is an un-revert of gh-124646 that then addresses the Py_TRACE_REFS failures identified by gh-124785 (i.e. backporting gh-125709 too). (cherry picked from commit f2cb39947093feda3ff85b8dc820922cc5e5f954, AKA gh-124865) Co-authored-by: Eric Snow --- Doc/library/sys.rst | 29 +++++++++++ Doc/using/configure.rst | 2 +- Doc/whatsnew/3.12.rst | 11 +++++ Include/internal/pycore_object_state.h | 8 +++- Include/internal/pycore_runtime_init.h | 7 --- ...-09-26-18-21-06.gh-issue-116510.FacUWO.rst | 5 ++ Objects/object.c | 22 +++++++-- Objects/unicodeobject.c | 48 ++++++++++++++++--- Python/pylifecycle.c | 8 ++++ Python/pystate.c | 4 +- 10 files changed, 124 insertions(+), 20 deletions(-) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2024-09-26-18-21-06.gh-issue-116510.FacUWO.rst diff --git a/Doc/library/sys.rst b/Doc/library/sys.rst index bf2ea94f23f84d..221f9bd6942207 100644 --- a/Doc/library/sys.rst +++ b/Doc/library/sys.rst @@ -907,6 +907,35 @@ always available. It is not guaranteed to exist in all implementations of Python. +.. function:: getobjects(limit[, type]) + + This function only exists if CPython was built using the + specialized configure option :option:`--with-trace-refs`. + It is intended only for debugging garbage-collection issues. + + Return a list of up to *limit* dynamically allocated Python objects. + If *type* is given, only objects of that exact type (not subtypes) + are included. + + Objects from the list are not safe to use. + Specifically, the result will include objects from all interpreters that + share their object allocator state (that is, ones created with + :c:member:`PyInterpreterConfig.use_main_obmalloc` set to 1 + or using :c:func:`Py_NewInterpreter`, and the + :ref:`main interpreter `). + Mixing objects from different interpreters may lead to crashes + or other unexpected behavior. + + .. impl-detail:: + + This function should be used for specialized purposes only. + It is not guaranteed to exist in all implementations of Python. + + .. versionchanged:: next + + The result may include objects from other interpreters. + + .. function:: getprofile() .. index:: diff --git a/Doc/using/configure.rst b/Doc/using/configure.rst index 51af4e3b7ad7a8..fed1d1e2c75940 100644 --- a/Doc/using/configure.rst +++ b/Doc/using/configure.rst @@ -459,7 +459,7 @@ Debug options Effects: * Define the ``Py_TRACE_REFS`` macro. - * Add :func:`!sys.getobjects` function. + * Add :func:`sys.getobjects` function. * Add :envvar:`PYTHONDUMPREFS` environment variable. This build is not ABI compatible with release build (default build) or debug diff --git a/Doc/whatsnew/3.12.rst b/Doc/whatsnew/3.12.rst index 5cfbe12ab1f666..ee347d2000d8dc 100644 --- a/Doc/whatsnew/3.12.rst +++ b/Doc/whatsnew/3.12.rst @@ -2301,3 +2301,14 @@ email check if the *strict* paramater is available. (Contributed by Thomas Dwyer and Victor Stinner for :gh:`102988` to improve the CVE-2023-27043 fix.) + + +Notable changes in 3.12.8 +========================= + +sys +--- + +* The previously undocumented special function :func:`sys.getobjects`, + which only exists in specialized builds of Python, may now return objects + from other interpreters than the one it's called in. diff --git a/Include/internal/pycore_object_state.h b/Include/internal/pycore_object_state.h index 65feb5af969f8b..6e07b1a01b0e34 100644 --- a/Include/internal/pycore_object_state.h +++ b/Include/internal/pycore_object_state.h @@ -24,7 +24,13 @@ struct _py_object_state { * together via the _ob_prev and _ob_next members of a PyObject, which * exist only in a Py_TRACE_REFS build. */ - PyObject refchain; + PyObject *refchain; + /* In most cases, refchain points to _refchain_obj. + * In sub-interpreters that share objmalloc state with the main interp, + * refchain points to the main interpreter's _refchain_obj, and their own + * _refchain_obj is unused. + */ + PyObject _refchain_obj; #endif int _not_used; }; diff --git a/Include/internal/pycore_runtime_init.h b/Include/internal/pycore_runtime_init.h index e5f9e17efff24b..ad90ea680a52c3 100644 --- a/Include/internal/pycore_runtime_init.h +++ b/Include/internal/pycore_runtime_init.h @@ -132,15 +132,8 @@ extern PyTypeObject _PyExc_MemoryError; .context_ver = 1, \ } -#ifdef Py_TRACE_REFS -# define _py_object_state_INIT(INTERP) \ - { \ - .refchain = {&INTERP.object_state.refchain, &INTERP.object_state.refchain}, \ - } -#else # define _py_object_state_INIT(INTERP) \ { 0 } -#endif // global objects diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2024-09-26-18-21-06.gh-issue-116510.FacUWO.rst b/Misc/NEWS.d/next/Core_and_Builtins/2024-09-26-18-21-06.gh-issue-116510.FacUWO.rst new file mode 100644 index 00000000000000..e3741321006548 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2024-09-26-18-21-06.gh-issue-116510.FacUWO.rst @@ -0,0 +1,5 @@ +Fix a crash caused by immortal interned strings being shared between +sub-interpreters that use basic single-phase init. In that case, the string +can be used by an interpreter that outlives the interpreter that created and +interned it. For interpreters that share obmalloc state, also share the +interned dict with the main interpreter. diff --git a/Objects/object.c b/Objects/object.c index 6b2e0aeaab9903..706719116da3e2 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -159,11 +159,27 @@ _PyDebug_PrintTotalRefs(void) { #ifdef Py_TRACE_REFS -#define REFCHAIN(interp) &interp->object_state.refchain +#define REFCHAIN(interp) interp->object_state.refchain + +static inline int +has_own_refchain(PyInterpreterState *interp) +{ + if (interp->feature_flags & Py_RTFLAGS_USE_MAIN_OBMALLOC) { + return (_Py_IsMainInterpreter(interp) + || _PyInterpreterState_Main() == NULL); + } + return 1; +} static inline void init_refchain(PyInterpreterState *interp) { + if (!has_own_refchain(interp)) { + // Legacy subinterpreters share a refchain with the main interpreter. + REFCHAIN(interp) = REFCHAIN(_PyInterpreterState_Main()); + return; + } + REFCHAIN(interp) = &interp->object_state._refchain_obj; PyObject *refchain = REFCHAIN(interp); refchain->_ob_prev = refchain; refchain->_ob_next = refchain; @@ -2010,9 +2026,7 @@ void _PyObject_InitState(PyInterpreterState *interp) { #ifdef Py_TRACE_REFS - if (!_Py_IsMainInterpreter(interp)) { - init_refchain(interp); - } + init_refchain(interp); #endif } diff --git a/Objects/unicodeobject.c b/Objects/unicodeobject.c index ba11351f003007..c885be5b6682aa 100644 --- a/Objects/unicodeobject.c +++ b/Objects/unicodeobject.c @@ -287,13 +287,37 @@ hashtable_unicode_compare(const void *key1, const void *key2) } } +/* Return true if this interpreter should share the main interpreter's + intern_dict. That's important for interpreters which load basic + single-phase init extension modules (m_size == -1). There could be interned + immortal strings that are shared between interpreters, due to the + PyDict_Update(mdict, m_copy) call in import_find_extension(). + + It's not safe to deallocate those strings until all interpreters that + potentially use them are freed. By storing them in the main interpreter, we + ensure they get freed after all other interpreters are freed. +*/ +static bool +has_shared_intern_dict(PyInterpreterState *interp) +{ + PyInterpreterState *main_interp = _PyInterpreterState_Main(); + return interp != main_interp && interp->feature_flags & Py_RTFLAGS_USE_MAIN_OBMALLOC; +} + static int init_interned_dict(PyInterpreterState *interp) { assert(get_interned_dict(interp) == NULL); - PyObject *interned = interned = PyDict_New(); - if (interned == NULL) { - return -1; + PyObject *interned; + if (has_shared_intern_dict(interp)) { + interned = get_interned_dict(_PyInterpreterState_Main()); + Py_INCREF(interned); + } + else { + interned = PyDict_New(); + if (interned == NULL) { + return -1; + } } _Py_INTERP_CACHED_OBJECT(interp, interned_strings) = interned; return 0; @@ -304,7 +328,10 @@ clear_interned_dict(PyInterpreterState *interp) { PyObject *interned = get_interned_dict(interp); if (interned != NULL) { - PyDict_Clear(interned); + if (!has_shared_intern_dict(interp)) { + // only clear if the dict belongs to this interpreter + PyDict_Clear(interned); + } Py_DECREF(interned); _Py_INTERP_CACHED_OBJECT(interp, interned_strings) = NULL; } @@ -15152,6 +15179,13 @@ _PyUnicode_ClearInterned(PyInterpreterState *interp) } assert(PyDict_CheckExact(interned)); + if (has_shared_intern_dict(interp)) { + // the dict doesn't belong to this interpreter, skip the debug + // checks on it and just clear the pointer to it + clear_interned_dict(interp); + return; + } + #ifdef INTERNED_STATS fprintf(stderr, "releasing %zd interned strings\n", PyDict_GET_SIZE(interned)); @@ -15670,8 +15704,10 @@ _PyUnicode_Fini(PyInterpreterState *interp) { struct _Py_unicode_state *state = &interp->unicode; - // _PyUnicode_ClearInterned() must be called before _PyUnicode_Fini() - assert(get_interned_dict(interp) == NULL); + if (!has_shared_intern_dict(interp)) { + // _PyUnicode_ClearInterned() must be called before _PyUnicode_Fini() + assert(get_interned_dict(interp) == NULL); + } _PyUnicode_FiniEncodings(&state->fs_codec); diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index 6d580c6d4892fd..e9c1a0d72d6d4d 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -650,6 +650,10 @@ pycore_create_interpreter(_PyRuntimeState *runtime, return status; } + // This could be done in init_interpreter() (in pystate.c) if it + // didn't depend on interp->feature_flags being set already. + _PyObject_InitState(interp); + PyThreadState *tstate = _PyThreadState_New(interp); if (tstate == NULL) { return _PyStatus_ERR("can't make first thread"); @@ -2103,6 +2107,10 @@ new_interpreter(PyThreadState **tstate_p, const PyInterpreterConfig *config) goto error; } + // This could be done in init_interpreter() (in pystate.c) if it + // didn't depend on interp->feature_flags being set already. + _PyObject_InitState(interp); + status = init_interp_create_gil(tstate, config->gil); if (_PyStatus_EXCEPTION(status)) { goto error; diff --git a/Python/pystate.c b/Python/pystate.c index f0e0d4117e4259..6aed9ac312a8a4 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -686,7 +686,9 @@ init_interpreter(PyInterpreterState *interp, _obmalloc_pools_INIT(interp->obmalloc.pools); memcpy(&interp->obmalloc.pools.used, temp, sizeof(temp)); } - _PyObject_InitState(interp); + + // We would call _PyObject_InitState() at this point + // if interp->feature_flags were alredy set. _PyEval_InitState(interp, pending_lock); _PyGC_InitState(&interp->gc); From 2dc476bcb9142cd25d7e1d52392b73a3dcdf1756 Mon Sep 17 00:00:00 2001 From: Thomas Wouters Date: Tue, 3 Dec 2024 19:40:02 +0100 Subject: [PATCH 269/269] Python 3.12.8 --- Doc/library/sys.rst | 2 +- Include/patchlevel.h | 4 +- Lib/pydoc_data/topics.py | 824 ++++++++---- Misc/NEWS.d/3.12.8.rst | 1101 +++++++++++++++++ ...4-10-16-09-37-51.gh-issue-89640.UDsW-j.rst | 2 - ...4-11-04-09-42-04.gh-issue-89640.QBv05o.rst | 1 - ...-07-04-13-23-27.gh-issue-113601.K3RLqp.rst | 2 - ...-12-30-00-21-45.gh-issue-113570._XQgsW.rst | 1 - ...-05-12-03-10-36.gh-issue-118950.5Wc4vp.rst | 1 - ...-09-22-21-01-56.gh-issue-109746.32MHt9.rst | 1 - ...-09-26-18-21-06.gh-issue-116510.FacUWO.rst | 5 - ...-10-03-14-39-41.gh-issue-123378.dCxANf.rst | 3 - ...-10-05-23-53-06.gh-issue-125008.ETANpd.rst | 2 - ...-10-29-15-17-31.gh-issue-126139.B4OQ8a.rst | 2 - ...-11-09-16-10-22.gh-issue-126066.9zs4m4.rst | 3 - ...-11-12-19-24-00.gh-issue-126341.5SdAe1.rst | 1 - ...-11-24-07-01-28.gh-issue-113841.WFg-Bu.rst | 2 - ...3-03-28-22-24-45.gh-issue-60712.So5uad.rst | 2 - ...-07-19-12-22-48.gh-issue-121277.wF_zKd.rst | 2 - ...-10-07-00-31-17.gh-issue-125018.yKnymn.rst | 4 - ...-10-10-23-46-54.gh-issue-125277.QAby09.rst | 2 - ...-10-04-15-34-34.gh-issue-122392.V8K3w2.rst | 2 - .../2018-12-04-07-36-27.bpo-14074.fMLKCu.rst | 2 - .../2021-12-19-10-47-24.bpo-46128.Qv3EK1.rst | 2 - ...2-10-15-10-18-20.gh-issue-71936.MzJjc_.rst | 1 - ...3-02-15-23-54-42.gh-issue-88110.KU6erv.rst | 2 - ...-10-26-16-36-22.gh-issue-101955.Ixu3IF.rst | 2 - ...-04-19-05-58-50.gh-issue-117766.J3xepp.rst | 1 - ...-06-02-11-48-19.gh-issue-119826.N1obGa.rst | 1 - ...4-06-06-04-06-05.gh-issue-70764.6511hw.rst | 1 - ...-07-23-02-24-50.gh-issue-120754.nHb5mG.rst | 1 - ...-08-28-19-27-35.gh-issue-123370.SPZ9Ux.rst | 1 - ...-09-13-18-24-27.gh-issue-124008.XaiPQx.rst | 2 - ...-09-16-12-31-48.gh-issue-123978.z3smEu.rst | 1 - ...-09-24-22-38-51.gh-issue-123884.iEPTK4.rst | 4 - ...-09-25-18-07-51.gh-issue-120378.NlBSz_.rst | 2 - ...-09-26-13-43-39.gh-issue-124594.peYhsP.rst | 1 - ...-09-28-02-03-04.gh-issue-124651.bLBGtH.rst | 1 - ...-10-01-13-46-58.gh-issue-124390.dK1Zcm.rst | 1 - ...-10-01-17-12-20.gh-issue-124858.Zy0tvT.rst | 1 - ...-10-02-15-05-45.gh-issue-124653.tqsTu9.rst | 2 - ...-10-02-21-11-18.gh-issue-124917.Lnwh5b.rst | 2 - ...4-10-03-20-45-57.gh-issue-53203.3Sk4Ia.rst | 5 - ...-10-04-08-46-00.gh-issue-124958.rea9-x.rst | 1 - ...-10-08-12-09-09.gh-issue-124969._VBQLq.rst | 4 - ...-10-08-21-17-16.gh-issue-125069.0RP0Mx.rst | 4 - ...-10-09-07-09-00.gh-issue-125118.J9rQ1S.rst | 1 - ...4-10-09-17-07-33.gh-issue-52551.PBakSY.rst | 8 - ...4-10-09-21-42-43.gh-issue-61011.pQXZb1.rst | 4 - ...-10-10-19-57-35.gh-issue-125254.RtZxXS.rst | 1 - ...4-10-13-20-21-35.gh-issue-53203.Rz1c8A.rst | 2 - ...-10-14-02-27-03.gh-issue-100141.NuAcwa.rst | 1 - ...-10-14-04-44-12.gh-issue-125422.MlVuC6.rst | 1 - ...-10-14-17-29-34.gh-issue-125451.fmP3T9.rst | 2 - ...-10-15-14-01-03.gh-issue-125519.TqGh6a.rst | 2 - ...-10-17-16-10-29.gh-issue-125259.oMew0c.rst | 1 - ...-10-18-08-58-10.gh-issue-125660.sDdDqO.rst | 1 - ...-10-18-09-51-29.gh-issue-125682.vsj4cU.rst | 2 - ...-10-22-13-28-00.gh-issue-125355.zssHm_.rst | 7 - ...-10-23-17-45-40.gh-issue-125884.41E_PD.rst | 1 - ...-10-24-10-49-47.gh-issue-124452.eqTRgx.rst | 4 - ...-10-24-14-08-10.gh-issue-125789.eaiAMw.rst | 1 - ...-10-25-10-53-56.gh-issue-125966.eOCYU_.rst | 2 - ...-10-25-11-13-24.gh-issue-125969.YvbrTr.rst | 2 - ...-10-26-12-50-48.gh-issue-125984.d4vp5_.rst | 3 - ...-10-28-22-35-22.gh-issue-126083.TuI--n.rst | 1 - ...-10-29-10-38-28.gh-issue-126080.qKRBuo.rst | 3 - ...-10-29-10-58-52.gh-issue-126106.rlF798.rst | 1 - ...-10-29-11-45-44.gh-issue-126105.cOL-R6.rst | 1 - ...-10-30-20-45-17.gh-issue-126205.CHEmtx.rst | 2 - ...-10-30-23-59-36.gh-issue-126212._9uYjT.rst | 3 - ...-10-31-14-06-28.gh-issue-126220.uJAJCU.rst | 2 - ...-11-01-14-31-41.gh-issue-126138.yTniOG.rst | 3 - ...-11-02-19-20-44.gh-issue-126303.yVvyWB.rst | 1 - ...-11-06-13-41-38.gh-issue-126489.toaf-0.rst | 3 - ...-11-06-18-30-50.gh-issue-126476.F1wh3c.rst | 2 - ...-11-06-23-40-28.gh-issue-125679.Qq9xF5.rst | 2 - ...-11-07-01-40-11.gh-issue-117378.o9O5uM.rst | 17 - ...-11-07-22-41-47.gh-issue-126505.iztYE1.rst | 4 - ...-11-08-11-06-14.gh-issue-126565.dFFO22.rst | 1 - ...-11-08-17-05-10.gh-issue-120423.7rdLVV.rst | 2 - ...-11-09-10-31-10.gh-issue-126595.A-7MyC.rst | 2 - ...-11-10-18-14-51.gh-issue-104745.zAa5Ke.rst | 3 - ...-11-11-13-24-22.gh-issue-126699.ONGbMd.rst | 1 - ...-11-12-13-14-47.gh-issue-126727.5Eqfqd.rst | 3 - ...-11-12-21-43-12.gh-issue-126766.oi2KJ7.rst | 2 - ...-11-13-20-03-18.gh-issue-126188.RJLKk-.rst | 1 - ...-11-13-22-25-57.gh-issue-126789.lKzlc7.rst | 4 - ...4-11-14-22-25-49.gh-issue-67877.G9hw0w.rst | 2 - ...4-11-15-01-50-36.gh-issue-85168.bP8VIN.rst | 4 - ...-11-20-08-54-11.gh-issue-126618.ef_53g.rst | 2 - ...-11-20-16-58-59.gh-issue-126997.0PI41Y.rst | 3 - ...-11-22-02-31-55.gh-issue-126766.jfkhBH.rst | 2 - ...-11-22-03-40-02.gh-issue-127078.gI_PaP.rst | 2 - ...-11-22-10-42-34.gh-issue-127035.UnbDlr.rst | 4 - ...-11-24-12-41-31.gh-issue-127217.UAXGFr.rst | 2 - ...-11-24-14-20-17.gh-issue-127182.WmfY2g.rst | 2 - ...-11-27-14-06-35.gh-issue-123967.wxUmnW.rst | 2 - ...-11-27-16-06-10.gh-issue-127303.asqkgh.rst | 1 - ...-11-13-11-09-12.gh-issue-126623.TO7NnR.rst | 1 - ...-09-30-22-52-44.gh-issue-124295.VZy5kx.rst | 1 - ...-10-07-14-13-38.gh-issue-125041.PKLWDf.rst | 3 - ...-11-17-16-56-48.gh-issue-126909.60VTxW.rst | 2 - ...-11-13-22-23-36.gh-issue-126807.vpaWuN.rst | 2 - ...-09-24-19-04-56.gh-issue-124448.srVT3d.rst | 1 - ...-10-15-21-28-43.gh-issue-125550.hmGWCP.rst | 2 - ...-10-29-19-48-03.gh-issue-125315.jdB9qN.rst | 2 - ...-11-16-22-08-41.gh-issue-126911.HchCZZ.rst | 1 - ...-09-24-10-48-46.gh-issue-124448.bFMrS6.rst | 1 - README.rst | 2 +- 110 files changed, 1654 insertions(+), 514 deletions(-) create mode 100644 Misc/NEWS.d/3.12.8.rst delete mode 100644 Misc/NEWS.d/next/Build/2024-10-16-09-37-51.gh-issue-89640.UDsW-j.rst delete mode 100644 Misc/NEWS.d/next/Build/2024-11-04-09-42-04.gh-issue-89640.QBv05o.rst delete mode 100644 Misc/NEWS.d/next/C API/2024-07-04-13-23-27.gh-issue-113601.K3RLqp.rst delete mode 100644 Misc/NEWS.d/next/Core and Builtins/2023-12-30-00-21-45.gh-issue-113570._XQgsW.rst delete mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-05-12-03-10-36.gh-issue-118950.5Wc4vp.rst delete mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2023-09-22-21-01-56.gh-issue-109746.32MHt9.rst delete mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2024-09-26-18-21-06.gh-issue-116510.FacUWO.rst delete mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2024-10-03-14-39-41.gh-issue-123378.dCxANf.rst delete mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2024-10-05-23-53-06.gh-issue-125008.ETANpd.rst delete mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2024-10-29-15-17-31.gh-issue-126139.B4OQ8a.rst delete mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2024-11-09-16-10-22.gh-issue-126066.9zs4m4.rst delete mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2024-11-12-19-24-00.gh-issue-126341.5SdAe1.rst delete mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2024-11-24-07-01-28.gh-issue-113841.WFg-Bu.rst delete mode 100644 Misc/NEWS.d/next/Documentation/2023-03-28-22-24-45.gh-issue-60712.So5uad.rst delete mode 100644 Misc/NEWS.d/next/Documentation/2024-07-19-12-22-48.gh-issue-121277.wF_zKd.rst delete mode 100644 Misc/NEWS.d/next/Documentation/2024-10-07-00-31-17.gh-issue-125018.yKnymn.rst delete mode 100644 Misc/NEWS.d/next/Documentation/2024-10-10-23-46-54.gh-issue-125277.QAby09.rst delete mode 100644 Misc/NEWS.d/next/IDLE/2024-10-04-15-34-34.gh-issue-122392.V8K3w2.rst delete mode 100644 Misc/NEWS.d/next/Library/2018-12-04-07-36-27.bpo-14074.fMLKCu.rst delete mode 100644 Misc/NEWS.d/next/Library/2021-12-19-10-47-24.bpo-46128.Qv3EK1.rst delete mode 100644 Misc/NEWS.d/next/Library/2022-10-15-10-18-20.gh-issue-71936.MzJjc_.rst delete mode 100644 Misc/NEWS.d/next/Library/2023-02-15-23-54-42.gh-issue-88110.KU6erv.rst delete mode 100644 Misc/NEWS.d/next/Library/2023-10-26-16-36-22.gh-issue-101955.Ixu3IF.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-04-19-05-58-50.gh-issue-117766.J3xepp.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-06-02-11-48-19.gh-issue-119826.N1obGa.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-06-06-04-06-05.gh-issue-70764.6511hw.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-07-23-02-24-50.gh-issue-120754.nHb5mG.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-08-28-19-27-35.gh-issue-123370.SPZ9Ux.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-09-13-18-24-27.gh-issue-124008.XaiPQx.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-09-16-12-31-48.gh-issue-123978.z3smEu.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-09-24-22-38-51.gh-issue-123884.iEPTK4.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-09-25-18-07-51.gh-issue-120378.NlBSz_.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-09-26-13-43-39.gh-issue-124594.peYhsP.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-09-28-02-03-04.gh-issue-124651.bLBGtH.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-10-01-13-46-58.gh-issue-124390.dK1Zcm.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-10-01-17-12-20.gh-issue-124858.Zy0tvT.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-10-02-15-05-45.gh-issue-124653.tqsTu9.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-10-02-21-11-18.gh-issue-124917.Lnwh5b.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-10-03-20-45-57.gh-issue-53203.3Sk4Ia.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-10-04-08-46-00.gh-issue-124958.rea9-x.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-10-08-12-09-09.gh-issue-124969._VBQLq.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-10-08-21-17-16.gh-issue-125069.0RP0Mx.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-10-09-07-09-00.gh-issue-125118.J9rQ1S.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-10-09-17-07-33.gh-issue-52551.PBakSY.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-10-09-21-42-43.gh-issue-61011.pQXZb1.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-10-10-19-57-35.gh-issue-125254.RtZxXS.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-10-13-20-21-35.gh-issue-53203.Rz1c8A.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-10-14-02-27-03.gh-issue-100141.NuAcwa.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-10-14-04-44-12.gh-issue-125422.MlVuC6.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-10-14-17-29-34.gh-issue-125451.fmP3T9.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-10-15-14-01-03.gh-issue-125519.TqGh6a.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-10-17-16-10-29.gh-issue-125259.oMew0c.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-10-18-08-58-10.gh-issue-125660.sDdDqO.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-10-18-09-51-29.gh-issue-125682.vsj4cU.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-10-22-13-28-00.gh-issue-125355.zssHm_.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-10-23-17-45-40.gh-issue-125884.41E_PD.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-10-24-10-49-47.gh-issue-124452.eqTRgx.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-10-24-14-08-10.gh-issue-125789.eaiAMw.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-10-25-10-53-56.gh-issue-125966.eOCYU_.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-10-25-11-13-24.gh-issue-125969.YvbrTr.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-10-26-12-50-48.gh-issue-125984.d4vp5_.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-10-28-22-35-22.gh-issue-126083.TuI--n.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-10-29-10-38-28.gh-issue-126080.qKRBuo.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-10-29-10-58-52.gh-issue-126106.rlF798.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-10-29-11-45-44.gh-issue-126105.cOL-R6.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-10-30-20-45-17.gh-issue-126205.CHEmtx.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-10-30-23-59-36.gh-issue-126212._9uYjT.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-10-31-14-06-28.gh-issue-126220.uJAJCU.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-11-01-14-31-41.gh-issue-126138.yTniOG.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-11-02-19-20-44.gh-issue-126303.yVvyWB.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-11-06-13-41-38.gh-issue-126489.toaf-0.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-11-06-18-30-50.gh-issue-126476.F1wh3c.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-11-06-23-40-28.gh-issue-125679.Qq9xF5.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-11-07-01-40-11.gh-issue-117378.o9O5uM.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-11-07-22-41-47.gh-issue-126505.iztYE1.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-11-08-11-06-14.gh-issue-126565.dFFO22.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-11-08-17-05-10.gh-issue-120423.7rdLVV.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-11-09-10-31-10.gh-issue-126595.A-7MyC.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-11-10-18-14-51.gh-issue-104745.zAa5Ke.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-11-11-13-24-22.gh-issue-126699.ONGbMd.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-11-12-13-14-47.gh-issue-126727.5Eqfqd.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-11-12-21-43-12.gh-issue-126766.oi2KJ7.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-11-13-20-03-18.gh-issue-126188.RJLKk-.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-11-13-22-25-57.gh-issue-126789.lKzlc7.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-11-14-22-25-49.gh-issue-67877.G9hw0w.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-11-15-01-50-36.gh-issue-85168.bP8VIN.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-11-20-08-54-11.gh-issue-126618.ef_53g.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-11-20-16-58-59.gh-issue-126997.0PI41Y.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-11-22-02-31-55.gh-issue-126766.jfkhBH.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-11-22-03-40-02.gh-issue-127078.gI_PaP.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-11-22-10-42-34.gh-issue-127035.UnbDlr.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-11-24-12-41-31.gh-issue-127217.UAXGFr.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-11-24-14-20-17.gh-issue-127182.WmfY2g.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-11-27-14-06-35.gh-issue-123967.wxUmnW.rst delete mode 100644 Misc/NEWS.d/next/Library/2024-11-27-16-06-10.gh-issue-127303.asqkgh.rst delete mode 100644 Misc/NEWS.d/next/Security/2024-11-13-11-09-12.gh-issue-126623.TO7NnR.rst delete mode 100644 Misc/NEWS.d/next/Tests/2024-09-30-22-52-44.gh-issue-124295.VZy5kx.rst delete mode 100644 Misc/NEWS.d/next/Tests/2024-10-07-14-13-38.gh-issue-125041.PKLWDf.rst delete mode 100644 Misc/NEWS.d/next/Tests/2024-11-17-16-56-48.gh-issue-126909.60VTxW.rst delete mode 100644 Misc/NEWS.d/next/Tools-Demos/2024-11-13-22-23-36.gh-issue-126807.vpaWuN.rst delete mode 100644 Misc/NEWS.d/next/Windows/2024-09-24-19-04-56.gh-issue-124448.srVT3d.rst delete mode 100644 Misc/NEWS.d/next/Windows/2024-10-15-21-28-43.gh-issue-125550.hmGWCP.rst delete mode 100644 Misc/NEWS.d/next/Windows/2024-10-29-19-48-03.gh-issue-125315.jdB9qN.rst delete mode 100644 Misc/NEWS.d/next/Windows/2024-11-16-22-08-41.gh-issue-126911.HchCZZ.rst delete mode 100644 Misc/NEWS.d/next/macOS/2024-09-24-10-48-46.gh-issue-124448.bFMrS6.rst diff --git a/Doc/library/sys.rst b/Doc/library/sys.rst index 221f9bd6942207..90c794e3f3c58d 100644 --- a/Doc/library/sys.rst +++ b/Doc/library/sys.rst @@ -931,7 +931,7 @@ always available. This function should be used for specialized purposes only. It is not guaranteed to exist in all implementations of Python. - .. versionchanged:: next + .. versionchanged:: 3.12.8 The result may include objects from other interpreters. diff --git a/Include/patchlevel.h b/Include/patchlevel.h index a1bbc45127b601..6c597837da093d 100644 --- a/Include/patchlevel.h +++ b/Include/patchlevel.h @@ -18,12 +18,12 @@ /*--start constants--*/ #define PY_MAJOR_VERSION 3 #define PY_MINOR_VERSION 12 -#define PY_MICRO_VERSION 7 +#define PY_MICRO_VERSION 8 #define PY_RELEASE_LEVEL PY_RELEASE_LEVEL_FINAL #define PY_RELEASE_SERIAL 0 /* Version as a string */ -#define PY_VERSION "3.12.7+" +#define PY_VERSION "3.12.8" /*--end constants--*/ /* Version as a single 4-byte hex number, e.g. 0x010502B2 == 1.5.2b2. diff --git a/Lib/pydoc_data/topics.py b/Lib/pydoc_data/topics.py index b5464cb4d04b9b..12523999ca886c 100644 --- a/Lib/pydoc_data/topics.py +++ b/Lib/pydoc_data/topics.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Autogenerated by Sphinx on Tue Oct 1 04:02:04 2024 +# Autogenerated by Sphinx on Tue Dec 3 19:41:14 2024 # as part of the release process. topics = {'assert': 'The "assert" statement\n' '**********************\n' @@ -29,13 +29,12 @@ '(command\n' 'line option "-O"). The current code generator emits no code for ' 'an\n' - 'assert statement when optimization is requested at compile time. ' - 'Note\n' - 'that it is unnecessary to include the source code for the ' - 'expression\n' - 'that failed in the error message; it will be displayed as part of ' - 'the\n' - 'stack trace.\n' + '"assert" statement when optimization is requested at compile ' + 'time.\n' + 'Note that it is unnecessary to include the source code for the\n' + 'expression that failed in the error message; it will be displayed ' + 'as\n' + 'part of the stack trace.\n' '\n' 'Assignments to "__debug__" are illegal. The value for the ' 'built-in\n' @@ -673,7 +672,8 @@ 'should either\n' ' return the (computed) attribute value or raise an ' '"AttributeError"\n' - ' exception.\n' + ' exception. The "object" class itself does not provide ' + 'this method.\n' '\n' ' Note that if the attribute is found through the ' 'normal mechanism,\n' @@ -856,7 +856,9 @@ 'parents). In the\n' 'examples below, “the attribute” refers to the attribute ' 'whose name is\n' - 'the key of the property in the owner class’ "__dict__".\n' + 'the key of the property in the owner class’ "__dict__". ' + 'The "object"\n' + 'class itself does not implement any of these protocols.\n' '\n' 'object.__get__(self, instance, owner=None)\n' '\n' @@ -1529,7 +1531,9 @@ ' Called when the instance is “called” as a function; if ' 'this method\n' ' is defined, "x(arg1, arg2, ...)" roughly translates to\n' - ' "type(x).__call__(x, arg1, ...)".\n', + ' "type(x).__call__(x, arg1, ...)". The "object" class ' + 'itself does\n' + ' not provide this method.\n', 'calls': 'Calls\n' '*****\n' '\n' @@ -1714,6 +1718,9 @@ ' Function definitions. When the code block executes a "return"\n' ' statement, this specifies the return value of the function ' 'call.\n' + ' If execution reaches the end of the code block without executing ' + 'a\n' + ' "return" statement, the return value is "None".\n' '\n' 'a built-in function or method:\n' ' The result is up to the interpreter; see Built-in Functions for ' @@ -2762,18 +2769,15 @@ ' enter = type(manager).__enter__\n' ' exit = type(manager).__exit__\n' ' value = enter(manager)\n' - ' hit_except = False\n' '\n' ' try:\n' ' TARGET = value\n' ' SUITE\n' ' except:\n' - ' hit_except = True\n' ' if not exit(manager, *sys.exc_info()):\n' ' raise\n' - ' finally:\n' - ' if not hit_except:\n' - ' exit(manager, None, None, None)\n' + ' else:\n' + ' exit(manager, None, None, None)\n' '\n' 'With more than one item, the context managers are processed as ' 'if\n' @@ -4389,6 +4393,9 @@ '\n' 'For more information on context managers, see Context ' 'Manager Types.\n' + 'The "object" class itself does not provide the context ' + 'manager\n' + 'methods.\n' '\n' 'object.__enter__(self)\n' '\n' @@ -4658,17 +4665,20 @@ '\n' ' This is typically used for debugging, so it is important ' 'that the\n' - ' representation is information-rich and unambiguous.\n' + ' representation is information-rich and unambiguous. A ' + 'default\n' + ' implementation is provided by the "object" class ' + 'itself.\n' '\n' 'object.__str__(self)\n' '\n' - ' Called by "str(object)" and the built-in functions ' - '"format()" and\n' - ' "print()" to compute the “informal” or nicely printable ' - 'string\n' - ' representation of an object. The return value must be a ' - 'string\n' - ' object.\n' + ' Called by "str(object)", the default "__format__()" ' + 'implementation,\n' + ' and the built-in function "print()", to compute the ' + '“informal” or\n' + ' nicely printable string representation of an object. ' + 'The return\n' + ' value must be a str object.\n' '\n' ' This method differs from "object.__repr__()" in that ' 'there is no\n' @@ -4684,7 +4694,9 @@ '\n' ' Called by bytes to compute a byte-string representation ' 'of an\n' - ' object. This should return a "bytes" object.\n' + ' object. This should return a "bytes" object. The ' + '"object" class\n' + ' itself does not provide this method.\n' '\n' 'object.__format__(self, format_spec)\n' '\n' @@ -4712,6 +4724,11 @@ '\n' ' The return value must be a string object.\n' '\n' + ' The default implementation by the "object" class should ' + 'be given an\n' + ' empty *format_spec* string. It delegates to ' + '"__str__()".\n' + '\n' ' Changed in version 3.4: The __format__ method of ' '"object" itself\n' ' raises a "TypeError" if passed any non-empty string.\n' @@ -4769,6 +4786,16 @@ ' ordering operations from a single root operation, see\n' ' "functools.total_ordering()".\n' '\n' + ' By default, the "object" class provides implementations ' + 'consistent\n' + ' with Value comparisons: equality compares according to ' + 'object\n' + ' identity, and order comparisons raise "TypeError". Each ' + 'default\n' + ' method may generate these results directly, but may also ' + 'return\n' + ' "NotImplemented".\n' + '\n' ' See the paragraph on "__hash__()" for some important ' 'notes on\n' ' creating *hashable* objects which support custom ' @@ -4855,12 +4882,13 @@ '\n' ' User-defined classes have "__eq__()" and "__hash__()" ' 'methods by\n' - ' default; with them, all objects compare unequal (except ' - 'with\n' - ' themselves) and "x.__hash__()" returns an appropriate ' - 'value such\n' - ' that "x == y" implies both that "x is y" and "hash(x) == ' - 'hash(y)".\n' + ' default (inherited from the "object" class); with them, ' + 'all objects\n' + ' compare unequal (except with themselves) and ' + '"x.__hash__()" returns\n' + ' an appropriate value such that "x == y" implies both ' + 'that "x is y"\n' + ' and "hash(x) == hash(y)".\n' '\n' ' A class that overrides "__eq__()" and does not define ' '"__hash__()"\n' @@ -4931,9 +4959,9 @@ 'the object is\n' ' considered true if its result is nonzero. If a class ' 'defines\n' - ' neither "__len__()" nor "__bool__()", all its instances ' - 'are\n' - ' considered true.\n', + ' neither "__len__()" nor "__bool__()" (which is true of ' + 'the "object"\n' + ' class itself), all its instances are considered true.\n', 'debugger': '"pdb" — The Python Debugger\n' '***************************\n' '\n' @@ -6802,10 +6830,12 @@ 'printing fields |\n' '| | in the form ‘+000000120’. This alignment ' 'option is only |\n' - '| | valid for numeric types. It becomes the ' - 'default for |\n' - '| | numbers when ‘0’ immediately precedes the ' - 'field width. |\n' + '| | valid for numeric types, excluding "complex". ' + 'It becomes |\n' + '| | the default for numbers when ‘0’ immediately ' + 'precedes the |\n' + '| | field ' + 'width. |\n' '+-----------+------------------------------------------------------------+\n' '| "\'^\'" | Forces the field to be centered within the ' 'available |\n' @@ -6912,9 +6942,9 @@ 'field by a\n' 'zero ("\'0\'") character enables sign-aware zero-padding ' 'for numeric\n' - 'types. This is equivalent to a *fill* character of "\'0\'" ' - 'with an\n' - '*alignment* type of "\'=\'".\n' + 'types, excluding "complex". This is equivalent to a *fill* ' + 'character\n' + 'of "\'0\'" with an *alignment* type of "\'=\'".\n' '\n' 'Changed in version 3.10: Preceding the *width* field by ' '"\'0\'" no\n' @@ -7045,12 +7075,10 @@ 'of "6" digits |\n' ' | | after the decimal point for "float", and ' 'shows all |\n' - ' | | coefficient digits for "Decimal". If no ' - 'digits follow the |\n' - ' | | decimal point, the decimal point is also ' - 'removed unless |\n' - ' | | the "#" option is ' - 'used. |\n' + ' | | coefficient digits for "Decimal". If ' + '"p=0", the decimal |\n' + ' | | point is omitted unless the "#" option is ' + 'used. |\n' ' ' '+-----------+------------------------------------------------------------+\n' ' | "\'E\'" | Scientific notation. Same as "\'e\'" ' @@ -7069,12 +7097,10 @@ 'decimal point for |\n' ' | | "float", and uses a precision large enough ' 'to show all |\n' - ' | | coefficient digits for "Decimal". If no ' - 'digits follow the |\n' - ' | | decimal point, the decimal point is also ' - 'removed unless |\n' - ' | | the "#" option is ' - 'used. |\n' + ' | | coefficient digits for "Decimal". If ' + '"p=0", the decimal |\n' + ' | | point is omitted unless the "#" option is ' + 'used. |\n' ' ' '+-----------+------------------------------------------------------------+\n' ' | "\'F\'" | Fixed-point notation. Same as "\'f\'", ' @@ -7184,6 +7210,32 @@ ' ' '+-----------+------------------------------------------------------------+\n' '\n' + 'The result should be correctly rounded to a given precision ' + '"p" of\n' + 'digits after the decimal point. The rounding mode for ' + '"float" matches\n' + 'that of the "round()" builtin. For "Decimal", the rounding ' + 'mode of\n' + 'the current context will be used.\n' + '\n' + 'The available presentation types for "complex" are the same ' + 'as those\n' + 'for "float" ("\'%\'" is not allowed). Both the real and ' + 'imaginary\n' + 'components of a complex number are formatted as ' + 'floating-point\n' + 'numbers, according to the specified presentation type. ' + 'They are\n' + 'separated by the mandatory sign of the imaginary part, the ' + 'latter\n' + 'being terminated by a "j" suffix. If the presentation type ' + 'is\n' + 'missing, the result will match the output of "str()" ' + '(complex numbers\n' + 'with a non-zero real part are also surrounded by ' + 'parentheses),\n' + 'possibly altered by other format modifiers.\n' + '\n' '\n' 'Format examples\n' '===============\n' @@ -7577,33 +7629,17 @@ '\n' ' global_stmt ::= "global" identifier ("," identifier)*\n' '\n' - 'The "global" statement is a declaration which holds for the ' - 'entire\n' - 'current code block. It means that the listed identifiers are to ' - 'be\n' - 'interpreted as globals. It would be impossible to assign to a ' - 'global\n' - 'variable without "global", although free variables may refer to\n' - 'globals without being declared global.\n' - '\n' - 'Names listed in a "global" statement must not be used in the same ' - 'code\n' - 'block textually preceding that "global" statement.\n' - '\n' - 'Names listed in a "global" statement must not be defined as ' - 'formal\n' - 'parameters, or as targets in "with" statements or "except" ' - 'clauses, or\n' - 'in a "for" target list, "class" definition, function definition,\n' - '"import" statement, or variable annotation.\n' + 'The "global" statement causes the listed identifiers to be ' + 'interpreted\n' + 'as globals. It would be impossible to assign to a global variable\n' + 'without "global", although free variables may refer to globals ' + 'without\n' + 'being declared global.\n' '\n' - '**CPython implementation detail:** The current implementation does ' - 'not\n' - 'enforce some of these restrictions, but programs should not abuse ' - 'this\n' - 'freedom, as future implementations may enforce them or silently ' - 'change\n' - 'the meaning of the program.\n' + 'The "global" statement applies to the entire scope of a function ' + 'or\n' + 'class body. A "SyntaxError" is raised if a variable is used or\n' + 'assigned to prior to its global declaration in the scope.\n' '\n' '**Programmer’s note:** "global" is a directive to the parser. It\n' 'applies only to code parsed at the same time as the "global"\n' @@ -7690,19 +7726,16 @@ '\n' 'Within the ASCII range (U+0001..U+007F), the valid characters ' 'for\n' - 'identifiers are the same as in Python 2.x: the uppercase and ' - 'lowercase\n' - 'letters "A" through "Z", the underscore "_" and, except for ' - 'the first\n' - 'character, the digits "0" through "9".\n' - '\n' - 'Python 3.0 introduces additional characters from outside the ' - 'ASCII\n' - 'range (see **PEP 3131**). For these characters, the ' - 'classification\n' - 'uses the version of the Unicode Character Database as ' - 'included in the\n' - '"unicodedata" module.\n' + 'identifiers include the uppercase and lowercase letters "A" ' + 'through\n' + '"Z", the underscore "_" and, except for the first character, ' + 'the\n' + 'digits "0" through "9". Python 3.0 introduced additional ' + 'characters\n' + 'from outside the ASCII range (see **PEP 3131**). For these\n' + 'characters, the classification uses the version of the ' + 'Unicode\n' + 'Character Database as included in the "unicodedata" module.\n' '\n' 'Identifiers are unlimited in length. Case is significant.\n' '\n' @@ -8666,8 +8699,8 @@ 'scope,\n' 'or if there is no nonlocal scope, a "SyntaxError" is raised.\n' '\n' - 'The nonlocal statement applies to the entire scope of a function ' - 'or\n' + 'The "nonlocal" statement applies to the entire scope of a ' + 'function or\n' 'class body. A "SyntaxError" is raised if a variable is used or\n' 'assigned to prior to its nonlocal declaration in the scope.\n' '\n' @@ -9425,56 +9458,58 @@ '\n' 'The following methods can be defined to implement ' 'container objects.\n' - 'Containers usually are *sequences* (such as "lists" or ' - '"tuples") or\n' - '*mappings* (like "dictionaries"), but can represent other ' - 'containers\n' - 'as well. The first set of methods is used either to ' - 'emulate a\n' - 'sequence or to emulate a mapping; the difference is that ' - 'for a\n' - 'sequence, the allowable keys should be the integers *k* ' - 'for which "0\n' - '<= k < N" where *N* is the length of the sequence, or ' - '"slice" objects,\n' - 'which define a range of items. It is also recommended ' - 'that mappings\n' - 'provide the methods "keys()", "values()", "items()", ' - '"get()",\n' - '"clear()", "setdefault()", "pop()", "popitem()", "copy()", ' + 'None of them are provided by the "object" class itself. ' + 'Containers\n' + 'usually are *sequences* (such as "lists" or "tuples") or ' + '*mappings*\n' + '(like *dictionaries*), but can represent other containers ' + 'as well.\n' + 'The first set of methods is used either to emulate a ' + 'sequence or to\n' + 'emulate a mapping; the difference is that for a sequence, ' + 'the\n' + 'allowable keys should be the integers *k* for which "0 <= ' + 'k < N" where\n' + '*N* is the length of the sequence, or "slice" objects, ' + 'which define a\n' + 'range of items. It is also recommended that mappings ' + 'provide the\n' + 'methods "keys()", "values()", "items()", "get()", ' + '"clear()",\n' + '"setdefault()", "pop()", "popitem()", "copy()", and ' + '"update()"\n' + 'behaving similar to those for Python’s standard ' + '"dictionary" objects.\n' + 'The "collections.abc" module provides a "MutableMapping" ' + '*abstract\n' + 'base class* to help create those methods from a base set ' + 'of\n' + '"__getitem__()", "__setitem__()", "__delitem__()", and ' + '"keys()".\n' + 'Mutable sequences should provide methods "append()", ' + '"count()",\n' + '"index()", "extend()", "insert()", "pop()", "remove()", ' + '"reverse()"\n' + 'and "sort()", like Python standard "list" objects. ' + 'Finally, sequence\n' + 'types should implement addition (meaning concatenation) ' 'and\n' - '"update()" behaving similar to those for Python’s ' - 'standard\n' - '"dictionary" objects. The "collections.abc" module ' - 'provides a\n' - '"MutableMapping" *abstract base class* to help create ' - 'those methods\n' - 'from a base set of "__getitem__()", "__setitem__()", ' - '"__delitem__()",\n' - 'and "keys()". Mutable sequences should provide methods ' - '"append()",\n' - '"count()", "index()", "extend()", "insert()", "pop()", ' - '"remove()",\n' - '"reverse()" and "sort()", like Python standard "list" ' - 'objects.\n' - 'Finally, sequence types should implement addition ' - '(meaning\n' - 'concatenation) and multiplication (meaning repetition) by ' - 'defining the\n' - 'methods "__add__()", "__radd__()", "__iadd__()", ' - '"__mul__()",\n' - '"__rmul__()" and "__imul__()" described below; they should ' - 'not define\n' - 'other numerical operators. It is recommended that both ' - 'mappings and\n' - 'sequences implement the "__contains__()" method to allow ' - 'efficient use\n' - 'of the "in" operator; for mappings, "in" should search the ' - 'mapping’s\n' - 'keys; for sequences, it should search through the values. ' - 'It is\n' - 'further recommended that both mappings and sequences ' - 'implement the\n' + 'multiplication (meaning repetition) by defining the ' + 'methods\n' + '"__add__()", "__radd__()", "__iadd__()", "__mul__()", ' + '"__rmul__()" and\n' + '"__imul__()" described below; they should not define other ' + 'numerical\n' + 'operators. It is recommended that both mappings and ' + 'sequences\n' + 'implement the "__contains__()" method to allow efficient ' + 'use of the\n' + '"in" operator; for mappings, "in" should search the ' + 'mapping’s keys;\n' + 'for sequences, it should search through the values. It is ' + 'further\n' + 'recommended that both mappings and sequences implement ' + 'the\n' '"__iter__()" method to allow efficient iteration through ' 'the\n' 'container; for mappings, "__iter__()" should iterate ' @@ -10014,17 +10049,19 @@ '\n' ' This is typically used for debugging, so it is important ' 'that the\n' - ' representation is information-rich and unambiguous.\n' + ' representation is information-rich and unambiguous. A ' + 'default\n' + ' implementation is provided by the "object" class itself.\n' '\n' 'object.__str__(self)\n' '\n' - ' Called by "str(object)" and the built-in functions ' - '"format()" and\n' - ' "print()" to compute the “informal” or nicely printable ' - 'string\n' - ' representation of an object. The return value must be a ' - 'string\n' - ' object.\n' + ' Called by "str(object)", the default "__format__()" ' + 'implementation,\n' + ' and the built-in function "print()", to compute the ' + '“informal” or\n' + ' nicely printable string representation of an object. The ' + 'return\n' + ' value must be a str object.\n' '\n' ' This method differs from "object.__repr__()" in that ' 'there is no\n' @@ -10040,7 +10077,9 @@ '\n' ' Called by bytes to compute a byte-string representation ' 'of an\n' - ' object. This should return a "bytes" object.\n' + ' object. This should return a "bytes" object. The "object" ' + 'class\n' + ' itself does not provide this method.\n' '\n' 'object.__format__(self, format_spec)\n' '\n' @@ -10068,6 +10107,10 @@ '\n' ' The return value must be a string object.\n' '\n' + ' The default implementation by the "object" class should ' + 'be given an\n' + ' empty *format_spec* string. It delegates to "__str__()".\n' + '\n' ' Changed in version 3.4: The __format__ method of "object" ' 'itself\n' ' raises a "TypeError" if passed any non-empty string.\n' @@ -10125,6 +10168,16 @@ ' ordering operations from a single root operation, see\n' ' "functools.total_ordering()".\n' '\n' + ' By default, the "object" class provides implementations ' + 'consistent\n' + ' with Value comparisons: equality compares according to ' + 'object\n' + ' identity, and order comparisons raise "TypeError". Each ' + 'default\n' + ' method may generate these results directly, but may also ' + 'return\n' + ' "NotImplemented".\n' + '\n' ' See the paragraph on "__hash__()" for some important ' 'notes on\n' ' creating *hashable* objects which support custom ' @@ -10210,12 +10263,13 @@ '\n' ' User-defined classes have "__eq__()" and "__hash__()" ' 'methods by\n' - ' default; with them, all objects compare unequal (except ' - 'with\n' - ' themselves) and "x.__hash__()" returns an appropriate ' - 'value such\n' - ' that "x == y" implies both that "x is y" and "hash(x) == ' - 'hash(y)".\n' + ' default (inherited from the "object" class); with them, ' + 'all objects\n' + ' compare unequal (except with themselves) and ' + '"x.__hash__()" returns\n' + ' an appropriate value such that "x == y" implies both that ' + '"x is y"\n' + ' and "hash(x) == hash(y)".\n' '\n' ' A class that overrides "__eq__()" and does not define ' '"__hash__()"\n' @@ -10284,9 +10338,9 @@ 'object is\n' ' considered true if its result is nonzero. If a class ' 'defines\n' - ' neither "__len__()" nor "__bool__()", all its instances ' - 'are\n' - ' considered true.\n' + ' neither "__len__()" nor "__bool__()" (which is true of ' + 'the "object"\n' + ' class itself), all its instances are considered true.\n' '\n' '\n' 'Customizing attribute access\n' @@ -10310,7 +10364,8 @@ 'either\n' ' return the (computed) attribute value or raise an ' '"AttributeError"\n' - ' exception.\n' + ' exception. The "object" class itself does not provide ' + 'this method.\n' '\n' ' Note that if the attribute is found through the normal ' 'mechanism,\n' @@ -10490,7 +10545,9 @@ 'parents). In the\n' 'examples below, “the attribute” refers to the attribute ' 'whose name is\n' - 'the key of the property in the owner class’ "__dict__".\n' + 'the key of the property in the owner class’ "__dict__". The ' + '"object"\n' + 'class itself does not implement any of these protocols.\n' '\n' 'object.__get__(self, instance, owner=None)\n' '\n' @@ -11373,7 +11430,9 @@ ' Called when the instance is “called” as a function; if ' 'this method\n' ' is defined, "x(arg1, arg2, ...)" roughly translates to\n' - ' "type(x).__call__(x, arg1, ...)".\n' + ' "type(x).__call__(x, arg1, ...)". The "object" class ' + 'itself does\n' + ' not provide this method.\n' '\n' '\n' 'Emulating container types\n' @@ -11381,54 +11440,54 @@ '\n' 'The following methods can be defined to implement container ' 'objects.\n' - 'Containers usually are *sequences* (such as "lists" or ' - '"tuples") or\n' - '*mappings* (like "dictionaries"), but can represent other ' - 'containers\n' - 'as well. The first set of methods is used either to emulate ' - 'a\n' - 'sequence or to emulate a mapping; the difference is that for ' - 'a\n' - 'sequence, the allowable keys should be the integers *k* for ' - 'which "0\n' - '<= k < N" where *N* is the length of the sequence, or ' - '"slice" objects,\n' - 'which define a range of items. It is also recommended that ' - 'mappings\n' - 'provide the methods "keys()", "values()", "items()", ' - '"get()",\n' - '"clear()", "setdefault()", "pop()", "popitem()", "copy()", ' - 'and\n' - '"update()" behaving similar to those for Python’s standard\n' - '"dictionary" objects. The "collections.abc" module provides ' - 'a\n' - '"MutableMapping" *abstract base class* to help create those ' - 'methods\n' - 'from a base set of "__getitem__()", "__setitem__()", ' - '"__delitem__()",\n' - 'and "keys()". Mutable sequences should provide methods ' - '"append()",\n' - '"count()", "index()", "extend()", "insert()", "pop()", ' - '"remove()",\n' - '"reverse()" and "sort()", like Python standard "list" ' + 'None of them are provided by the "object" class itself. ' + 'Containers\n' + 'usually are *sequences* (such as "lists" or "tuples") or ' + '*mappings*\n' + '(like *dictionaries*), but can represent other containers as ' + 'well.\n' + 'The first set of methods is used either to emulate a ' + 'sequence or to\n' + 'emulate a mapping; the difference is that for a sequence, ' + 'the\n' + 'allowable keys should be the integers *k* for which "0 <= k ' + '< N" where\n' + '*N* is the length of the sequence, or "slice" objects, which ' + 'define a\n' + 'range of items. It is also recommended that mappings ' + 'provide the\n' + 'methods "keys()", "values()", "items()", "get()", ' + '"clear()",\n' + '"setdefault()", "pop()", "popitem()", "copy()", and ' + '"update()"\n' + 'behaving similar to those for Python’s standard "dictionary" ' 'objects.\n' - 'Finally, sequence types should implement addition (meaning\n' - 'concatenation) and multiplication (meaning repetition) by ' - 'defining the\n' - 'methods "__add__()", "__radd__()", "__iadd__()", ' - '"__mul__()",\n' - '"__rmul__()" and "__imul__()" described below; they should ' - 'not define\n' - 'other numerical operators. It is recommended that both ' - 'mappings and\n' - 'sequences implement the "__contains__()" method to allow ' - 'efficient use\n' - 'of the "in" operator; for mappings, "in" should search the ' - 'mapping’s\n' - 'keys; for sequences, it should search through the values. ' - 'It is\n' - 'further recommended that both mappings and sequences ' - 'implement the\n' + 'The "collections.abc" module provides a "MutableMapping" ' + '*abstract\n' + 'base class* to help create those methods from a base set of\n' + '"__getitem__()", "__setitem__()", "__delitem__()", and ' + '"keys()".\n' + 'Mutable sequences should provide methods "append()", ' + '"count()",\n' + '"index()", "extend()", "insert()", "pop()", "remove()", ' + '"reverse()"\n' + 'and "sort()", like Python standard "list" objects. Finally, ' + 'sequence\n' + 'types should implement addition (meaning concatenation) and\n' + 'multiplication (meaning repetition) by defining the methods\n' + '"__add__()", "__radd__()", "__iadd__()", "__mul__()", ' + '"__rmul__()" and\n' + '"__imul__()" described below; they should not define other ' + 'numerical\n' + 'operators. It is recommended that both mappings and ' + 'sequences\n' + 'implement the "__contains__()" method to allow efficient use ' + 'of the\n' + '"in" operator; for mappings, "in" should search the ' + 'mapping’s keys;\n' + 'for sequences, it should search through the values. It is ' + 'further\n' + 'recommended that both mappings and sequences implement the\n' '"__iter__()" method to allow efficient iteration through ' 'the\n' 'container; for mappings, "__iter__()" should iterate through ' @@ -11844,6 +11903,9 @@ '\n' 'For more information on context managers, see Context ' 'Manager Types.\n' + 'The "object" class itself does not provide the context ' + 'manager\n' + 'methods.\n' '\n' 'object.__enter__(self)\n' '\n' @@ -14348,43 +14410,254 @@ 'e.g.,\n' '"m.x = 1" is equivalent to "m.__dict__["x"] = 1".\n' '\n' - 'Predefined (writable) attributes:\n' '\n' - ' "__name__"\n' - ' The module’s name.\n' + 'Import-related attributes on module objects\n' + '-------------------------------------------\n' + '\n' + 'Module objects have the following attributes that relate to the ' + 'import\n' + 'system. When a module is created using the machinery associated ' + 'with\n' + 'the import system, these attributes are filled in based on the\n' + 'module’s *spec*, before the *loader* executes and loads the ' + 'module.\n' + '\n' + 'To create a module dynamically rather than using the import ' + 'system,\n' + 'it’s recommended to use "importlib.util.module_from_spec()", which\n' + 'will set the various import-controlled attributes to appropriate\n' + 'values. It’s also possible to use the "types.ModuleType" ' + 'constructor\n' + 'to create modules directly, but this technique is more error-prone, ' + 'as\n' + 'most attributes must be manually set on the module object after it ' + 'has\n' + 'been created when using this approach.\n' + '\n' + 'Caution:\n' + '\n' + ' With the exception of "__name__", it is **strongly** recommended\n' + ' that you rely on "__spec__" and its attributes instead of any of ' + 'the\n' + ' other individual attributes listed in this subsection. Note that\n' + ' updating an attribute on "__spec__" will not update the\n' + ' corresponding attribute on the module itself:\n' + '\n' + ' >>> import typing\n' + ' >>> typing.__name__, typing.__spec__.name\n' + " ('typing', 'typing')\n" + " >>> typing.__spec__.name = 'spelling'\n" + ' >>> typing.__name__, typing.__spec__.name\n' + " ('typing', 'spelling')\n" + " >>> typing.__name__ = 'keyboard_smashing'\n" + ' >>> typing.__name__, typing.__spec__.name\n' + " ('keyboard_smashing', 'spelling')\n" + '\n' + 'module.__name__\n' + '\n' + ' The name used to uniquely identify the module in the import ' + 'system.\n' + ' For a directly executed module, this will be set to ' + '""__main__"".\n' + '\n' + ' This attribute must be set to the fully qualified name of the\n' + ' module. It is expected to match the value of\n' + ' "module.__spec__.name".\n' + '\n' + 'module.__spec__\n' + '\n' + ' A record of the module’s import-system-related state.\n' + '\n' + ' Set to the "module spec" that was used when importing the ' + 'module.\n' + ' See Module specs for more details.\n' + '\n' + ' Added in version 3.4.\n' + '\n' + 'module.__package__\n' + '\n' + ' The *package* a module belongs to.\n' + '\n' + ' If the module is top-level (that is, not a part of any specific\n' + ' package) then the attribute should be set to "\'\'" (the empty\n' + ' string). Otherwise, it should be set to the name of the ' + 'module’s\n' + ' package (which can be equal to "module.__name__" if the module\n' + ' itself is a package). See **PEP 366** for further details.\n' + '\n' + ' This attribute is used instead of "__name__" to calculate ' + 'explicit\n' + ' relative imports for main modules. It defaults to "None" for\n' + ' modules created dynamically using the "types.ModuleType"\n' + ' constructor; use "importlib.util.module_from_spec()" instead to\n' + ' ensure the attribute is set to a "str".\n' + '\n' + ' It is **strongly** recommended that you use\n' + ' "module.__spec__.parent" instead of "module.__package__".\n' + ' "__package__" is now only used as a fallback if ' + '"__spec__.parent"\n' + ' is not set, and this fallback path is deprecated.\n' + '\n' + ' Changed in version 3.4: This attribute now defaults to "None" ' + 'for\n' + ' modules created dynamically using the "types.ModuleType"\n' + ' constructor. Previously the attribute was optional.\n' + '\n' + ' Changed in version 3.6: The value of "__package__" is expected ' + 'to\n' + ' be the same as "__spec__.parent". "__package__" is now only used ' + 'as\n' + ' a fallback during import resolution if "__spec__.parent" is not\n' + ' defined.\n' + '\n' + ' Changed in version 3.10: "ImportWarning" is raised if an import\n' + ' resolution falls back to "__package__" instead of\n' + ' "__spec__.parent".\n' + '\n' + ' Changed in version 3.12: Raise "DeprecationWarning" instead of\n' + ' "ImportWarning" when falling back to "__package__" during ' + 'import\n' + ' resolution.\n' + '\n' + 'module.__loader__\n' '\n' - ' "__doc__"\n' - ' The module’s documentation string, or "None" if unavailable.\n' + ' The *loader* object that the import machinery used to load the\n' + ' module.\n' + '\n' + ' This attribute is mostly useful for introspection, but can be ' + 'used\n' + ' for additional loader-specific functionality, for example ' + 'getting\n' + ' data associated with a loader.\n' + '\n' + ' "__loader__" defaults to "None" for modules created dynamically\n' + ' using the "types.ModuleType" constructor; use\n' + ' "importlib.util.module_from_spec()" instead to ensure the ' + 'attribute\n' + ' is set to a *loader* object.\n' '\n' - ' "__file__"\n' - ' The pathname of the file from which the module was loaded, if ' - 'it\n' - ' was loaded from a file. The "__file__" attribute may be ' - 'missing\n' - ' for certain types of modules, such as C modules that are\n' - ' statically linked into the interpreter. For extension ' + ' It is **strongly** recommended that you use\n' + ' "module.__spec__.loader" instead of "module.__loader__".\n' + '\n' + ' Changed in version 3.4: This attribute now defaults to "None" ' + 'for\n' + ' modules created dynamically using the "types.ModuleType"\n' + ' constructor. Previously the attribute was optional.\n' + '\n' + ' Deprecated since version 3.12, will be removed in version 3.16:\n' + ' Setting "__loader__" on a module while failing to set\n' + ' "__spec__.loader" is deprecated. In Python 3.16, "__loader__" ' + 'will\n' + ' cease to be set or taken into consideration by the import system ' + 'or\n' + ' the standard library.\n' + '\n' + 'module.__path__\n' + '\n' + ' A (possibly empty) *sequence* of strings enumerating the ' + 'locations\n' + ' where the package’s submodules will be found. Non-package ' 'modules\n' - ' loaded dynamically from a shared library, it’s the pathname ' - 'of\n' - ' the shared library file.\n' + ' should not have a "__path__" attribute. See __path__ attributes ' + 'on\n' + ' modules for more details.\n' + '\n' + ' It is **strongly** recommended that you use\n' + ' "module.__spec__.submodule_search_locations" instead of\n' + ' "module.__path__".\n' + '\n' + 'module.__file__\n' + '\n' + 'module.__cached__\n' + '\n' + ' "__file__" and "__cached__" are both optional attributes that ' + 'may\n' + ' or may not be set. Both attributes should be a "str" when they ' + 'are\n' + ' available.\n' + '\n' + ' "__file__" indicates the pathname of the file from which the ' + 'module\n' + ' was loaded (if loaded from a file), or the pathname of the ' + 'shared\n' + ' library file for extension modules loaded dynamically from a ' + 'shared\n' + ' library. It might be missing for certain types of modules, such ' + 'as\n' + ' C modules that are statically linked into the interpreter, and ' + 'the\n' + ' import system may opt to leave it unset if it has no semantic\n' + ' meaning (for example, a module loaded from a database).\n' + '\n' + ' If "__file__" is set then the "__cached__" attribute might also ' + 'be\n' + ' set, which is the path to any compiled version of the code ' + '(for\n' + ' example, a byte-compiled file). The file does not need to exist ' + 'to\n' + ' set this attribute; the path can simply point to where the ' + 'compiled\n' + ' file *would* exist (see **PEP 3147**).\n' + '\n' + ' Note that "__cached__" may be set even if "__file__" is not ' + 'set.\n' + ' However, that scenario is quite atypical. Ultimately, the ' + '*loader*\n' + ' is what makes use of the module spec provided by the *finder* ' + '(from\n' + ' which "__file__" and "__cached__" are derived). So if a loader ' + 'can\n' + ' load from a cached module but otherwise does not load from a ' + 'file,\n' + ' that atypical scenario may be appropriate.\n' + '\n' + ' It is **strongly** recommended that you use\n' + ' "module.__spec__.cached" instead of "module.__cached__".\n' + '\n' + '\n' + 'Other writable attributes on module objects\n' + '-------------------------------------------\n' + '\n' + 'As well as the import-related attributes listed above, module ' + 'objects\n' + 'also have the following writable attributes:\n' + '\n' + 'module.__doc__\n' + '\n' + ' The module’s documentation string, or "None" if unavailable. ' + 'See\n' + ' also: "__doc__ attributes".\n' '\n' - ' "__annotations__"\n' - ' A dictionary containing *variable annotations* collected ' - 'during\n' - ' module body execution. For best practices on working with\n' - ' "__annotations__", please see Annotations Best Practices.\n' + 'module.__annotations__\n' '\n' - 'Special read-only attribute: "__dict__" is the module’s namespace ' - 'as a\n' - 'dictionary object.\n' + ' A dictionary containing *variable annotations* collected during\n' + ' module body execution. For best practices on working with\n' + ' "__annotations__", please see Annotations Best Practices.\n' '\n' - '**CPython implementation detail:** Because of the way CPython ' - 'clears\n' - 'module dictionaries, the module dictionary will be cleared when ' + '\n' + 'Module dictionaries\n' + '-------------------\n' + '\n' + 'Module objects also have the following special read-only ' + 'attribute:\n' + '\n' + 'module.__dict__\n' + '\n' + ' The module’s namespace as a dictionary object. Uniquely among ' + 'the\n' + ' attributes listed here, "__dict__" cannot be accessed as a ' + 'global\n' + ' variable from within a module; it can only be accessed as an\n' + ' attribute on module objects.\n' + '\n' + ' **CPython implementation detail:** Because of the way CPython\n' + ' clears module dictionaries, the module dictionary will be ' + 'cleared\n' + ' when the module falls out of scope even if the dictionary still ' + 'has\n' + ' live references. To avoid this, copy the dictionary or keep ' 'the\n' - 'module falls out of scope even if the dictionary still has live\n' - 'references. To avoid this, copy the dictionary or keep the module\n' - 'around while using its dictionary directly.\n' + ' module around while using its dictionary directly.\n' '\n' '\n' 'Custom classes\n' @@ -14719,7 +14992,7 @@ '| | version ' '3.12: This attribute of code objects is |\n' '| | deprecated, ' - 'and may be removed in Python 3.14. |\n' + 'and may be removed in Python 3.15. |\n' '+----------------------------------------------------+----------------------------------------------------+\n' '| codeobject.co_stacksize | The required ' 'stack size of the code object |\n' @@ -15174,21 +15447,23 @@ '\n' ' If no positional argument is given, an empty dictionary ' 'is created.\n' - ' If a positional argument is given and it is a mapping ' - 'object, a\n' - ' dictionary is created with the same key-value pairs as ' - 'the mapping\n' - ' object. Otherwise, the positional argument must be an ' - '*iterable*\n' - ' object. Each item in the iterable must itself be an ' - 'iterable with\n' - ' exactly two objects. The first object of each item ' - 'becomes a key\n' - ' in the new dictionary, and the second object the ' - 'corresponding\n' - ' value. If a key occurs more than once, the last value ' - 'for that key\n' - ' becomes the corresponding value in the new dictionary.\n' + ' If a positional argument is given and it defines a ' + '"keys()" method,\n' + ' a dictionary is created by calling "__getitem__()" on the ' + 'argument\n' + ' with each returned key from the method. Otherwise, the ' + 'positional\n' + ' argument must be an *iterable* object. Each item in the ' + 'iterable\n' + ' must itself be an iterable with exactly two elements. ' + 'The first\n' + ' element of each item becomes a key in the new dictionary, ' + 'and the\n' + ' second element the corresponding value. If a key occurs ' + 'more than\n' + ' once, the last value for that key becomes the ' + 'corresponding value\n' + ' in the new dictionary.\n' '\n' ' If keyword arguments are given, the keyword arguments and ' 'their\n' @@ -15383,15 +15658,17 @@ '*other*,\n' ' overwriting existing keys. Return "None".\n' '\n' - ' "update()" accepts either another dictionary object or ' - 'an\n' - ' iterable of key/value pairs (as tuples or other ' - 'iterables of\n' - ' length two). If keyword arguments are specified, the ' - 'dictionary\n' - ' is then updated with those key/value pairs: ' - '"d.update(red=1,\n' - ' blue=2)".\n' + ' "update()" accepts either another object with a ' + '"keys()" method\n' + ' (in which case "__getitem__()" is called with every ' + 'key returned\n' + ' from the method) or an iterable of key/value pairs (as ' + 'tuples or\n' + ' other iterables of length two). If keyword arguments ' + 'are\n' + ' specified, the dictionary is then updated with those ' + 'key/value\n' + ' pairs: "d.update(red=1, blue=2)".\n' '\n' ' values()\n' '\n' @@ -16699,18 +16976,15 @@ ' enter = type(manager).__enter__\n' ' exit = type(manager).__exit__\n' ' value = enter(manager)\n' - ' hit_except = False\n' '\n' ' try:\n' ' TARGET = value\n' ' SUITE\n' ' except:\n' - ' hit_except = True\n' ' if not exit(manager, *sys.exc_info()):\n' ' raise\n' - ' finally:\n' - ' if not hit_except:\n' - ' exit(manager, None, None, None)\n' + ' else:\n' + ' exit(manager, None, None, None)\n' '\n' 'With more than one item, the context managers are processed as if\n' 'multiple "with" statements were nested:\n' @@ -16751,7 +17025,8 @@ '\n' 'A "yield" statement is semantically equivalent to a yield ' 'expression.\n' - 'The yield statement can be used to omit the parentheses that would\n' + 'The "yield" statement can be used to omit the parentheses that ' + 'would\n' 'otherwise be required in the equivalent yield expression ' 'statement.\n' 'For example, the yield statements\n' @@ -16767,10 +17042,9 @@ 'Yield expressions and statements are only used when defining a\n' '*generator* function, and are only used in the body of the ' 'generator\n' - 'function. Using yield in a function definition is sufficient to ' - 'cause\n' - 'that definition to create a generator function instead of a normal\n' - 'function.\n' + 'function. Using "yield" in a function definition is sufficient to\n' + 'cause that definition to create a generator function instead of a\n' + 'normal function.\n' '\n' 'For full details of "yield" semantics, refer to the Yield ' 'expressions\n' diff --git a/Misc/NEWS.d/3.12.8.rst b/Misc/NEWS.d/3.12.8.rst new file mode 100644 index 00000000000000..6bec66f8557944 --- /dev/null +++ b/Misc/NEWS.d/3.12.8.rst @@ -0,0 +1,1101 @@ +.. date: 2024-09-24-10-48-46 +.. gh-issue: 124448 +.. nonce: bFMrS6 +.. release date: 2024-12-03 +.. section: macOS + +Update bundled Tcl/Tk in macOS installer to 8.6.15. + +.. + +.. date: 2024-11-16-22-08-41 +.. gh-issue: 126911 +.. nonce: HchCZZ +.. section: Windows + +Update credits command output. + +.. + +.. date: 2024-10-29-19-48-03 +.. gh-issue: 125315 +.. nonce: jdB9qN +.. section: Windows + +Avoid crashing in :mod:`platform` due to slow WMI calls on some Windows +machines. + +.. + +.. date: 2024-10-15-21-28-43 +.. gh-issue: 125550 +.. nonce: hmGWCP +.. section: Windows + +Enable the :ref:`launcher` to detect Python 3.14 installs from the Windows +Store. + +.. + +.. date: 2024-09-24-19-04-56 +.. gh-issue: 124448 +.. nonce: srVT3d +.. section: Windows + +Updated bundled Tcl/Tk to 8.6.15. + +.. + +.. date: 2024-11-13-22-23-36 +.. gh-issue: 126807 +.. nonce: vpaWuN +.. section: Tools/Demos + +Fix extraction warnings in :program:`pygettext.py` caused by mistaking +function definitions for function calls. + +.. + +.. date: 2024-11-17-16-56-48 +.. gh-issue: 126909 +.. nonce: 60VTxW +.. section: Tests + +Fix test_os extended attribute tests to work on filesystems with 1 KiB xattr +size limit. + +.. + +.. date: 2024-10-07-14-13-38 +.. gh-issue: 125041 +.. nonce: PKLWDf +.. section: Tests + +Re-enable skipped tests for :mod:`zlib` on the s390x architecture: only skip +checks of the compressed bytes, which can be different between zlib's +software implementation and the hardware-accelerated implementation. + +.. + +.. date: 2024-09-30-22-52-44 +.. gh-issue: 124295 +.. nonce: VZy5kx +.. section: Tests + +Add translation tests to the :mod:`argparse` module. + +.. + +.. date: 2024-11-13-11-09-12 +.. gh-issue: 126623 +.. nonce: TO7NnR +.. section: Security + +Upgrade libexpat to 2.6.4 + +.. + +.. date: 2024-11-27-16-06-10 +.. gh-issue: 127303 +.. nonce: asqkgh +.. section: Library + +Publicly expose :data:`~token.EXACT_TOKEN_TYPES` in :attr:`!token.__all__`. + +.. + +.. date: 2024-11-27-14-06-35 +.. gh-issue: 123967 +.. nonce: wxUmnW +.. section: Library + +Fix faulthandler for trampoline frames. If the top-most frame is a +trampoline frame, skip it. Patch by Victor Stinner. + +.. + +.. date: 2024-11-24-14-20-17 +.. gh-issue: 127182 +.. nonce: WmfY2g +.. section: Library + +Fix :meth:`!io.StringIO.__setstate__` crash, when :const:`None` was passed +as the first value. + +.. + +.. date: 2024-11-24-12-41-31 +.. gh-issue: 127217 +.. nonce: UAXGFr +.. section: Library + +Fix :func:`urllib.request.pathname2url` for paths starting with multiple +slashes on Posix. + +.. + +.. date: 2024-11-22-10-42-34 +.. gh-issue: 127035 +.. nonce: UnbDlr +.. section: Library + +Fix :mod:`shutil.which` on Windows. Now it looks at direct match if and only +if the command ends with a PATHEXT extension or X_OK is not in mode. Support +extensionless files if "." is in PATHEXT. Support PATHEXT extensions that +end with a dot. + +.. + +.. date: 2024-11-22-03-40-02 +.. gh-issue: 127078 +.. nonce: gI_PaP +.. section: Library + +Fix issue where :func:`urllib.request.url2pathname` failed to discard an +extra slash before a UNC drive in the URL path on Windows. + +.. + +.. date: 2024-11-22-02-31-55 +.. gh-issue: 126766 +.. nonce: jfkhBH +.. section: Library + +Fix issue where :func:`urllib.request.url2pathname` failed to discard any +'localhost' authority present in the URL. + +.. + +.. date: 2024-11-20-16-58-59 +.. gh-issue: 126997 +.. nonce: 0PI41Y +.. section: Library + +Fix support of STRING and GLOBAL opcodes with non-ASCII arguments in +:mod:`pickletools`. :func:`pickletools.dis` now outputs non-ASCII bytes in +STRING, BINSTRING and SHORT_BINSTRING arguments as escaped (``\xXX``). + +.. + +.. date: 2024-11-20-08-54-11 +.. gh-issue: 126618 +.. nonce: ef_53g +.. section: Library + +Fix the representation of :class:`itertools.count` objects when the count +value is :data:`sys.maxsize`. + +.. + +.. date: 2024-11-15-01-50-36 +.. gh-issue: 85168 +.. nonce: bP8VIN +.. section: Library + +Fix issue where :func:`urllib.request.url2pathname` and +:func:`~urllib.request.pathname2url` always used UTF-8 when quoting and +unquoting file URIs. They now use the :term:`filesystem encoding and error +handler`. + +.. + +.. date: 2024-11-14-22-25-49 +.. gh-issue: 67877 +.. nonce: G9hw0w +.. section: Library + +Fix memory leaks when :mod:`regular expression ` matching terminates +abruptly, either because of a signal or because memory allocation fails. + +.. + +.. date: 2024-11-13-22-25-57 +.. gh-issue: 126789 +.. nonce: lKzlc7 +.. section: Library + +Fixed the values of :py:func:`sysconfig.get_config_vars`, +:py:func:`sysconfig.get_paths`, and their siblings when the :py:mod:`site` +initialization happens after :py:mod:`sysconfig` has built a cache for +:py:func:`sysconfig.get_config_vars`. + +.. + +.. date: 2024-11-13-20-03-18 +.. gh-issue: 126188 +.. nonce: RJLKk- +.. section: Library + +Update bundled pip to 24.3.1 + +.. + +.. date: 2024-11-12-21-43-12 +.. gh-issue: 126766 +.. nonce: oi2KJ7 +.. section: Library + +Fix issue where :func:`urllib.request.url2pathname` failed to discard two +leading slashes introducing an empty authority section. + +.. + +.. date: 2024-11-12-13-14-47 +.. gh-issue: 126727 +.. nonce: 5Eqfqd +.. section: Library + +``locale.nl_langinfo(locale.ERA)`` now returns multiple era description +segments separated by semicolons. Previously it only returned the first +segment on platforms with Glibc. + +.. + +.. date: 2024-11-11-13-24-22 +.. gh-issue: 126699 +.. nonce: ONGbMd +.. section: Library + +Allow :class:`collections.abc.AsyncIterator` to be a base for Protocols. + +.. + +.. date: 2024-11-10-18-14-51 +.. gh-issue: 104745 +.. nonce: zAa5Ke +.. section: Library + +Limit starting a patcher (from :func:`unittest.mock.patch` or +:func:`unittest.mock.patch.object`) more than once without stopping it + +.. + +.. date: 2024-11-09-10-31-10 +.. gh-issue: 126595 +.. nonce: A-7MyC +.. section: Library + +Fix a crash when instantiating :class:`itertools.count` with an initial +count of :data:`sys.maxsize` on debug builds. Patch by Bénédikt Tran. + +.. + +.. date: 2024-11-08-17-05-10 +.. gh-issue: 120423 +.. nonce: 7rdLVV +.. section: Library + +Fix issue where :func:`urllib.request.pathname2url` mishandled Windows paths +with embedded forward slashes. + +.. + +.. date: 2024-11-08-11-06-14 +.. gh-issue: 126565 +.. nonce: dFFO22 +.. section: Library + +Improve performances of :meth:`zipfile.Path.open` for non-reading modes. + +.. + +.. date: 2024-11-07-22-41-47 +.. gh-issue: 126505 +.. nonce: iztYE1 +.. section: Library + +Fix bugs in compiling case-insensitive :mod:`regular expressions ` with +character classes containing non-BMP characters: upper-case non-BMP +character did was ignored and the ASCII flag was ignored when matching a +character range whose upper bound is beyond the BMP region. + +.. + +.. date: 2024-11-07-01-40-11 +.. gh-issue: 117378 +.. nonce: o9O5uM +.. section: Library + +Fixed the :mod:`multiprocessing` ``"forkserver"`` start method forkserver +process to correctly inherit the parent's :data:`sys.path` during the +importing of :func:`multiprocessing.set_forkserver_preload` modules in the +same manner as :data:`sys.path` is configured in workers before executing +work items. + +This bug caused some forkserver module preloading to silently fail to +preload. This manifested as a performance degration in child processes when +the ``sys.path`` was required due to additional repeated work in every +worker. + +It could also have a side effect of ``""`` remaining in :data:`sys.path` +during forkserver preload imports instead of the absolute path from +:func:`os.getcwd` at multiprocessing import time used in the worker +``sys.path``. + +The ``sys.path`` differences between phases in the child process could +potentially have caused preload to import incorrect things from the wrong +location. We are unaware of that actually having happened in practice. + +.. + +.. date: 2024-11-06-23-40-28 +.. gh-issue: 125679 +.. nonce: Qq9xF5 +.. section: Library + +The :class:`multiprocessing.Lock` and :class:`multiprocessing.RLock` +``repr`` values no longer say "unknown" on macOS. + +.. + +.. date: 2024-11-06-18-30-50 +.. gh-issue: 126476 +.. nonce: F1wh3c +.. section: Library + +Raise :class:`calendar.IllegalMonthError` (now a subclass of +:class:`IndexError`) for :func:`calendar.month` when the input month is not +correct. + +.. + +.. date: 2024-11-06-13-41-38 +.. gh-issue: 126489 +.. nonce: toaf-0 +.. section: Library + +The Python implementation of :mod:`pickle` no longer calls +:meth:`pickle.Pickler.persistent_id` for the result of +:meth:`!persistent_id`. + +.. + +.. date: 2024-11-02-19-20-44 +.. gh-issue: 126303 +.. nonce: yVvyWB +.. section: Library + +Fix pickling and copying of :class:`os.sched_param` objects. + +.. + +.. date: 2024-11-01-14-31-41 +.. gh-issue: 126138 +.. nonce: yTniOG +.. section: Library + +Fix a use-after-free crash on :class:`asyncio.Task` objects whose underlying +coroutine yields an object that implements an evil +:meth:`~object.__getattribute__`. Patch by Nico Posada. + +.. + +.. date: 2024-10-31-14-06-28 +.. gh-issue: 126220 +.. nonce: uJAJCU +.. section: Library + +Fix crash in :class:`!cProfile.Profile` and :class:`!_lsprof.Profiler` when +their callbacks were directly called with 0 arguments. + +.. + +.. date: 2024-10-30-23-59-36 +.. gh-issue: 126212 +.. nonce: _9uYjT +.. section: Library + +Fix issue where :func:`urllib.request.pathname2url` and +:func:`~urllib.request.url2pathname` removed slashes from Windows DOS drive +paths and URLs. + +.. + +.. date: 2024-10-30-20-45-17 +.. gh-issue: 126205 +.. nonce: CHEmtx +.. section: Library + +Fix issue where :func:`urllib.request.pathname2url` generated URLs beginning +with four slashes (rather than two) when given a Windows UNC path. + +.. + +.. date: 2024-10-29-11-45-44 +.. gh-issue: 126105 +.. nonce: cOL-R6 +.. section: Library + +Fix a crash in :mod:`ast` when the :attr:`ast.AST._fields` attribute is +deleted. + +.. + +.. date: 2024-10-29-10-58-52 +.. gh-issue: 126106 +.. nonce: rlF798 +.. section: Library + +Fixes a possible ``NULL`` pointer dereference in :mod:`ssl`. + +.. + +.. date: 2024-10-29-10-38-28 +.. gh-issue: 126080 +.. nonce: qKRBuo +.. section: Library + +Fix a use-after-free crash on :class:`asyncio.Task` objects for which the +underlying event loop implements an evil :meth:`~object.__getattribute__`. +Reported by Nico-Posada. Patch by Bénédikt Tran. + +.. + +.. date: 2024-10-28-22-35-22 +.. gh-issue: 126083 +.. nonce: TuI--n +.. section: Library + +Fixed a reference leak in :class:`asyncio.Task` objects when reinitializing +the same object with a non-``None`` context. Patch by Nico Posada. + +.. + +.. date: 2024-10-26-12-50-48 +.. gh-issue: 125984 +.. nonce: d4vp5_ +.. section: Library + +Fix use-after-free crashes on :class:`asyncio.Future` objects for which the +underlying event loop implements an evil :meth:`~object.__getattribute__`. +Reported by Nico-Posada. Patch by Bénédikt Tran. + +.. + +.. date: 2024-10-25-11-13-24 +.. gh-issue: 125969 +.. nonce: YvbrTr +.. section: Library + +Fix an out-of-bounds crash when an evil :meth:`asyncio.loop.call_soon` +mutates the length of the internal callbacks list. Patch by Bénédikt Tran. + +.. + +.. date: 2024-10-25-10-53-56 +.. gh-issue: 125966 +.. nonce: eOCYU_ +.. section: Library + +Fix a use-after-free crash in :meth:`asyncio.Future.remove_done_callback`. +Patch by Bénédikt Tran. + +.. + +.. date: 2024-10-24-14-08-10 +.. gh-issue: 125789 +.. nonce: eaiAMw +.. section: Library + +Fix possible crash when mutating list of callbacks returned by +:attr:`!asyncio.Future._callbacks`. It now always returns a new copy in C +implementation :mod:`!_asyncio`. Patch by Kumar Aditya. + +.. + +.. date: 2024-10-24-10-49-47 +.. gh-issue: 124452 +.. nonce: eqTRgx +.. section: Library + +Fix an issue in :meth:`email.policy.EmailPolicy.header_source_parse` and +:meth:`email.policy.Compat32.header_source_parse` that introduced spurious +leading whitespaces into header values when the header includes a newline +character after the header name delimiter (``:``) and before the value. + +.. + +.. date: 2024-10-23-17-45-40 +.. gh-issue: 125884 +.. nonce: 41E_PD +.. section: Library + +Fixed the bug for :mod:`pdb` where it can't set breakpoints on functions +with certain annotations. + +.. + +.. date: 2024-10-22-13-28-00 +.. gh-issue: 125355 +.. nonce: zssHm_ +.. section: Library + +Fix several bugs in :meth:`argparse.ArgumentParser.parse_intermixed_args`. + +* The parser no longer changes temporarily during parsing. +* Default values are not processed twice. +* Required mutually exclusive groups containing positional arguments are now supported. +* The missing arguments report now includes the names of all required optional and positional arguments. +* Unknown options can be intermixed with positional arguments in parse_known_intermixed_args(). + +.. + +.. date: 2024-10-18-09-51-29 +.. gh-issue: 125682 +.. nonce: vsj4cU +.. section: Library + +Reject non-ASCII digits in the Python implementation of :func:`json.loads` +conforming to the JSON specification. + +.. + +.. date: 2024-10-18-08-58-10 +.. gh-issue: 125660 +.. nonce: sDdDqO +.. section: Library + +Reject invalid unicode escapes for Python implementation of +:func:`json.loads`. + +.. + +.. date: 2024-10-17-16-10-29 +.. gh-issue: 125259 +.. nonce: oMew0c +.. section: Library + +Fix the notes removal logic for errors thrown in enum initialization. + +.. + +.. date: 2024-10-15-14-01-03 +.. gh-issue: 125519 +.. nonce: TqGh6a +.. section: Library + +Improve traceback if :func:`importlib.reload` is called with an object that +is not a module. Patch by Alex Waygood. + +.. + +.. date: 2024-10-14-17-29-34 +.. gh-issue: 125451 +.. nonce: fmP3T9 +.. section: Library + +Fix deadlock when :class:`concurrent.futures.ProcessPoolExecutor` shuts down +concurrently with an error when feeding a job to a worker process. + +.. + +.. date: 2024-10-14-04-44-12 +.. gh-issue: 125422 +.. nonce: MlVuC6 +.. section: Library + +Fixed the bug where :mod:`pdb` and :mod:`bdb` can step into the bottom +caller frame. + +.. + +.. date: 2024-10-14-02-27-03 +.. gh-issue: 100141 +.. nonce: NuAcwa +.. section: Library + +Fixed the bug where :mod:`pdb` will be stuck in an infinite loop when +debugging an empty file. + +.. + +.. date: 2024-10-13-20-21-35 +.. gh-issue: 53203 +.. nonce: Rz1c8A +.. section: Library + +Fix :func:`time.strptime` for ``%c``, ``%x`` and ``%X`` formats in many +locales that use non-ASCII digits, like Persian, Burmese, Odia and Shan. + +.. + +.. date: 2024-10-10-19-57-35 +.. gh-issue: 125254 +.. nonce: RtZxXS +.. section: Library + +Fix a bug where ArgumentError includes the incorrect ambiguous option in +:mod:`argparse`. + +.. + +.. date: 2024-10-09-21-42-43 +.. gh-issue: 61011 +.. nonce: pQXZb1 +.. section: Library + +Fix inheritance of nested mutually exclusive groups from parent parser in +:class:`argparse.ArgumentParser`. Previously, all nested mutually exclusive +groups lost their connection to the group containing them and were displayed +as belonging directly to the parser. + +.. + +.. date: 2024-10-09-17-07-33 +.. gh-issue: 52551 +.. nonce: PBakSY +.. section: Library + +Fix encoding issues in :func:`time.strftime`, the +:meth:`~datetime.datetime.strftime` method of the :mod:`datetime` classes +:class:`~datetime.datetime`, :class:`~datetime.date` and +:class:`~datetime.time` and formatting of these classes. Characters not +encodable in the current locale are now acceptable in the format string. +Surrogate pairs and sequence of surrogatescape-encoded bytes are no longer +recombinated. Embedded null character no longer terminates the format +string. + +.. + +.. date: 2024-10-09-07-09-00 +.. gh-issue: 125118 +.. nonce: J9rQ1S +.. section: Library + +Don't copy arbitrary values to :c:expr:`_Bool` in the :mod:`struct` module. + +.. + +.. date: 2024-10-08-21-17-16 +.. gh-issue: 125069 +.. nonce: 0RP0Mx +.. section: Library + +Fix an issue where providing a :class:`pathlib.PurePath` object as an +initializer argument to a second :class:`~pathlib.PurePath` object with a +different flavour resulted in arguments to the former object's initializer +being joined by the latter object's flavour. + +.. + +.. date: 2024-10-08-12-09-09 +.. gh-issue: 124969 +.. nonce: _VBQLq +.. section: Library + +Fix ``locale.nl_langinfo(locale.ALT_DIGITS)`` on platforms with glibc. Now +it returns a string consisting of up to 100 semicolon-separated symbols (an +empty string in most locales) on all Posix platforms. Previously it only +returned the first symbol or an empty string. + +.. + +.. date: 2024-10-04-08-46-00 +.. gh-issue: 124958 +.. nonce: rea9-x +.. section: Library + +Fix refcycles in exceptions raised from :class:`asyncio.TaskGroup` and the +python implementation of :class:`asyncio.Future` + +.. + +.. date: 2024-10-03-20-45-57 +.. gh-issue: 53203 +.. nonce: 3Sk4Ia +.. section: Library + +Fix :func:`time.strptime` for ``%c`` and ``%x`` formats in many locales: +Arabic, Bislama, Breton, Bodo, Kashubian, Chuvash, Estonian, French, Irish, +Ge'ez, Gurajati, Manx Gaelic, Hebrew, Hindi, Chhattisgarhi, Haitian Kreyol, +Japanese, Kannada, Korean, Marathi, Malay, Norwegian, Nynorsk, Punjabi, +Rajasthani, Tok Pisin, Yoruba, Yue Chinese, Yau/Nungon and Chinese. + +.. + +.. date: 2024-10-02-21-11-18 +.. gh-issue: 124917 +.. nonce: Lnwh5b +.. section: Library + +Allow calling :func:`os.path.exists` and :func:`os.path.lexists` with +keyword arguments on Windows. Fixes a regression in 3.12.4. + +.. + +.. date: 2024-10-02-15-05-45 +.. gh-issue: 124653 +.. nonce: tqsTu9 +.. section: Library + +Fix detection of the minimal Queue API needed by the :mod:`logging` module. +Patch by Bénédikt Tran. + +.. + +.. date: 2024-10-01-17-12-20 +.. gh-issue: 124858 +.. nonce: Zy0tvT +.. section: Library + +Fix reference cycles left in tracebacks in :func:`asyncio.open_connection` +when used with ``happy_eyeballs_delay`` + +.. + +.. date: 2024-10-01-13-46-58 +.. gh-issue: 124390 +.. nonce: dK1Zcm +.. section: Library + +Fixed :exc:`AssertionError` when using +:func:`!asyncio.staggered.staggered_race` with +:attr:`asyncio.eager_task_factory`. + +.. + +.. date: 2024-09-28-02-03-04 +.. gh-issue: 124651 +.. nonce: bLBGtH +.. section: Library + +Properly quote template strings in :mod:`venv` activation scripts. + +.. + +.. date: 2024-09-26-13-43-39 +.. gh-issue: 124594 +.. nonce: peYhsP +.. section: Library + +All :mod:`asyncio` REPL prompts run in the same :class:`context +`. Contributed by Bartosz Sławecki. + +.. + +.. date: 2024-09-25-18-07-51 +.. gh-issue: 120378 +.. nonce: NlBSz_ +.. section: Library + +Fix a crash related to an integer overflow in :func:`curses.resizeterm` and +:func:`curses.resize_term`. + +.. + +.. date: 2024-09-24-22-38-51 +.. gh-issue: 123884 +.. nonce: iEPTK4 +.. section: Library + +Fixed bug in itertools.tee() handling of other tee inputs (a tee in a tee). +The output now has the promised *n* independent new iterators. Formerly, +the first iterator was identical (not independent) to the input iterator. +This would sometimes give surprising results. + +.. + +.. date: 2024-09-16-12-31-48 +.. gh-issue: 123978 +.. nonce: z3smEu +.. section: Library + +Remove broken :func:`time.thread_time` and :func:`time.thread_time_ns` on +NetBSD. + +.. + +.. date: 2024-09-13-18-24-27 +.. gh-issue: 124008 +.. nonce: XaiPQx +.. section: Library + +Fix possible crash (in debug build), incorrect output or returning incorrect +value from raw binary ``write()`` when writing to console on Windows. + +.. + +.. date: 2024-08-28-19-27-35 +.. gh-issue: 123370 +.. nonce: SPZ9Ux +.. section: Library + +Fix the canvas not clearing after running turtledemo clock. + +.. + +.. date: 2024-07-23-02-24-50 +.. gh-issue: 120754 +.. nonce: nHb5mG +.. section: Library + +Update unbounded ``read`` calls in :mod:`zipfile` to specify an explicit +``size`` putting a limit on how much data they may read. This also updates +handling around ZIP max comment size to match the standard instead of +reading comments that are one byte too long. + +.. + +.. date: 2024-06-06-04-06-05 +.. gh-issue: 70764 +.. nonce: 6511hw +.. section: Library + +Fixed an issue where :func:`inspect.getclosurevars` would incorrectly +classify an attribute name as a global variable when the name exists both as +an attribute name and a global variable. + +.. + +.. date: 2024-06-02-11-48-19 +.. gh-issue: 119826 +.. nonce: N1obGa +.. section: Library + +Always return an absolute path for :func:`os.path.abspath` on Windows. + +.. + +.. date: 2024-04-19-05-58-50 +.. gh-issue: 117766 +.. nonce: J3xepp +.. section: Library + +Always use :func:`str` to print ``choices`` in :mod:`argparse`. + +.. + +.. date: 2023-10-26-16-36-22 +.. gh-issue: 101955 +.. nonce: Ixu3IF +.. section: Library + +Fix SystemError when match regular expression pattern containing some +combination of possessive quantifier, alternative and capture group. + +.. + +.. date: 2023-02-15-23-54-42 +.. gh-issue: 88110 +.. nonce: KU6erv +.. section: Library + +Fixed :class:`multiprocessing.Process` reporting a ``.exitcode`` of 1 even +on success when using the ``"fork"`` start method while using a +:class:`concurrent.futures.ThreadPoolExecutor`. + +.. + +.. date: 2022-10-15-10-18-20 +.. gh-issue: 71936 +.. nonce: MzJjc_ +.. section: Library + +Fix a race condition in :class:`multiprocessing.pool.Pool`. + +.. + +.. bpo: 46128 +.. date: 2021-12-19-10-47-24 +.. nonce: Qv3EK1 +.. section: Library + +Strip :class:`unittest.IsolatedAsyncioTestCase` stack frames from reported +stacktraces. + +.. + +.. bpo: 14074 +.. date: 2018-12-04-07-36-27 +.. nonce: fMLKCu +.. section: Library + +Fix :mod:`argparse` metavar processing to allow positional arguments to have +a tuple metavar. + +.. + +.. date: 2024-10-04-15-34-34 +.. gh-issue: 122392 +.. nonce: V8K3w2 +.. section: IDLE + +Increase currently inadequate vertical spacing for the IDLE browsers (path, +module, and stack) on high-resolution monitors. + +.. + +.. date: 2024-10-10-23-46-54 +.. gh-issue: 125277 +.. nonce: QAby09 +.. section: Documentation + +Require Sphinx 7.2.6 or later to build the Python documentation. Patch by +Adam Turner. + +.. + +.. date: 2024-10-07-00-31-17 +.. gh-issue: 125018 +.. nonce: yKnymn +.. section: Documentation + +The :mod:`importlib.metadata` documentation now includes semantic +cross-reference targets for the significant documented APIs. This means +intersphinx references like :func:`importlib.metadata.version` will now work +as expected. + +.. + +.. date: 2024-07-19-12-22-48 +.. gh-issue: 121277 +.. nonce: wF_zKd +.. section: Documentation + +Writers of CPython's documentation can now use ``next`` as the version for +the ``versionchanged``, ``versionadded``, ``deprecated`` directives. + +.. + +.. date: 2023-03-28-22-24-45 +.. gh-issue: 60712 +.. nonce: So5uad +.. section: Documentation + +Include the :class:`object` type in the lists of documented types. Change by +Furkan Onder and Martin Panter. + +.. + +.. date: 2024-11-24-07-01-28 +.. gh-issue: 113841 +.. nonce: WFg-Bu +.. section: Core and Builtins + +Fix possible undefined behavior division by zero in :class:`complex`'s +:c:func:`_Py_c_pow`. + +.. + +.. date: 2024-11-12-19-24-00 +.. gh-issue: 126341 +.. nonce: 5SdAe1 +.. section: Core and Builtins + +Now :exc:`ValueError` is raised instead of :exc:`SystemError` when trying to +iterate over a released :class:`memoryview` object. + +.. + +.. date: 2024-11-09-16-10-22 +.. gh-issue: 126066 +.. nonce: 9zs4m4 +.. section: Core and Builtins + +Fix :mod:`importlib` to not write an incomplete .pyc files when a ulimit or +some other operating system mechanism is preventing the write to go through +fully. + +.. + +.. date: 2024-10-29-15-17-31 +.. gh-issue: 126139 +.. nonce: B4OQ8a +.. section: Core and Builtins + +Provide better error location when attempting to use a :term:`future +statement <__future__>` with an unknown future feature. + +.. + +.. date: 2024-10-05-23-53-06 +.. gh-issue: 125008 +.. nonce: ETANpd +.. section: Core and Builtins + +Fix :func:`tokenize.untokenize` producing invalid syntax for double braces +preceded by certain escape characters. + +.. + +.. date: 2024-10-03-14-39-41 +.. gh-issue: 123378 +.. nonce: dCxANf +.. section: Core and Builtins + +Fix a crash in the :meth:`~object.__str__` method of :exc:`UnicodeError` +objects when the :attr:`UnicodeError.start` and :attr:`UnicodeError.end` +values are invalid or out-of-range. Patch by Bénédikt Tran. + +.. + +.. date: 2024-09-26-18-21-06 +.. gh-issue: 116510 +.. nonce: FacUWO +.. section: Core and Builtins + +Fix a crash caused by immortal interned strings being shared between +sub-interpreters that use basic single-phase init. In that case, the string +can be used by an interpreter that outlives the interpreter that created and +interned it. For interpreters that share obmalloc state, also share the +interned dict with the main interpreter. + +.. + +.. date: 2024-05-12-03-10-36 +.. gh-issue: 118950 +.. nonce: 5Wc4vp +.. section: Core and Builtins + +Fix bug where SSLProtocol.connection_lost wasn't getting called when OSError +was thrown on writing to socket. + +.. + +.. date: 2023-12-30-00-21-45 +.. gh-issue: 113570 +.. nonce: _XQgsW +.. section: Core and Builtins + +Fixed a bug in ``reprlib.repr`` where it incorrectly called the repr method +on shadowed Python built-in types. + +.. + +.. date: 2023-09-22-21-01-56 +.. gh-issue: 109746 +.. nonce: 32MHt9 +.. section: Core and Builtins + +If :func:`!_thread.start_new_thread` fails to start a new thread, it deletes +its state from interpreter and thus avoids its repeated cleanup on +finalization. + +.. + +.. date: 2024-07-04-13-23-27 +.. gh-issue: 113601 +.. nonce: K3RLqp +.. section: C API + +Removed debug build assertions related to interning strings, which were +falsely triggered by stable ABI extensions. + +.. + +.. date: 2024-11-04-09-42-04 +.. gh-issue: 89640 +.. nonce: QBv05o +.. section: Build + +Hard-code float word ordering as little endian on WASM. + +.. + +.. date: 2024-10-16-09-37-51 +.. gh-issue: 89640 +.. nonce: UDsW-j +.. section: Build + +Improve detection of float word ordering on Linux when link-time +optimizations are enabled. diff --git a/Misc/NEWS.d/next/Build/2024-10-16-09-37-51.gh-issue-89640.UDsW-j.rst b/Misc/NEWS.d/next/Build/2024-10-16-09-37-51.gh-issue-89640.UDsW-j.rst deleted file mode 100644 index 5aba2c789b6842..00000000000000 --- a/Misc/NEWS.d/next/Build/2024-10-16-09-37-51.gh-issue-89640.UDsW-j.rst +++ /dev/null @@ -1,2 +0,0 @@ -Improve detection of float word ordering on Linux when link-time optimizations -are enabled. diff --git a/Misc/NEWS.d/next/Build/2024-11-04-09-42-04.gh-issue-89640.QBv05o.rst b/Misc/NEWS.d/next/Build/2024-11-04-09-42-04.gh-issue-89640.QBv05o.rst deleted file mode 100644 index 4fa44a1d6493b4..00000000000000 --- a/Misc/NEWS.d/next/Build/2024-11-04-09-42-04.gh-issue-89640.QBv05o.rst +++ /dev/null @@ -1 +0,0 @@ -Hard-code float word ordering as little endian on WASM. diff --git a/Misc/NEWS.d/next/C API/2024-07-04-13-23-27.gh-issue-113601.K3RLqp.rst b/Misc/NEWS.d/next/C API/2024-07-04-13-23-27.gh-issue-113601.K3RLqp.rst deleted file mode 100644 index 009cc2bf017180..00000000000000 --- a/Misc/NEWS.d/next/C API/2024-07-04-13-23-27.gh-issue-113601.K3RLqp.rst +++ /dev/null @@ -1,2 +0,0 @@ -Removed debug build assertions related to interning strings, which were -falsely triggered by stable ABI extensions. diff --git a/Misc/NEWS.d/next/Core and Builtins/2023-12-30-00-21-45.gh-issue-113570._XQgsW.rst b/Misc/NEWS.d/next/Core and Builtins/2023-12-30-00-21-45.gh-issue-113570._XQgsW.rst deleted file mode 100644 index 6e0f0afe05369b..00000000000000 --- a/Misc/NEWS.d/next/Core and Builtins/2023-12-30-00-21-45.gh-issue-113570._XQgsW.rst +++ /dev/null @@ -1 +0,0 @@ -Fixed a bug in ``reprlib.repr`` where it incorrectly called the repr method on shadowed Python built-in types. diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-05-12-03-10-36.gh-issue-118950.5Wc4vp.rst b/Misc/NEWS.d/next/Core and Builtins/2024-05-12-03-10-36.gh-issue-118950.5Wc4vp.rst deleted file mode 100644 index 82be975f4d808d..00000000000000 --- a/Misc/NEWS.d/next/Core and Builtins/2024-05-12-03-10-36.gh-issue-118950.5Wc4vp.rst +++ /dev/null @@ -1 +0,0 @@ -Fix bug where SSLProtocol.connection_lost wasn't getting called when OSError was thrown on writing to socket. diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2023-09-22-21-01-56.gh-issue-109746.32MHt9.rst b/Misc/NEWS.d/next/Core_and_Builtins/2023-09-22-21-01-56.gh-issue-109746.32MHt9.rst deleted file mode 100644 index 2d350c33aa6975..00000000000000 --- a/Misc/NEWS.d/next/Core_and_Builtins/2023-09-22-21-01-56.gh-issue-109746.32MHt9.rst +++ /dev/null @@ -1 +0,0 @@ -If :func:`!_thread.start_new_thread` fails to start a new thread, it deletes its state from interpreter and thus avoids its repeated cleanup on finalization. diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2024-09-26-18-21-06.gh-issue-116510.FacUWO.rst b/Misc/NEWS.d/next/Core_and_Builtins/2024-09-26-18-21-06.gh-issue-116510.FacUWO.rst deleted file mode 100644 index e3741321006548..00000000000000 --- a/Misc/NEWS.d/next/Core_and_Builtins/2024-09-26-18-21-06.gh-issue-116510.FacUWO.rst +++ /dev/null @@ -1,5 +0,0 @@ -Fix a crash caused by immortal interned strings being shared between -sub-interpreters that use basic single-phase init. In that case, the string -can be used by an interpreter that outlives the interpreter that created and -interned it. For interpreters that share obmalloc state, also share the -interned dict with the main interpreter. diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2024-10-03-14-39-41.gh-issue-123378.dCxANf.rst b/Misc/NEWS.d/next/Core_and_Builtins/2024-10-03-14-39-41.gh-issue-123378.dCxANf.rst deleted file mode 100644 index 5cd34535d674d3..00000000000000 --- a/Misc/NEWS.d/next/Core_and_Builtins/2024-10-03-14-39-41.gh-issue-123378.dCxANf.rst +++ /dev/null @@ -1,3 +0,0 @@ -Fix a crash in the :meth:`~object.__str__` method of :exc:`UnicodeError` -objects when the :attr:`UnicodeError.start` and :attr:`UnicodeError.end` -values are invalid or out-of-range. Patch by Bénédikt Tran. diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2024-10-05-23-53-06.gh-issue-125008.ETANpd.rst b/Misc/NEWS.d/next/Core_and_Builtins/2024-10-05-23-53-06.gh-issue-125008.ETANpd.rst deleted file mode 100644 index 8971e052860225..00000000000000 --- a/Misc/NEWS.d/next/Core_and_Builtins/2024-10-05-23-53-06.gh-issue-125008.ETANpd.rst +++ /dev/null @@ -1,2 +0,0 @@ -Fix :func:`tokenize.untokenize` producing invalid syntax for -double braces preceded by certain escape characters. diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2024-10-29-15-17-31.gh-issue-126139.B4OQ8a.rst b/Misc/NEWS.d/next/Core_and_Builtins/2024-10-29-15-17-31.gh-issue-126139.B4OQ8a.rst deleted file mode 100644 index 278971b46d18ab..00000000000000 --- a/Misc/NEWS.d/next/Core_and_Builtins/2024-10-29-15-17-31.gh-issue-126139.B4OQ8a.rst +++ /dev/null @@ -1,2 +0,0 @@ -Provide better error location when attempting to use a :term:`future -statement <__future__>` with an unknown future feature. diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2024-11-09-16-10-22.gh-issue-126066.9zs4m4.rst b/Misc/NEWS.d/next/Core_and_Builtins/2024-11-09-16-10-22.gh-issue-126066.9zs4m4.rst deleted file mode 100644 index 9c0072304ded63..00000000000000 --- a/Misc/NEWS.d/next/Core_and_Builtins/2024-11-09-16-10-22.gh-issue-126066.9zs4m4.rst +++ /dev/null @@ -1,3 +0,0 @@ -Fix :mod:`importlib` to not write an incomplete .pyc files when a ulimit or some -other operating system mechanism is preventing the write to go through -fully. diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2024-11-12-19-24-00.gh-issue-126341.5SdAe1.rst b/Misc/NEWS.d/next/Core_and_Builtins/2024-11-12-19-24-00.gh-issue-126341.5SdAe1.rst deleted file mode 100644 index c2436d2ebf4d09..00000000000000 --- a/Misc/NEWS.d/next/Core_and_Builtins/2024-11-12-19-24-00.gh-issue-126341.5SdAe1.rst +++ /dev/null @@ -1 +0,0 @@ -Now :exc:`ValueError` is raised instead of :exc:`SystemError` when trying to iterate over a released :class:`memoryview` object. diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2024-11-24-07-01-28.gh-issue-113841.WFg-Bu.rst b/Misc/NEWS.d/next/Core_and_Builtins/2024-11-24-07-01-28.gh-issue-113841.WFg-Bu.rst deleted file mode 100644 index 2b07fdfcc6b527..00000000000000 --- a/Misc/NEWS.d/next/Core_and_Builtins/2024-11-24-07-01-28.gh-issue-113841.WFg-Bu.rst +++ /dev/null @@ -1,2 +0,0 @@ -Fix possible undefined behavior division by zero in :class:`complex`'s -:c:func:`_Py_c_pow`. diff --git a/Misc/NEWS.d/next/Documentation/2023-03-28-22-24-45.gh-issue-60712.So5uad.rst b/Misc/NEWS.d/next/Documentation/2023-03-28-22-24-45.gh-issue-60712.So5uad.rst deleted file mode 100644 index e401cc2535e389..00000000000000 --- a/Misc/NEWS.d/next/Documentation/2023-03-28-22-24-45.gh-issue-60712.So5uad.rst +++ /dev/null @@ -1,2 +0,0 @@ -Include the :class:`object` type in the lists of documented types. -Change by Furkan Onder and Martin Panter. diff --git a/Misc/NEWS.d/next/Documentation/2024-07-19-12-22-48.gh-issue-121277.wF_zKd.rst b/Misc/NEWS.d/next/Documentation/2024-07-19-12-22-48.gh-issue-121277.wF_zKd.rst deleted file mode 100644 index 60f75ae0c21326..00000000000000 --- a/Misc/NEWS.d/next/Documentation/2024-07-19-12-22-48.gh-issue-121277.wF_zKd.rst +++ /dev/null @@ -1,2 +0,0 @@ -Writers of CPython's documentation can now use ``next`` as the version for -the ``versionchanged``, ``versionadded``, ``deprecated`` directives. diff --git a/Misc/NEWS.d/next/Documentation/2024-10-07-00-31-17.gh-issue-125018.yKnymn.rst b/Misc/NEWS.d/next/Documentation/2024-10-07-00-31-17.gh-issue-125018.yKnymn.rst deleted file mode 100644 index e910da5b879ba5..00000000000000 --- a/Misc/NEWS.d/next/Documentation/2024-10-07-00-31-17.gh-issue-125018.yKnymn.rst +++ /dev/null @@ -1,4 +0,0 @@ -The :mod:`importlib.metadata` documentation now includes semantic -cross-reference targets for the significant documented APIs. This means -intersphinx references like :func:`importlib.metadata.version` will -now work as expected. diff --git a/Misc/NEWS.d/next/Documentation/2024-10-10-23-46-54.gh-issue-125277.QAby09.rst b/Misc/NEWS.d/next/Documentation/2024-10-10-23-46-54.gh-issue-125277.QAby09.rst deleted file mode 100644 index fcd6e22c27b5f4..00000000000000 --- a/Misc/NEWS.d/next/Documentation/2024-10-10-23-46-54.gh-issue-125277.QAby09.rst +++ /dev/null @@ -1,2 +0,0 @@ -Require Sphinx 7.2.6 or later to build the Python documentation. -Patch by Adam Turner. diff --git a/Misc/NEWS.d/next/IDLE/2024-10-04-15-34-34.gh-issue-122392.V8K3w2.rst b/Misc/NEWS.d/next/IDLE/2024-10-04-15-34-34.gh-issue-122392.V8K3w2.rst deleted file mode 100644 index 541f6212794ef2..00000000000000 --- a/Misc/NEWS.d/next/IDLE/2024-10-04-15-34-34.gh-issue-122392.V8K3w2.rst +++ /dev/null @@ -1,2 +0,0 @@ -Increase currently inadequate vertical spacing for the IDLE browsers (path, -module, and stack) on high-resolution monitors. diff --git a/Misc/NEWS.d/next/Library/2018-12-04-07-36-27.bpo-14074.fMLKCu.rst b/Misc/NEWS.d/next/Library/2018-12-04-07-36-27.bpo-14074.fMLKCu.rst deleted file mode 100644 index 221c8e05fa98aa..00000000000000 --- a/Misc/NEWS.d/next/Library/2018-12-04-07-36-27.bpo-14074.fMLKCu.rst +++ /dev/null @@ -1,2 +0,0 @@ -Fix :mod:`argparse` metavar processing to allow positional arguments to have a -tuple metavar. diff --git a/Misc/NEWS.d/next/Library/2021-12-19-10-47-24.bpo-46128.Qv3EK1.rst b/Misc/NEWS.d/next/Library/2021-12-19-10-47-24.bpo-46128.Qv3EK1.rst deleted file mode 100644 index 7d11d20d94e8a3..00000000000000 --- a/Misc/NEWS.d/next/Library/2021-12-19-10-47-24.bpo-46128.Qv3EK1.rst +++ /dev/null @@ -1,2 +0,0 @@ -Strip :class:`unittest.IsolatedAsyncioTestCase` stack frames from reported -stacktraces. diff --git a/Misc/NEWS.d/next/Library/2022-10-15-10-18-20.gh-issue-71936.MzJjc_.rst b/Misc/NEWS.d/next/Library/2022-10-15-10-18-20.gh-issue-71936.MzJjc_.rst deleted file mode 100644 index a0959cc086fa9e..00000000000000 --- a/Misc/NEWS.d/next/Library/2022-10-15-10-18-20.gh-issue-71936.MzJjc_.rst +++ /dev/null @@ -1 +0,0 @@ -Fix a race condition in :class:`multiprocessing.pool.Pool`. diff --git a/Misc/NEWS.d/next/Library/2023-02-15-23-54-42.gh-issue-88110.KU6erv.rst b/Misc/NEWS.d/next/Library/2023-02-15-23-54-42.gh-issue-88110.KU6erv.rst deleted file mode 100644 index 42a83edc3ba68d..00000000000000 --- a/Misc/NEWS.d/next/Library/2023-02-15-23-54-42.gh-issue-88110.KU6erv.rst +++ /dev/null @@ -1,2 +0,0 @@ -Fixed :class:`multiprocessing.Process` reporting a ``.exitcode`` of 1 even on success when -using the ``"fork"`` start method while using a :class:`concurrent.futures.ThreadPoolExecutor`. diff --git a/Misc/NEWS.d/next/Library/2023-10-26-16-36-22.gh-issue-101955.Ixu3IF.rst b/Misc/NEWS.d/next/Library/2023-10-26-16-36-22.gh-issue-101955.Ixu3IF.rst deleted file mode 100644 index 89431010f784f8..00000000000000 --- a/Misc/NEWS.d/next/Library/2023-10-26-16-36-22.gh-issue-101955.Ixu3IF.rst +++ /dev/null @@ -1,2 +0,0 @@ -Fix SystemError when match regular expression pattern containing some -combination of possessive quantifier, alternative and capture group. diff --git a/Misc/NEWS.d/next/Library/2024-04-19-05-58-50.gh-issue-117766.J3xepp.rst b/Misc/NEWS.d/next/Library/2024-04-19-05-58-50.gh-issue-117766.J3xepp.rst deleted file mode 100644 index d090f931f0238d..00000000000000 --- a/Misc/NEWS.d/next/Library/2024-04-19-05-58-50.gh-issue-117766.J3xepp.rst +++ /dev/null @@ -1 +0,0 @@ -Always use :func:`str` to print ``choices`` in :mod:`argparse`. diff --git a/Misc/NEWS.d/next/Library/2024-06-02-11-48-19.gh-issue-119826.N1obGa.rst b/Misc/NEWS.d/next/Library/2024-06-02-11-48-19.gh-issue-119826.N1obGa.rst deleted file mode 100644 index 6901e7475dd082..00000000000000 --- a/Misc/NEWS.d/next/Library/2024-06-02-11-48-19.gh-issue-119826.N1obGa.rst +++ /dev/null @@ -1 +0,0 @@ -Always return an absolute path for :func:`os.path.abspath` on Windows. diff --git a/Misc/NEWS.d/next/Library/2024-06-06-04-06-05.gh-issue-70764.6511hw.rst b/Misc/NEWS.d/next/Library/2024-06-06-04-06-05.gh-issue-70764.6511hw.rst deleted file mode 100644 index 4cfb66a6ccc6ee..00000000000000 --- a/Misc/NEWS.d/next/Library/2024-06-06-04-06-05.gh-issue-70764.6511hw.rst +++ /dev/null @@ -1 +0,0 @@ -Fixed an issue where :func:`inspect.getclosurevars` would incorrectly classify an attribute name as a global variable when the name exists both as an attribute name and a global variable. diff --git a/Misc/NEWS.d/next/Library/2024-07-23-02-24-50.gh-issue-120754.nHb5mG.rst b/Misc/NEWS.d/next/Library/2024-07-23-02-24-50.gh-issue-120754.nHb5mG.rst deleted file mode 100644 index 6c33e7b7ec7716..00000000000000 --- a/Misc/NEWS.d/next/Library/2024-07-23-02-24-50.gh-issue-120754.nHb5mG.rst +++ /dev/null @@ -1 +0,0 @@ -Update unbounded ``read`` calls in :mod:`zipfile` to specify an explicit ``size`` putting a limit on how much data they may read. This also updates handling around ZIP max comment size to match the standard instead of reading comments that are one byte too long. diff --git a/Misc/NEWS.d/next/Library/2024-08-28-19-27-35.gh-issue-123370.SPZ9Ux.rst b/Misc/NEWS.d/next/Library/2024-08-28-19-27-35.gh-issue-123370.SPZ9Ux.rst deleted file mode 100644 index 1fd5cc54eaf3e7..00000000000000 --- a/Misc/NEWS.d/next/Library/2024-08-28-19-27-35.gh-issue-123370.SPZ9Ux.rst +++ /dev/null @@ -1 +0,0 @@ -Fix the canvas not clearing after running turtledemo clock. diff --git a/Misc/NEWS.d/next/Library/2024-09-13-18-24-27.gh-issue-124008.XaiPQx.rst b/Misc/NEWS.d/next/Library/2024-09-13-18-24-27.gh-issue-124008.XaiPQx.rst deleted file mode 100644 index cd6dd9a7a97e90..00000000000000 --- a/Misc/NEWS.d/next/Library/2024-09-13-18-24-27.gh-issue-124008.XaiPQx.rst +++ /dev/null @@ -1,2 +0,0 @@ -Fix possible crash (in debug build), incorrect output or returning incorrect -value from raw binary ``write()`` when writing to console on Windows. diff --git a/Misc/NEWS.d/next/Library/2024-09-16-12-31-48.gh-issue-123978.z3smEu.rst b/Misc/NEWS.d/next/Library/2024-09-16-12-31-48.gh-issue-123978.z3smEu.rst deleted file mode 100644 index e5b3229122b509..00000000000000 --- a/Misc/NEWS.d/next/Library/2024-09-16-12-31-48.gh-issue-123978.z3smEu.rst +++ /dev/null @@ -1 +0,0 @@ -Remove broken :func:`time.thread_time` and :func:`time.thread_time_ns` on NetBSD. diff --git a/Misc/NEWS.d/next/Library/2024-09-24-22-38-51.gh-issue-123884.iEPTK4.rst b/Misc/NEWS.d/next/Library/2024-09-24-22-38-51.gh-issue-123884.iEPTK4.rst deleted file mode 100644 index 55f1d4b41125c3..00000000000000 --- a/Misc/NEWS.d/next/Library/2024-09-24-22-38-51.gh-issue-123884.iEPTK4.rst +++ /dev/null @@ -1,4 +0,0 @@ -Fixed bug in itertools.tee() handling of other tee inputs (a tee in a tee). -The output now has the promised *n* independent new iterators. Formerly, -the first iterator was identical (not independent) to the input iterator. -This would sometimes give surprising results. diff --git a/Misc/NEWS.d/next/Library/2024-09-25-18-07-51.gh-issue-120378.NlBSz_.rst b/Misc/NEWS.d/next/Library/2024-09-25-18-07-51.gh-issue-120378.NlBSz_.rst deleted file mode 100644 index 1a8c1427b6b9b9..00000000000000 --- a/Misc/NEWS.d/next/Library/2024-09-25-18-07-51.gh-issue-120378.NlBSz_.rst +++ /dev/null @@ -1,2 +0,0 @@ -Fix a crash related to an integer overflow in :func:`curses.resizeterm` -and :func:`curses.resize_term`. diff --git a/Misc/NEWS.d/next/Library/2024-09-26-13-43-39.gh-issue-124594.peYhsP.rst b/Misc/NEWS.d/next/Library/2024-09-26-13-43-39.gh-issue-124594.peYhsP.rst deleted file mode 100644 index ac48bd84930745..00000000000000 --- a/Misc/NEWS.d/next/Library/2024-09-26-13-43-39.gh-issue-124594.peYhsP.rst +++ /dev/null @@ -1 +0,0 @@ -All :mod:`asyncio` REPL prompts run in the same :class:`context `. Contributed by Bartosz Sławecki. diff --git a/Misc/NEWS.d/next/Library/2024-09-28-02-03-04.gh-issue-124651.bLBGtH.rst b/Misc/NEWS.d/next/Library/2024-09-28-02-03-04.gh-issue-124651.bLBGtH.rst deleted file mode 100644 index 17fc9171390dd9..00000000000000 --- a/Misc/NEWS.d/next/Library/2024-09-28-02-03-04.gh-issue-124651.bLBGtH.rst +++ /dev/null @@ -1 +0,0 @@ -Properly quote template strings in :mod:`venv` activation scripts. diff --git a/Misc/NEWS.d/next/Library/2024-10-01-13-46-58.gh-issue-124390.dK1Zcm.rst b/Misc/NEWS.d/next/Library/2024-10-01-13-46-58.gh-issue-124390.dK1Zcm.rst deleted file mode 100644 index 89610fa44bf743..00000000000000 --- a/Misc/NEWS.d/next/Library/2024-10-01-13-46-58.gh-issue-124390.dK1Zcm.rst +++ /dev/null @@ -1 +0,0 @@ -Fixed :exc:`AssertionError` when using :func:`!asyncio.staggered.staggered_race` with :attr:`asyncio.eager_task_factory`. diff --git a/Misc/NEWS.d/next/Library/2024-10-01-17-12-20.gh-issue-124858.Zy0tvT.rst b/Misc/NEWS.d/next/Library/2024-10-01-17-12-20.gh-issue-124858.Zy0tvT.rst deleted file mode 100644 index c05d24a7c5aacb..00000000000000 --- a/Misc/NEWS.d/next/Library/2024-10-01-17-12-20.gh-issue-124858.Zy0tvT.rst +++ /dev/null @@ -1 +0,0 @@ -Fix reference cycles left in tracebacks in :func:`asyncio.open_connection` when used with ``happy_eyeballs_delay`` diff --git a/Misc/NEWS.d/next/Library/2024-10-02-15-05-45.gh-issue-124653.tqsTu9.rst b/Misc/NEWS.d/next/Library/2024-10-02-15-05-45.gh-issue-124653.tqsTu9.rst deleted file mode 100644 index 6f5ad12d2c2981..00000000000000 --- a/Misc/NEWS.d/next/Library/2024-10-02-15-05-45.gh-issue-124653.tqsTu9.rst +++ /dev/null @@ -1,2 +0,0 @@ -Fix detection of the minimal Queue API needed by the :mod:`logging` module. -Patch by Bénédikt Tran. diff --git a/Misc/NEWS.d/next/Library/2024-10-02-21-11-18.gh-issue-124917.Lnwh5b.rst b/Misc/NEWS.d/next/Library/2024-10-02-21-11-18.gh-issue-124917.Lnwh5b.rst deleted file mode 100644 index 6218528d2079c3..00000000000000 --- a/Misc/NEWS.d/next/Library/2024-10-02-21-11-18.gh-issue-124917.Lnwh5b.rst +++ /dev/null @@ -1,2 +0,0 @@ -Allow calling :func:`os.path.exists` and :func:`os.path.lexists` with -keyword arguments on Windows. Fixes a regression in 3.12.4. diff --git a/Misc/NEWS.d/next/Library/2024-10-03-20-45-57.gh-issue-53203.3Sk4Ia.rst b/Misc/NEWS.d/next/Library/2024-10-03-20-45-57.gh-issue-53203.3Sk4Ia.rst deleted file mode 100644 index 6895cffcf545fd..00000000000000 --- a/Misc/NEWS.d/next/Library/2024-10-03-20-45-57.gh-issue-53203.3Sk4Ia.rst +++ /dev/null @@ -1,5 +0,0 @@ -Fix :func:`time.strptime` for ``%c`` and ``%x`` formats in many locales: -Arabic, Bislama, Breton, Bodo, Kashubian, Chuvash, Estonian, French, Irish, -Ge'ez, Gurajati, Manx Gaelic, Hebrew, Hindi, Chhattisgarhi, Haitian Kreyol, -Japanese, Kannada, Korean, Marathi, Malay, Norwegian, Nynorsk, Punjabi, -Rajasthani, Tok Pisin, Yoruba, Yue Chinese, Yau/Nungon and Chinese. diff --git a/Misc/NEWS.d/next/Library/2024-10-04-08-46-00.gh-issue-124958.rea9-x.rst b/Misc/NEWS.d/next/Library/2024-10-04-08-46-00.gh-issue-124958.rea9-x.rst deleted file mode 100644 index 534d5bb8c898da..00000000000000 --- a/Misc/NEWS.d/next/Library/2024-10-04-08-46-00.gh-issue-124958.rea9-x.rst +++ /dev/null @@ -1 +0,0 @@ -Fix refcycles in exceptions raised from :class:`asyncio.TaskGroup` and the python implementation of :class:`asyncio.Future` diff --git a/Misc/NEWS.d/next/Library/2024-10-08-12-09-09.gh-issue-124969._VBQLq.rst b/Misc/NEWS.d/next/Library/2024-10-08-12-09-09.gh-issue-124969._VBQLq.rst deleted file mode 100644 index 7959ce2d1e9907..00000000000000 --- a/Misc/NEWS.d/next/Library/2024-10-08-12-09-09.gh-issue-124969._VBQLq.rst +++ /dev/null @@ -1,4 +0,0 @@ -Fix ``locale.nl_langinfo(locale.ALT_DIGITS)`` on platforms with glibc. -Now it returns a string consisting of up to 100 semicolon-separated symbols -(an empty string in most locales) on all Posix platforms. -Previously it only returned the first symbol or an empty string. diff --git a/Misc/NEWS.d/next/Library/2024-10-08-21-17-16.gh-issue-125069.0RP0Mx.rst b/Misc/NEWS.d/next/Library/2024-10-08-21-17-16.gh-issue-125069.0RP0Mx.rst deleted file mode 100644 index 73d5fa59303d4d..00000000000000 --- a/Misc/NEWS.d/next/Library/2024-10-08-21-17-16.gh-issue-125069.0RP0Mx.rst +++ /dev/null @@ -1,4 +0,0 @@ -Fix an issue where providing a :class:`pathlib.PurePath` object as an -initializer argument to a second :class:`~pathlib.PurePath` object with a -different flavour resulted in arguments to the former object's initializer - being joined by the latter object's flavour. diff --git a/Misc/NEWS.d/next/Library/2024-10-09-07-09-00.gh-issue-125118.J9rQ1S.rst b/Misc/NEWS.d/next/Library/2024-10-09-07-09-00.gh-issue-125118.J9rQ1S.rst deleted file mode 100644 index 5d57cdbbbc2fe9..00000000000000 --- a/Misc/NEWS.d/next/Library/2024-10-09-07-09-00.gh-issue-125118.J9rQ1S.rst +++ /dev/null @@ -1 +0,0 @@ -Don't copy arbitrary values to :c:expr:`_Bool` in the :mod:`struct` module. diff --git a/Misc/NEWS.d/next/Library/2024-10-09-17-07-33.gh-issue-52551.PBakSY.rst b/Misc/NEWS.d/next/Library/2024-10-09-17-07-33.gh-issue-52551.PBakSY.rst deleted file mode 100644 index edc9ac5bb23117..00000000000000 --- a/Misc/NEWS.d/next/Library/2024-10-09-17-07-33.gh-issue-52551.PBakSY.rst +++ /dev/null @@ -1,8 +0,0 @@ -Fix encoding issues in :func:`time.strftime`, the -:meth:`~datetime.datetime.strftime` method of the :mod:`datetime` classes -:class:`~datetime.datetime`, :class:`~datetime.date` and -:class:`~datetime.time` and formatting of these classes. Characters not -encodable in the current locale are now acceptable in the format string. -Surrogate pairs and sequence of surrogatescape-encoded bytes are no longer -recombinated. Embedded null character no longer terminates the format -string. diff --git a/Misc/NEWS.d/next/Library/2024-10-09-21-42-43.gh-issue-61011.pQXZb1.rst b/Misc/NEWS.d/next/Library/2024-10-09-21-42-43.gh-issue-61011.pQXZb1.rst deleted file mode 100644 index 20f9c0b9c78b12..00000000000000 --- a/Misc/NEWS.d/next/Library/2024-10-09-21-42-43.gh-issue-61011.pQXZb1.rst +++ /dev/null @@ -1,4 +0,0 @@ -Fix inheritance of nested mutually exclusive groups from parent parser in -:class:`argparse.ArgumentParser`. Previously, all nested mutually exclusive -groups lost their connection to the group containing them and were displayed -as belonging directly to the parser. diff --git a/Misc/NEWS.d/next/Library/2024-10-10-19-57-35.gh-issue-125254.RtZxXS.rst b/Misc/NEWS.d/next/Library/2024-10-10-19-57-35.gh-issue-125254.RtZxXS.rst deleted file mode 100644 index abe37fefedc3be..00000000000000 --- a/Misc/NEWS.d/next/Library/2024-10-10-19-57-35.gh-issue-125254.RtZxXS.rst +++ /dev/null @@ -1 +0,0 @@ -Fix a bug where ArgumentError includes the incorrect ambiguous option in :mod:`argparse`. diff --git a/Misc/NEWS.d/next/Library/2024-10-13-20-21-35.gh-issue-53203.Rz1c8A.rst b/Misc/NEWS.d/next/Library/2024-10-13-20-21-35.gh-issue-53203.Rz1c8A.rst deleted file mode 100644 index cdfa8c191e8242..00000000000000 --- a/Misc/NEWS.d/next/Library/2024-10-13-20-21-35.gh-issue-53203.Rz1c8A.rst +++ /dev/null @@ -1,2 +0,0 @@ -Fix :func:`time.strptime` for ``%c``, ``%x`` and ``%X`` formats in many -locales that use non-ASCII digits, like Persian, Burmese, Odia and Shan. diff --git a/Misc/NEWS.d/next/Library/2024-10-14-02-27-03.gh-issue-100141.NuAcwa.rst b/Misc/NEWS.d/next/Library/2024-10-14-02-27-03.gh-issue-100141.NuAcwa.rst deleted file mode 100644 index c366b0ad4040d3..00000000000000 --- a/Misc/NEWS.d/next/Library/2024-10-14-02-27-03.gh-issue-100141.NuAcwa.rst +++ /dev/null @@ -1 +0,0 @@ -Fixed the bug where :mod:`pdb` will be stuck in an infinite loop when debugging an empty file. diff --git a/Misc/NEWS.d/next/Library/2024-10-14-04-44-12.gh-issue-125422.MlVuC6.rst b/Misc/NEWS.d/next/Library/2024-10-14-04-44-12.gh-issue-125422.MlVuC6.rst deleted file mode 100644 index c890ecec8beaf8..00000000000000 --- a/Misc/NEWS.d/next/Library/2024-10-14-04-44-12.gh-issue-125422.MlVuC6.rst +++ /dev/null @@ -1 +0,0 @@ -Fixed the bug where :mod:`pdb` and :mod:`bdb` can step into the bottom caller frame. diff --git a/Misc/NEWS.d/next/Library/2024-10-14-17-29-34.gh-issue-125451.fmP3T9.rst b/Misc/NEWS.d/next/Library/2024-10-14-17-29-34.gh-issue-125451.fmP3T9.rst deleted file mode 100644 index 589988d4d6273f..00000000000000 --- a/Misc/NEWS.d/next/Library/2024-10-14-17-29-34.gh-issue-125451.fmP3T9.rst +++ /dev/null @@ -1,2 +0,0 @@ -Fix deadlock when :class:`concurrent.futures.ProcessPoolExecutor` shuts down -concurrently with an error when feeding a job to a worker process. diff --git a/Misc/NEWS.d/next/Library/2024-10-15-14-01-03.gh-issue-125519.TqGh6a.rst b/Misc/NEWS.d/next/Library/2024-10-15-14-01-03.gh-issue-125519.TqGh6a.rst deleted file mode 100644 index e6062625104590..00000000000000 --- a/Misc/NEWS.d/next/Library/2024-10-15-14-01-03.gh-issue-125519.TqGh6a.rst +++ /dev/null @@ -1,2 +0,0 @@ -Improve traceback if :func:`importlib.reload` is called with an object that -is not a module. Patch by Alex Waygood. diff --git a/Misc/NEWS.d/next/Library/2024-10-17-16-10-29.gh-issue-125259.oMew0c.rst b/Misc/NEWS.d/next/Library/2024-10-17-16-10-29.gh-issue-125259.oMew0c.rst deleted file mode 100644 index 4fa6330abea512..00000000000000 --- a/Misc/NEWS.d/next/Library/2024-10-17-16-10-29.gh-issue-125259.oMew0c.rst +++ /dev/null @@ -1 +0,0 @@ -Fix the notes removal logic for errors thrown in enum initialization. diff --git a/Misc/NEWS.d/next/Library/2024-10-18-08-58-10.gh-issue-125660.sDdDqO.rst b/Misc/NEWS.d/next/Library/2024-10-18-08-58-10.gh-issue-125660.sDdDqO.rst deleted file mode 100644 index 74d76c7bddae7d..00000000000000 --- a/Misc/NEWS.d/next/Library/2024-10-18-08-58-10.gh-issue-125660.sDdDqO.rst +++ /dev/null @@ -1 +0,0 @@ -Reject invalid unicode escapes for Python implementation of :func:`json.loads`. diff --git a/Misc/NEWS.d/next/Library/2024-10-18-09-51-29.gh-issue-125682.vsj4cU.rst b/Misc/NEWS.d/next/Library/2024-10-18-09-51-29.gh-issue-125682.vsj4cU.rst deleted file mode 100644 index 3eb2905ad8d810..00000000000000 --- a/Misc/NEWS.d/next/Library/2024-10-18-09-51-29.gh-issue-125682.vsj4cU.rst +++ /dev/null @@ -1,2 +0,0 @@ -Reject non-ASCII digits in the Python implementation of :func:`json.loads` -conforming to the JSON specification. diff --git a/Misc/NEWS.d/next/Library/2024-10-22-13-28-00.gh-issue-125355.zssHm_.rst b/Misc/NEWS.d/next/Library/2024-10-22-13-28-00.gh-issue-125355.zssHm_.rst deleted file mode 100644 index fd67f697641d92..00000000000000 --- a/Misc/NEWS.d/next/Library/2024-10-22-13-28-00.gh-issue-125355.zssHm_.rst +++ /dev/null @@ -1,7 +0,0 @@ -Fix several bugs in :meth:`argparse.ArgumentParser.parse_intermixed_args`. - -* The parser no longer changes temporarily during parsing. -* Default values are not processed twice. -* Required mutually exclusive groups containing positional arguments are now supported. -* The missing arguments report now includes the names of all required optional and positional arguments. -* Unknown options can be intermixed with positional arguments in parse_known_intermixed_args(). diff --git a/Misc/NEWS.d/next/Library/2024-10-23-17-45-40.gh-issue-125884.41E_PD.rst b/Misc/NEWS.d/next/Library/2024-10-23-17-45-40.gh-issue-125884.41E_PD.rst deleted file mode 100644 index 684b1f282b143e..00000000000000 --- a/Misc/NEWS.d/next/Library/2024-10-23-17-45-40.gh-issue-125884.41E_PD.rst +++ /dev/null @@ -1 +0,0 @@ -Fixed the bug for :mod:`pdb` where it can't set breakpoints on functions with certain annotations. diff --git a/Misc/NEWS.d/next/Library/2024-10-24-10-49-47.gh-issue-124452.eqTRgx.rst b/Misc/NEWS.d/next/Library/2024-10-24-10-49-47.gh-issue-124452.eqTRgx.rst deleted file mode 100644 index b0d63794022db4..00000000000000 --- a/Misc/NEWS.d/next/Library/2024-10-24-10-49-47.gh-issue-124452.eqTRgx.rst +++ /dev/null @@ -1,4 +0,0 @@ -Fix an issue in :meth:`email.policy.EmailPolicy.header_source_parse` and -:meth:`email.policy.Compat32.header_source_parse` that introduced spurious -leading whitespaces into header values when the header includes a newline -character after the header name delimiter (``:``) and before the value. diff --git a/Misc/NEWS.d/next/Library/2024-10-24-14-08-10.gh-issue-125789.eaiAMw.rst b/Misc/NEWS.d/next/Library/2024-10-24-14-08-10.gh-issue-125789.eaiAMw.rst deleted file mode 100644 index 964a006bb47b7b..00000000000000 --- a/Misc/NEWS.d/next/Library/2024-10-24-14-08-10.gh-issue-125789.eaiAMw.rst +++ /dev/null @@ -1 +0,0 @@ -Fix possible crash when mutating list of callbacks returned by :attr:`!asyncio.Future._callbacks`. It now always returns a new copy in C implementation :mod:`!_asyncio`. Patch by Kumar Aditya. diff --git a/Misc/NEWS.d/next/Library/2024-10-25-10-53-56.gh-issue-125966.eOCYU_.rst b/Misc/NEWS.d/next/Library/2024-10-25-10-53-56.gh-issue-125966.eOCYU_.rst deleted file mode 100644 index 9fe8795de18003..00000000000000 --- a/Misc/NEWS.d/next/Library/2024-10-25-10-53-56.gh-issue-125966.eOCYU_.rst +++ /dev/null @@ -1,2 +0,0 @@ -Fix a use-after-free crash in :meth:`asyncio.Future.remove_done_callback`. -Patch by Bénédikt Tran. diff --git a/Misc/NEWS.d/next/Library/2024-10-25-11-13-24.gh-issue-125969.YvbrTr.rst b/Misc/NEWS.d/next/Library/2024-10-25-11-13-24.gh-issue-125969.YvbrTr.rst deleted file mode 100644 index dc99adff7416c5..00000000000000 --- a/Misc/NEWS.d/next/Library/2024-10-25-11-13-24.gh-issue-125969.YvbrTr.rst +++ /dev/null @@ -1,2 +0,0 @@ -Fix an out-of-bounds crash when an evil :meth:`asyncio.loop.call_soon` -mutates the length of the internal callbacks list. Patch by Bénédikt Tran. diff --git a/Misc/NEWS.d/next/Library/2024-10-26-12-50-48.gh-issue-125984.d4vp5_.rst b/Misc/NEWS.d/next/Library/2024-10-26-12-50-48.gh-issue-125984.d4vp5_.rst deleted file mode 100644 index 7a1d7b53b11301..00000000000000 --- a/Misc/NEWS.d/next/Library/2024-10-26-12-50-48.gh-issue-125984.d4vp5_.rst +++ /dev/null @@ -1,3 +0,0 @@ -Fix use-after-free crashes on :class:`asyncio.Future` objects for which the -underlying event loop implements an evil :meth:`~object.__getattribute__`. -Reported by Nico-Posada. Patch by Bénédikt Tran. diff --git a/Misc/NEWS.d/next/Library/2024-10-28-22-35-22.gh-issue-126083.TuI--n.rst b/Misc/NEWS.d/next/Library/2024-10-28-22-35-22.gh-issue-126083.TuI--n.rst deleted file mode 100644 index d64b7dd2fedbd6..00000000000000 --- a/Misc/NEWS.d/next/Library/2024-10-28-22-35-22.gh-issue-126083.TuI--n.rst +++ /dev/null @@ -1 +0,0 @@ -Fixed a reference leak in :class:`asyncio.Task` objects when reinitializing the same object with a non-``None`` context. Patch by Nico Posada. diff --git a/Misc/NEWS.d/next/Library/2024-10-29-10-38-28.gh-issue-126080.qKRBuo.rst b/Misc/NEWS.d/next/Library/2024-10-29-10-38-28.gh-issue-126080.qKRBuo.rst deleted file mode 100644 index e54ac17b217c92..00000000000000 --- a/Misc/NEWS.d/next/Library/2024-10-29-10-38-28.gh-issue-126080.qKRBuo.rst +++ /dev/null @@ -1,3 +0,0 @@ -Fix a use-after-free crash on :class:`asyncio.Task` objects for which the -underlying event loop implements an evil :meth:`~object.__getattribute__`. -Reported by Nico-Posada. Patch by Bénédikt Tran. diff --git a/Misc/NEWS.d/next/Library/2024-10-29-10-58-52.gh-issue-126106.rlF798.rst b/Misc/NEWS.d/next/Library/2024-10-29-10-58-52.gh-issue-126106.rlF798.rst deleted file mode 100644 index de989007b4c35a..00000000000000 --- a/Misc/NEWS.d/next/Library/2024-10-29-10-58-52.gh-issue-126106.rlF798.rst +++ /dev/null @@ -1 +0,0 @@ -Fixes a possible ``NULL`` pointer dereference in :mod:`ssl`. diff --git a/Misc/NEWS.d/next/Library/2024-10-29-11-45-44.gh-issue-126105.cOL-R6.rst b/Misc/NEWS.d/next/Library/2024-10-29-11-45-44.gh-issue-126105.cOL-R6.rst deleted file mode 100644 index 547eb3af1ca064..00000000000000 --- a/Misc/NEWS.d/next/Library/2024-10-29-11-45-44.gh-issue-126105.cOL-R6.rst +++ /dev/null @@ -1 +0,0 @@ -Fix a crash in :mod:`ast` when the :attr:`ast.AST._fields` attribute is deleted. diff --git a/Misc/NEWS.d/next/Library/2024-10-30-20-45-17.gh-issue-126205.CHEmtx.rst b/Misc/NEWS.d/next/Library/2024-10-30-20-45-17.gh-issue-126205.CHEmtx.rst deleted file mode 100644 index c92ffb75056606..00000000000000 --- a/Misc/NEWS.d/next/Library/2024-10-30-20-45-17.gh-issue-126205.CHEmtx.rst +++ /dev/null @@ -1,2 +0,0 @@ -Fix issue where :func:`urllib.request.pathname2url` generated URLs beginning -with four slashes (rather than two) when given a Windows UNC path. diff --git a/Misc/NEWS.d/next/Library/2024-10-30-23-59-36.gh-issue-126212._9uYjT.rst b/Misc/NEWS.d/next/Library/2024-10-30-23-59-36.gh-issue-126212._9uYjT.rst deleted file mode 100644 index 047fe0f68048b5..00000000000000 --- a/Misc/NEWS.d/next/Library/2024-10-30-23-59-36.gh-issue-126212._9uYjT.rst +++ /dev/null @@ -1,3 +0,0 @@ -Fix issue where :func:`urllib.request.pathname2url` and -:func:`~urllib.request.url2pathname` removed slashes from Windows DOS drive -paths and URLs. diff --git a/Misc/NEWS.d/next/Library/2024-10-31-14-06-28.gh-issue-126220.uJAJCU.rst b/Misc/NEWS.d/next/Library/2024-10-31-14-06-28.gh-issue-126220.uJAJCU.rst deleted file mode 100644 index 555f2f3bafbf33..00000000000000 --- a/Misc/NEWS.d/next/Library/2024-10-31-14-06-28.gh-issue-126220.uJAJCU.rst +++ /dev/null @@ -1,2 +0,0 @@ -Fix crash in :class:`!cProfile.Profile` and :class:`!_lsprof.Profiler` when their -callbacks were directly called with 0 arguments. diff --git a/Misc/NEWS.d/next/Library/2024-11-01-14-31-41.gh-issue-126138.yTniOG.rst b/Misc/NEWS.d/next/Library/2024-11-01-14-31-41.gh-issue-126138.yTniOG.rst deleted file mode 100644 index 459eebc82bd42a..00000000000000 --- a/Misc/NEWS.d/next/Library/2024-11-01-14-31-41.gh-issue-126138.yTniOG.rst +++ /dev/null @@ -1,3 +0,0 @@ -Fix a use-after-free crash on :class:`asyncio.Task` objects -whose underlying coroutine yields an object that implements -an evil :meth:`~object.__getattribute__`. Patch by Nico Posada. diff --git a/Misc/NEWS.d/next/Library/2024-11-02-19-20-44.gh-issue-126303.yVvyWB.rst b/Misc/NEWS.d/next/Library/2024-11-02-19-20-44.gh-issue-126303.yVvyWB.rst deleted file mode 100644 index 0072c97338c251..00000000000000 --- a/Misc/NEWS.d/next/Library/2024-11-02-19-20-44.gh-issue-126303.yVvyWB.rst +++ /dev/null @@ -1 +0,0 @@ -Fix pickling and copying of :class:`os.sched_param` objects. diff --git a/Misc/NEWS.d/next/Library/2024-11-06-13-41-38.gh-issue-126489.toaf-0.rst b/Misc/NEWS.d/next/Library/2024-11-06-13-41-38.gh-issue-126489.toaf-0.rst deleted file mode 100644 index 8a6573cdea7b42..00000000000000 --- a/Misc/NEWS.d/next/Library/2024-11-06-13-41-38.gh-issue-126489.toaf-0.rst +++ /dev/null @@ -1,3 +0,0 @@ -The Python implementation of :mod:`pickle` no longer calls -:meth:`pickle.Pickler.persistent_id` for the result of -:meth:`!persistent_id`. diff --git a/Misc/NEWS.d/next/Library/2024-11-06-18-30-50.gh-issue-126476.F1wh3c.rst b/Misc/NEWS.d/next/Library/2024-11-06-18-30-50.gh-issue-126476.F1wh3c.rst deleted file mode 100644 index f558c29e8b087f..00000000000000 --- a/Misc/NEWS.d/next/Library/2024-11-06-18-30-50.gh-issue-126476.F1wh3c.rst +++ /dev/null @@ -1,2 +0,0 @@ -Raise :class:`calendar.IllegalMonthError` (now a subclass of :class:`IndexError`) for :func:`calendar.month` -when the input month is not correct. diff --git a/Misc/NEWS.d/next/Library/2024-11-06-23-40-28.gh-issue-125679.Qq9xF5.rst b/Misc/NEWS.d/next/Library/2024-11-06-23-40-28.gh-issue-125679.Qq9xF5.rst deleted file mode 100644 index ac6851e2689692..00000000000000 --- a/Misc/NEWS.d/next/Library/2024-11-06-23-40-28.gh-issue-125679.Qq9xF5.rst +++ /dev/null @@ -1,2 +0,0 @@ -The :class:`multiprocessing.Lock` and :class:`multiprocessing.RLock` -``repr`` values no longer say "unknown" on macOS. diff --git a/Misc/NEWS.d/next/Library/2024-11-07-01-40-11.gh-issue-117378.o9O5uM.rst b/Misc/NEWS.d/next/Library/2024-11-07-01-40-11.gh-issue-117378.o9O5uM.rst deleted file mode 100644 index d7d4477ec17814..00000000000000 --- a/Misc/NEWS.d/next/Library/2024-11-07-01-40-11.gh-issue-117378.o9O5uM.rst +++ /dev/null @@ -1,17 +0,0 @@ -Fixed the :mod:`multiprocessing` ``"forkserver"`` start method forkserver -process to correctly inherit the parent's :data:`sys.path` during the importing -of :func:`multiprocessing.set_forkserver_preload` modules in the same manner as -:data:`sys.path` is configured in workers before executing work items. - -This bug caused some forkserver module preloading to silently fail to preload. -This manifested as a performance degration in child processes when the -``sys.path`` was required due to additional repeated work in every worker. - -It could also have a side effect of ``""`` remaining in :data:`sys.path` during -forkserver preload imports instead of the absolute path from :func:`os.getcwd` -at multiprocessing import time used in the worker ``sys.path``. - -The ``sys.path`` differences between phases in the child process could -potentially have caused preload to import incorrect things from the wrong -location. We are unaware of that actually having happened in practice. - diff --git a/Misc/NEWS.d/next/Library/2024-11-07-22-41-47.gh-issue-126505.iztYE1.rst b/Misc/NEWS.d/next/Library/2024-11-07-22-41-47.gh-issue-126505.iztYE1.rst deleted file mode 100644 index 0a0f893a2688a0..00000000000000 --- a/Misc/NEWS.d/next/Library/2024-11-07-22-41-47.gh-issue-126505.iztYE1.rst +++ /dev/null @@ -1,4 +0,0 @@ -Fix bugs in compiling case-insensitive :mod:`regular expressions ` with -character classes containing non-BMP characters: upper-case non-BMP -character did was ignored and the ASCII flag was ignored when -matching a character range whose upper bound is beyond the BMP region. diff --git a/Misc/NEWS.d/next/Library/2024-11-08-11-06-14.gh-issue-126565.dFFO22.rst b/Misc/NEWS.d/next/Library/2024-11-08-11-06-14.gh-issue-126565.dFFO22.rst deleted file mode 100644 index 22858570bbe03c..00000000000000 --- a/Misc/NEWS.d/next/Library/2024-11-08-11-06-14.gh-issue-126565.dFFO22.rst +++ /dev/null @@ -1 +0,0 @@ -Improve performances of :meth:`zipfile.Path.open` for non-reading modes. diff --git a/Misc/NEWS.d/next/Library/2024-11-08-17-05-10.gh-issue-120423.7rdLVV.rst b/Misc/NEWS.d/next/Library/2024-11-08-17-05-10.gh-issue-120423.7rdLVV.rst deleted file mode 100644 index b475257ceb6610..00000000000000 --- a/Misc/NEWS.d/next/Library/2024-11-08-17-05-10.gh-issue-120423.7rdLVV.rst +++ /dev/null @@ -1,2 +0,0 @@ -Fix issue where :func:`urllib.request.pathname2url` mishandled Windows paths -with embedded forward slashes. diff --git a/Misc/NEWS.d/next/Library/2024-11-09-10-31-10.gh-issue-126595.A-7MyC.rst b/Misc/NEWS.d/next/Library/2024-11-09-10-31-10.gh-issue-126595.A-7MyC.rst deleted file mode 100644 index 84a5dc0b23922f..00000000000000 --- a/Misc/NEWS.d/next/Library/2024-11-09-10-31-10.gh-issue-126595.A-7MyC.rst +++ /dev/null @@ -1,2 +0,0 @@ -Fix a crash when instantiating :class:`itertools.count` with an initial -count of :data:`sys.maxsize` on debug builds. Patch by Bénédikt Tran. diff --git a/Misc/NEWS.d/next/Library/2024-11-10-18-14-51.gh-issue-104745.zAa5Ke.rst b/Misc/NEWS.d/next/Library/2024-11-10-18-14-51.gh-issue-104745.zAa5Ke.rst deleted file mode 100644 index c83a10769820cf..00000000000000 --- a/Misc/NEWS.d/next/Library/2024-11-10-18-14-51.gh-issue-104745.zAa5Ke.rst +++ /dev/null @@ -1,3 +0,0 @@ -Limit starting a patcher (from :func:`unittest.mock.patch` or -:func:`unittest.mock.patch.object`) more than -once without stopping it diff --git a/Misc/NEWS.d/next/Library/2024-11-11-13-24-22.gh-issue-126699.ONGbMd.rst b/Misc/NEWS.d/next/Library/2024-11-11-13-24-22.gh-issue-126699.ONGbMd.rst deleted file mode 100644 index 9741294487d716..00000000000000 --- a/Misc/NEWS.d/next/Library/2024-11-11-13-24-22.gh-issue-126699.ONGbMd.rst +++ /dev/null @@ -1 +0,0 @@ -Allow :class:`collections.abc.AsyncIterator` to be a base for Protocols. diff --git a/Misc/NEWS.d/next/Library/2024-11-12-13-14-47.gh-issue-126727.5Eqfqd.rst b/Misc/NEWS.d/next/Library/2024-11-12-13-14-47.gh-issue-126727.5Eqfqd.rst deleted file mode 100644 index 7bec8a6b7a830a..00000000000000 --- a/Misc/NEWS.d/next/Library/2024-11-12-13-14-47.gh-issue-126727.5Eqfqd.rst +++ /dev/null @@ -1,3 +0,0 @@ -``locale.nl_langinfo(locale.ERA)`` now returns multiple era description -segments separated by semicolons. Previously it only returned the first -segment on platforms with Glibc. diff --git a/Misc/NEWS.d/next/Library/2024-11-12-21-43-12.gh-issue-126766.oi2KJ7.rst b/Misc/NEWS.d/next/Library/2024-11-12-21-43-12.gh-issue-126766.oi2KJ7.rst deleted file mode 100644 index e3936305164883..00000000000000 --- a/Misc/NEWS.d/next/Library/2024-11-12-21-43-12.gh-issue-126766.oi2KJ7.rst +++ /dev/null @@ -1,2 +0,0 @@ -Fix issue where :func:`urllib.request.url2pathname` failed to discard two -leading slashes introducing an empty authority section. diff --git a/Misc/NEWS.d/next/Library/2024-11-13-20-03-18.gh-issue-126188.RJLKk-.rst b/Misc/NEWS.d/next/Library/2024-11-13-20-03-18.gh-issue-126188.RJLKk-.rst deleted file mode 100644 index bb13662e6ae62c..00000000000000 --- a/Misc/NEWS.d/next/Library/2024-11-13-20-03-18.gh-issue-126188.RJLKk-.rst +++ /dev/null @@ -1 +0,0 @@ -Update bundled pip to 24.3.1 diff --git a/Misc/NEWS.d/next/Library/2024-11-13-22-25-57.gh-issue-126789.lKzlc7.rst b/Misc/NEWS.d/next/Library/2024-11-13-22-25-57.gh-issue-126789.lKzlc7.rst deleted file mode 100644 index 09d4d2e5ab9037..00000000000000 --- a/Misc/NEWS.d/next/Library/2024-11-13-22-25-57.gh-issue-126789.lKzlc7.rst +++ /dev/null @@ -1,4 +0,0 @@ -Fixed the values of :py:func:`sysconfig.get_config_vars`, -:py:func:`sysconfig.get_paths`, and their siblings when the :py:mod:`site` -initialization happens after :py:mod:`sysconfig` has built a cache for -:py:func:`sysconfig.get_config_vars`. diff --git a/Misc/NEWS.d/next/Library/2024-11-14-22-25-49.gh-issue-67877.G9hw0w.rst b/Misc/NEWS.d/next/Library/2024-11-14-22-25-49.gh-issue-67877.G9hw0w.rst deleted file mode 100644 index 021b4ae2e100bc..00000000000000 --- a/Misc/NEWS.d/next/Library/2024-11-14-22-25-49.gh-issue-67877.G9hw0w.rst +++ /dev/null @@ -1,2 +0,0 @@ -Fix memory leaks when :mod:`regular expression ` matching terminates -abruptly, either because of a signal or because memory allocation fails. diff --git a/Misc/NEWS.d/next/Library/2024-11-15-01-50-36.gh-issue-85168.bP8VIN.rst b/Misc/NEWS.d/next/Library/2024-11-15-01-50-36.gh-issue-85168.bP8VIN.rst deleted file mode 100644 index abceda8f6fd707..00000000000000 --- a/Misc/NEWS.d/next/Library/2024-11-15-01-50-36.gh-issue-85168.bP8VIN.rst +++ /dev/null @@ -1,4 +0,0 @@ -Fix issue where :func:`urllib.request.url2pathname` and -:func:`~urllib.request.pathname2url` always used UTF-8 when quoting and -unquoting file URIs. They now use the :term:`filesystem encoding and error -handler`. diff --git a/Misc/NEWS.d/next/Library/2024-11-20-08-54-11.gh-issue-126618.ef_53g.rst b/Misc/NEWS.d/next/Library/2024-11-20-08-54-11.gh-issue-126618.ef_53g.rst deleted file mode 100644 index 7a0a7b7517b70d..00000000000000 --- a/Misc/NEWS.d/next/Library/2024-11-20-08-54-11.gh-issue-126618.ef_53g.rst +++ /dev/null @@ -1,2 +0,0 @@ -Fix the representation of :class:`itertools.count` objects when the count -value is :data:`sys.maxsize`. diff --git a/Misc/NEWS.d/next/Library/2024-11-20-16-58-59.gh-issue-126997.0PI41Y.rst b/Misc/NEWS.d/next/Library/2024-11-20-16-58-59.gh-issue-126997.0PI41Y.rst deleted file mode 100644 index b85c51ef07dcbe..00000000000000 --- a/Misc/NEWS.d/next/Library/2024-11-20-16-58-59.gh-issue-126997.0PI41Y.rst +++ /dev/null @@ -1,3 +0,0 @@ -Fix support of STRING and GLOBAL opcodes with non-ASCII arguments in -:mod:`pickletools`. :func:`pickletools.dis` now outputs non-ASCII bytes in -STRING, BINSTRING and SHORT_BINSTRING arguments as escaped (``\xXX``). diff --git a/Misc/NEWS.d/next/Library/2024-11-22-02-31-55.gh-issue-126766.jfkhBH.rst b/Misc/NEWS.d/next/Library/2024-11-22-02-31-55.gh-issue-126766.jfkhBH.rst deleted file mode 100644 index 998c99bf4358d5..00000000000000 --- a/Misc/NEWS.d/next/Library/2024-11-22-02-31-55.gh-issue-126766.jfkhBH.rst +++ /dev/null @@ -1,2 +0,0 @@ -Fix issue where :func:`urllib.request.url2pathname` failed to discard any -'localhost' authority present in the URL. diff --git a/Misc/NEWS.d/next/Library/2024-11-22-03-40-02.gh-issue-127078.gI_PaP.rst b/Misc/NEWS.d/next/Library/2024-11-22-03-40-02.gh-issue-127078.gI_PaP.rst deleted file mode 100644 index a84c06f3c7a273..00000000000000 --- a/Misc/NEWS.d/next/Library/2024-11-22-03-40-02.gh-issue-127078.gI_PaP.rst +++ /dev/null @@ -1,2 +0,0 @@ -Fix issue where :func:`urllib.request.url2pathname` failed to discard an -extra slash before a UNC drive in the URL path on Windows. diff --git a/Misc/NEWS.d/next/Library/2024-11-22-10-42-34.gh-issue-127035.UnbDlr.rst b/Misc/NEWS.d/next/Library/2024-11-22-10-42-34.gh-issue-127035.UnbDlr.rst deleted file mode 100644 index 6bb7abfdd50040..00000000000000 --- a/Misc/NEWS.d/next/Library/2024-11-22-10-42-34.gh-issue-127035.UnbDlr.rst +++ /dev/null @@ -1,4 +0,0 @@ -Fix :mod:`shutil.which` on Windows. Now it looks at direct match if and only -if the command ends with a PATHEXT extension or X_OK is not in mode. Support -extensionless files if "." is in PATHEXT. Support PATHEXT extensions that end -with a dot. diff --git a/Misc/NEWS.d/next/Library/2024-11-24-12-41-31.gh-issue-127217.UAXGFr.rst b/Misc/NEWS.d/next/Library/2024-11-24-12-41-31.gh-issue-127217.UAXGFr.rst deleted file mode 100644 index 3139e33302f378..00000000000000 --- a/Misc/NEWS.d/next/Library/2024-11-24-12-41-31.gh-issue-127217.UAXGFr.rst +++ /dev/null @@ -1,2 +0,0 @@ -Fix :func:`urllib.request.pathname2url` for paths starting with multiple -slashes on Posix. diff --git a/Misc/NEWS.d/next/Library/2024-11-24-14-20-17.gh-issue-127182.WmfY2g.rst b/Misc/NEWS.d/next/Library/2024-11-24-14-20-17.gh-issue-127182.WmfY2g.rst deleted file mode 100644 index 2cc46ca3d33977..00000000000000 --- a/Misc/NEWS.d/next/Library/2024-11-24-14-20-17.gh-issue-127182.WmfY2g.rst +++ /dev/null @@ -1,2 +0,0 @@ -Fix :meth:`!io.StringIO.__setstate__` crash, when :const:`None` was passed as -the first value. diff --git a/Misc/NEWS.d/next/Library/2024-11-27-14-06-35.gh-issue-123967.wxUmnW.rst b/Misc/NEWS.d/next/Library/2024-11-27-14-06-35.gh-issue-123967.wxUmnW.rst deleted file mode 100644 index 788fe0c78ef257..00000000000000 --- a/Misc/NEWS.d/next/Library/2024-11-27-14-06-35.gh-issue-123967.wxUmnW.rst +++ /dev/null @@ -1,2 +0,0 @@ -Fix faulthandler for trampoline frames. If the top-most frame is a -trampoline frame, skip it. Patch by Victor Stinner. diff --git a/Misc/NEWS.d/next/Library/2024-11-27-16-06-10.gh-issue-127303.asqkgh.rst b/Misc/NEWS.d/next/Library/2024-11-27-16-06-10.gh-issue-127303.asqkgh.rst deleted file mode 100644 index 58ebf5d0abe141..00000000000000 --- a/Misc/NEWS.d/next/Library/2024-11-27-16-06-10.gh-issue-127303.asqkgh.rst +++ /dev/null @@ -1 +0,0 @@ -Publicly expose :data:`~token.EXACT_TOKEN_TYPES` in :attr:`!token.__all__`. diff --git a/Misc/NEWS.d/next/Security/2024-11-13-11-09-12.gh-issue-126623.TO7NnR.rst b/Misc/NEWS.d/next/Security/2024-11-13-11-09-12.gh-issue-126623.TO7NnR.rst deleted file mode 100644 index f09a158af2a475..00000000000000 --- a/Misc/NEWS.d/next/Security/2024-11-13-11-09-12.gh-issue-126623.TO7NnR.rst +++ /dev/null @@ -1 +0,0 @@ -Upgrade libexpat to 2.6.4 diff --git a/Misc/NEWS.d/next/Tests/2024-09-30-22-52-44.gh-issue-124295.VZy5kx.rst b/Misc/NEWS.d/next/Tests/2024-09-30-22-52-44.gh-issue-124295.VZy5kx.rst deleted file mode 100644 index 3c2455cfc8c530..00000000000000 --- a/Misc/NEWS.d/next/Tests/2024-09-30-22-52-44.gh-issue-124295.VZy5kx.rst +++ /dev/null @@ -1 +0,0 @@ -Add translation tests to the :mod:`argparse` module. diff --git a/Misc/NEWS.d/next/Tests/2024-10-07-14-13-38.gh-issue-125041.PKLWDf.rst b/Misc/NEWS.d/next/Tests/2024-10-07-14-13-38.gh-issue-125041.PKLWDf.rst deleted file mode 100644 index c7181eb9c1f3a9..00000000000000 --- a/Misc/NEWS.d/next/Tests/2024-10-07-14-13-38.gh-issue-125041.PKLWDf.rst +++ /dev/null @@ -1,3 +0,0 @@ -Re-enable skipped tests for :mod:`zlib` on the s390x architecture: only skip -checks of the compressed bytes, which can be different between zlib's -software implementation and the hardware-accelerated implementation. diff --git a/Misc/NEWS.d/next/Tests/2024-11-17-16-56-48.gh-issue-126909.60VTxW.rst b/Misc/NEWS.d/next/Tests/2024-11-17-16-56-48.gh-issue-126909.60VTxW.rst deleted file mode 100644 index 68bd9ac70cd1f4..00000000000000 --- a/Misc/NEWS.d/next/Tests/2024-11-17-16-56-48.gh-issue-126909.60VTxW.rst +++ /dev/null @@ -1,2 +0,0 @@ -Fix test_os extended attribute tests to work on filesystems with 1 KiB xattr size -limit. diff --git a/Misc/NEWS.d/next/Tools-Demos/2024-11-13-22-23-36.gh-issue-126807.vpaWuN.rst b/Misc/NEWS.d/next/Tools-Demos/2024-11-13-22-23-36.gh-issue-126807.vpaWuN.rst deleted file mode 100644 index 310286ce8319ea..00000000000000 --- a/Misc/NEWS.d/next/Tools-Demos/2024-11-13-22-23-36.gh-issue-126807.vpaWuN.rst +++ /dev/null @@ -1,2 +0,0 @@ -Fix extraction warnings in :program:`pygettext.py` caused by mistaking -function definitions for function calls. diff --git a/Misc/NEWS.d/next/Windows/2024-09-24-19-04-56.gh-issue-124448.srVT3d.rst b/Misc/NEWS.d/next/Windows/2024-09-24-19-04-56.gh-issue-124448.srVT3d.rst deleted file mode 100644 index ca9845a8daea9d..00000000000000 --- a/Misc/NEWS.d/next/Windows/2024-09-24-19-04-56.gh-issue-124448.srVT3d.rst +++ /dev/null @@ -1 +0,0 @@ -Updated bundled Tcl/Tk to 8.6.15. diff --git a/Misc/NEWS.d/next/Windows/2024-10-15-21-28-43.gh-issue-125550.hmGWCP.rst b/Misc/NEWS.d/next/Windows/2024-10-15-21-28-43.gh-issue-125550.hmGWCP.rst deleted file mode 100644 index c3ae00c74b3d91..00000000000000 --- a/Misc/NEWS.d/next/Windows/2024-10-15-21-28-43.gh-issue-125550.hmGWCP.rst +++ /dev/null @@ -1,2 +0,0 @@ -Enable the :ref:`launcher` to detect Python 3.14 installs from the Windows -Store. diff --git a/Misc/NEWS.d/next/Windows/2024-10-29-19-48-03.gh-issue-125315.jdB9qN.rst b/Misc/NEWS.d/next/Windows/2024-10-29-19-48-03.gh-issue-125315.jdB9qN.rst deleted file mode 100644 index 3d813248766a5b..00000000000000 --- a/Misc/NEWS.d/next/Windows/2024-10-29-19-48-03.gh-issue-125315.jdB9qN.rst +++ /dev/null @@ -1,2 +0,0 @@ -Avoid crashing in :mod:`platform` due to slow WMI calls on some Windows -machines. diff --git a/Misc/NEWS.d/next/Windows/2024-11-16-22-08-41.gh-issue-126911.HchCZZ.rst b/Misc/NEWS.d/next/Windows/2024-11-16-22-08-41.gh-issue-126911.HchCZZ.rst deleted file mode 100644 index 32481cde930fcd..00000000000000 --- a/Misc/NEWS.d/next/Windows/2024-11-16-22-08-41.gh-issue-126911.HchCZZ.rst +++ /dev/null @@ -1 +0,0 @@ -Update credits command output. diff --git a/Misc/NEWS.d/next/macOS/2024-09-24-10-48-46.gh-issue-124448.bFMrS6.rst b/Misc/NEWS.d/next/macOS/2024-09-24-10-48-46.gh-issue-124448.bFMrS6.rst deleted file mode 100644 index 6d57aa1ee190d6..00000000000000 --- a/Misc/NEWS.d/next/macOS/2024-09-24-10-48-46.gh-issue-124448.bFMrS6.rst +++ /dev/null @@ -1 +0,0 @@ -Update bundled Tcl/Tk in macOS installer to 8.6.15. diff --git a/README.rst b/README.rst index 0e8da6088322bd..c6dc40b17fe2f3 100644 --- a/README.rst +++ b/README.rst @@ -1,4 +1,4 @@ -This is Python version 3.12.7 +This is Python version 3.12.8 ============================= .. image:: https://github.com/python/cpython/workflows/Tests/badge.svg