Skip to content

Commit 0ded62f

Browse files
authored
Merge branch 'main' into deadcode/importlib-object-name
2 parents e3a8519 + e833f57 commit 0ded62f

31 files changed

Lines changed: 709 additions & 371 deletions

Doc/c-api/complex.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ rather than dereferencing them through pointers.
130130
131131
Please note, that these functions are :term:`soft deprecated` since Python
132132
3.15. Avoid using this API in a new code to do complex arithmetic: either use
133-
the `Number Protocol <number>`_ API or use native complex types, like
133+
the :ref:`Number Protocol <number>` API or use native complex types, like
134134
:c:expr:`double complex`.
135135
136136

Doc/extending/first-extension-module.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ Then, create ``meson.build`` containing the following:
164164
165165
.. note::
166166

167-
See `meson-python documentation <meson-python>`_ for details on
167+
See the `meson-python documentation <meson-python_>`_ for details on
168168
configuration.
169169

170170
Now, build install the *project in the current directory* (``.``) via ``pip``:

Doc/library/shlex.rst

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,15 @@ The :mod:`!shlex` module defines the following functions:
4444
.. versionadded:: 3.8
4545

4646

47-
.. function:: quote(s)
47+
.. function:: quote(s, *, force=False)
4848

4949
Return a shell-escaped version of the string *s*. The returned value is a
5050
string that can safely be used as one token in a shell command line, for
5151
cases where you cannot use a list.
5252

53+
If *force* is :const:`True`, then *s* is unconditionally quoted,
54+
even if it is already safe for a shell without being quoted.
55+
5356
.. _shlex-quote-warning:
5457

5558
.. warning::
@@ -91,8 +94,23 @@ The :mod:`!shlex` module defines the following functions:
9194
>>> command
9295
['ls', '-l', 'somefile; rm -rf ~']
9396

97+
The *force* keyword can be used to produce consistent behavior when
98+
escaping multiple strings:
99+
100+
>>> from shlex import quote
101+
>>> filenames = ['my first file', 'file2', 'file 3']
102+
>>> filenames_some_escaped = [quote(f) for f in filenames]
103+
>>> filenames_some_escaped
104+
["'my first file'", 'file2', "'file 3'"]
105+
>>> filenames_all_escaped = [quote(f, force=True) for f in filenames]
106+
>>> filenames_all_escaped
107+
["'my first file'", "'file2'", "'file 3'"]
108+
94109
.. versionadded:: 3.3
95110

111+
.. versionchanged:: next
112+
The *force* keyword was added.
113+
96114
The :mod:`!shlex` module defines the following class:
97115

98116

Doc/library/stdtypes.rst

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2616,7 +2616,9 @@ expression support in the :mod:`re` module).
26162616
:func:`re.split`). Splitting an empty string with a specified separator
26172617
returns ``['']``.
26182618

2619-
For example::
2619+
For example:
2620+
2621+
.. doctest::
26202622

26212623
>>> '1,2,3'.split(',')
26222624
['1', '2', '3']
@@ -2634,7 +2636,9 @@ expression support in the :mod:`re` module).
26342636
string or a string consisting of just whitespace with a ``None`` separator
26352637
returns ``[]``.
26362638

2637-
For example::
2639+
For example:
2640+
2641+
.. doctest::
26382642

26392643
>>> '1 2 3'.split()
26402644
['1', '2', '3']
@@ -2646,7 +2650,9 @@ expression support in the :mod:`re` module).
26462650
If *sep* is not specified or is ``None`` and *maxsplit* is ``0``, only
26472651
leading runs of consecutive whitespace are considered.
26482652

2649-
For example::
2653+
For example:
2654+
2655+
.. doctest::
26502656

26512657
>>> "".split(None, 0)
26522658
[]
@@ -2655,7 +2661,7 @@ expression support in the :mod:`re` module).
26552661
>>> " foo ".split(maxsplit=0)
26562662
['foo ']
26572663

2658-
See also :meth:`join`.
2664+
See also :meth:`join` and :meth:`rsplit`.
26592665

26602666

26612667
.. index::

Doc/whatsnew/3.16.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,13 @@ os
109109
process via a pidfd. Available on Linux 5.6+.
110110
(Contributed by Maurycy Pawłowski-Wieroński in :gh:`149464`.)
111111

112+
shlex
113+
-----
114+
115+
* Add keyword-only parameter *force* to :func:`shlex.quote` to force quoting
116+
a string, even if it is already safe for a shell without being quoted.
117+
(Contributed by Jay Berry in :gh:`148846`.)
118+
112119
xml
113120
---
114121

Lib/logging/__init__.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1373,9 +1373,16 @@ def getLogger(self, name):
13731373
logger and fix up the parent/child references which pointed to the
13741374
placeholder to now point to the logger.
13751375
"""
1376-
rv = None
13771376
if not isinstance(name, str):
13781377
raise TypeError('A logger name must be a string')
1378+
# Fast path: an already-registered, non-placeholder logger can be
1379+
# returned without taking the lock. dict.get() is atomic under both
1380+
# the GIL and free threading, and a Logger is fully initialised before
1381+
# being inserted into loggerDict under the lock, so this never sees a
1382+
# partially-constructed object.
1383+
rv = self.loggerDict.get(name)
1384+
if rv is not None and not isinstance(rv, PlaceHolder):
1385+
return rv
13791386
with _lock:
13801387
if name in self.loggerDict:
13811388
rv = self.loggerDict[name]

Lib/logging/handlers.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -282,7 +282,16 @@ def __init__(self, filename, when='h', interval=1, backupCount=0,
282282
# path object (see Issue #27493), but self.baseFilename will be a string
283283
filename = self.baseFilename
284284
if os.path.exists(filename):
285-
t = int(os.stat(filename).st_mtime)
285+
# Use the minimum of file creation and modification time as
286+
# the base of the rollover calculation
287+
stat_result = os.stat(filename)
288+
# Use st_birthtime whenever it is available or use st_ctime
289+
# instead otherwise
290+
try:
291+
creation_time = stat_result.st_birthtime
292+
except AttributeError:
293+
creation_time = stat_result.st_ctime
294+
t = int(min(creation_time, stat_result.st_mtime))
286295
else:
287296
t = int(time.time())
288297
self.rolloverAt = self.computeRollover(t)

Lib/shlex.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -317,8 +317,12 @@ def join(split_command):
317317
return ' '.join(quote(arg) for arg in split_command)
318318

319319

320-
def quote(s):
321-
"""Return a shell-escaped version of the string *s*."""
320+
def quote(s, *, force=False):
321+
"""Return a shell-escaped version of the string *s*.
322+
323+
If *force* is *True*, then *s* is unconditionally quoted,
324+
even if it is already safe for a shell without being quoted.
325+
"""
322326
if not s:
323327
return "''"
324328

@@ -329,8 +333,10 @@ def quote(s):
329333
safe_chars = (b'%+,-./0123456789:=@'
330334
b'ABCDEFGHIJKLMNOPQRSTUVWXYZ_'
331335
b'abcdefghijklmnopqrstuvwxyz')
332-
# No quoting is needed if `s` is an ASCII string consisting only of `safe_chars`
333-
if s.isascii() and not s.encode().translate(None, delete=safe_chars):
336+
# No quoting is needed if we are not forcing quoting
337+
# and `s` is an ASCII string consisting only of `safe_chars`.
338+
if (not force
339+
and s.isascii() and not s.encode().translate(None, delete=safe_chars)):
334340
return s
335341

336342
# use single quotes, and put single quotes into double quotes

Lib/test/support/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
"has_fork_support", "requires_fork",
4141
"has_subprocess_support", "requires_subprocess",
4242
"has_socket_support", "requires_working_socket",
43+
"has_st_birthtime",
4344
"has_remote_subprocess_debugging", "requires_remote_subprocess_debugging",
4445
"anticipate_failure", "load_package_tests", "detect_api_mismatch",
4546
"check__all__", "skip_if_buggy_ucrt_strfptime",
@@ -620,6 +621,10 @@ def skip_wasi_stack_overflow():
620621
or is_android
621622
)
622623

624+
# At the moment, st_birthtime attribute is only supported on Windows,
625+
# MacOS and FreeBSD.
626+
has_st_birthtime = sys.platform.startswith(("win", "freebsd", "darwin"))
627+
623628
def requires_fork():
624629
return unittest.skipUnless(has_fork_support, "requires working os.fork()")
625630

Lib/test/test_ast/test_ast.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -625,7 +625,7 @@ def test_invalid_identifier(self):
625625
ast.fix_missing_locations(m)
626626
with self.assertRaises(TypeError) as cm:
627627
compile(m, "<test>", "exec")
628-
self.assertIn("identifier must be of type str", str(cm.exception))
628+
self.assertIn("expecting a string object", str(cm.exception))
629629

630630
def test_invalid_constant(self):
631631
for invalid_constant in int, (1, 2, int), frozenset((1, 2, int)):
@@ -1081,6 +1081,30 @@ def test_none_checks(self) -> None:
10811081
for node, attr, source in tests:
10821082
self.assert_none_check(node, attr, source)
10831083

1084+
def test_required_field_messages(self):
1085+
binop = ast.BinOp(
1086+
left=ast.Constant(value=2),
1087+
right=ast.Constant(value=2),
1088+
op=ast.Add(),
1089+
)
1090+
expr_without_position = ast.Expression(body=binop)
1091+
expr_with_wrong_body = ast.Expression(body=[binop])
1092+
1093+
with self.assertRaisesRegex(TypeError, "required field") as cm:
1094+
compile(expr_without_position, "<test>", "eval")
1095+
with self.assertRaisesRegex(
1096+
TypeError,
1097+
"field 'body' was expecting node of type 'expr', got 'list'",
1098+
):
1099+
compile(expr_with_wrong_body, "<test>", "eval")
1100+
1101+
constant = ast.parse("u'test'", mode="eval")
1102+
constant.body.kind = 0xFF
1103+
with self.assertRaisesRegex(
1104+
TypeError, "field 'kind' was expecting a string or bytes object"
1105+
):
1106+
compile(constant, "<test>", "eval")
1107+
10841108
def test_repr(self) -> None:
10851109
snapshots = AST_REPR_DATA_FILE.read_text().split("\n")
10861110
for test, snapshot in zip(ast_repr_get_test_cases(), snapshots, strict=True):

0 commit comments

Comments
 (0)