Skip to content

Commit e634bda

Browse files
authored
Merge branch 'main' into 85045-io.TextIOBase.buffer-is-not-necessarily-a-buffer
2 parents 169c954 + b430e92 commit e634bda

File tree

13 files changed

+119
-47
lines changed

13 files changed

+119
-47
lines changed

Doc/library/datetime.rst

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1502,11 +1502,11 @@ Instance methods:
15021502
returned by :func:`time.time`.
15031503

15041504
Naive :class:`.datetime` instances are assumed to represent local
1505-
time and this method relies on the platform C :c:func:`mktime`
1506-
function to perform the conversion. Since :class:`.datetime`
1507-
supports wider range of values than :c:func:`mktime` on many
1508-
platforms, this method may raise :exc:`OverflowError` or :exc:`OSError`
1509-
for times far in the past or far in the future.
1505+
time and this method relies on platform C functions to perform
1506+
the conversion. Since :class:`.datetime` supports a wider range of
1507+
values than the platform C functions on many platforms, this
1508+
method may raise :exc:`OverflowError` or :exc:`OSError` for times
1509+
far in the past or far in the future.
15101510

15111511
For aware :class:`.datetime` instances, the return value is computed
15121512
as::
@@ -1519,6 +1519,10 @@ Instance methods:
15191519
The :meth:`timestamp` method uses the :attr:`.fold` attribute to
15201520
disambiguate the times during a repeated interval.
15211521

1522+
.. versionchanged:: 3.6
1523+
This method no longer relies on the platform C :c:func:`mktime`
1524+
function to perform conversions.
1525+
15221526
.. note::
15231527

15241528
There is no method to obtain the POSIX timestamp directly from a

Doc/whatsnew/3.14.rst

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1200,6 +1200,9 @@ ctypes
12001200
making it a :term:`generic type`.
12011201
(Contributed by Brian Schubert in :gh:`132168`.)
12021202

1203+
* :mod:`ctypes` now supports :term:`free-threading builds <free threading>`.
1204+
(Contributed by Kumar Aditya and Peter Bierma in :gh:`127945`.)
1205+
12031206
curses
12041207
------
12051208

@@ -1997,11 +2000,19 @@ Optimizations
19972000
asyncio
19982001
-------
19992002

2000-
* :mod:`asyncio` now uses double linked list implementation for native tasks
2001-
which speeds up execution by 10% on standard pyperformance benchmarks and
2002-
reduces memory usage.
2003+
* :mod:`asyncio` has a new per-thread double linked list implementation internally for
2004+
:class:`native tasks <asyncio.Task>` which speeds up execution by 10-20% on standard
2005+
pyperformance benchmarks and reduces memory usage.
2006+
This enables external introspection tools such as
2007+
:ref:`python -m asyncio pstree <whatsnew314-asyncio-introspection>`
2008+
to introspect the call graph of asyncio tasks running in all threads.
20032009
(Contributed by Kumar Aditya in :gh:`107803`.)
20042010

2011+
* :mod:`asyncio` has first class support for :term:`free-threading builds <free threading>`.
2012+
This enables parallel execution of multiple event loops across different threads and scales
2013+
linearly with the number of threads.
2014+
(Contributed by Kumar Aditya in :gh:`128002`.)
2015+
20052016
* :mod:`asyncio` has new utility functions for introspecting and printing
20062017
the program's call graph: :func:`asyncio.capture_call_graph` and
20072018
:func:`asyncio.print_call_graph`.
@@ -2083,7 +2094,6 @@ Deprecated
20832094
* :class:`asyncio.WindowsProactorEventLoopPolicy`
20842095
* :func:`asyncio.get_event_loop_policy`
20852096
* :func:`asyncio.set_event_loop_policy`
2086-
* :func:`asyncio.set_event_loop`
20872097

20882098
Users should use :func:`asyncio.run` or :class:`asyncio.Runner` with
20892099
*loop_factory* to use the desired event loop implementation.

Lib/_strptime.py

Lines changed: 23 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -302,7 +302,7 @@ def __init__(self, locale_time=None):
302302
# W is set below by using 'U'
303303
'y': r"(?P<y>\d\d)",
304304
'Y': r"(?P<Y>\d\d\d\d)",
305-
'z': r"(?P<z>[+-]\d\d:?[0-5]\d(:?[0-5]\d(\.\d{1,6})?)?|(?-i:Z))",
305+
'z': r"(?P<z>([+-]\d\d:?[0-5]\d(:?[0-5]\d(\.\d{1,6})?)?)|(?-i:Z))?",
306306
'A': self.__seqToRE(self.locale_time.f_weekday, 'A'),
307307
'a': self.__seqToRE(self.locale_time.a_weekday, 'a'),
308308
'B': self.__seqToRE(self.locale_time.f_month[1:], 'B'),
@@ -548,27 +548,28 @@ def _strptime(data_string, format="%a %b %d %H:%M:%S %Y"):
548548
iso_week = int(found_dict['V'])
549549
elif group_key == 'z':
550550
z = found_dict['z']
551-
if z == 'Z':
552-
gmtoff = 0
553-
else:
554-
if z[3] == ':':
555-
z = z[:3] + z[4:]
556-
if len(z) > 5:
557-
if z[5] != ':':
558-
msg = f"Inconsistent use of : in {found_dict['z']}"
559-
raise ValueError(msg)
560-
z = z[:5] + z[6:]
561-
hours = int(z[1:3])
562-
minutes = int(z[3:5])
563-
seconds = int(z[5:7] or 0)
564-
gmtoff = (hours * 60 * 60) + (minutes * 60) + seconds
565-
gmtoff_remainder = z[8:]
566-
# Pad to always return microseconds.
567-
gmtoff_remainder_padding = "0" * (6 - len(gmtoff_remainder))
568-
gmtoff_fraction = int(gmtoff_remainder + gmtoff_remainder_padding)
569-
if z.startswith("-"):
570-
gmtoff = -gmtoff
571-
gmtoff_fraction = -gmtoff_fraction
551+
if z:
552+
if z == 'Z':
553+
gmtoff = 0
554+
else:
555+
if z[3] == ':':
556+
z = z[:3] + z[4:]
557+
if len(z) > 5:
558+
if z[5] != ':':
559+
msg = f"Inconsistent use of : in {found_dict['z']}"
560+
raise ValueError(msg)
561+
z = z[:5] + z[6:]
562+
hours = int(z[1:3])
563+
minutes = int(z[3:5])
564+
seconds = int(z[5:7] or 0)
565+
gmtoff = (hours * 60 * 60) + (minutes * 60) + seconds
566+
gmtoff_remainder = z[8:]
567+
# Pad to always return microseconds.
568+
gmtoff_remainder_padding = "0" * (6 - len(gmtoff_remainder))
569+
gmtoff_fraction = int(gmtoff_remainder + gmtoff_remainder_padding)
570+
if z.startswith("-"):
571+
gmtoff = -gmtoff
572+
gmtoff_fraction = -gmtoff_fraction
572573
elif group_key == 'Z':
573574
# Since -1 is default value only need to worry about setting tz if
574575
# it can be something other than -1.

Lib/test/datetimetester.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2972,6 +2972,17 @@ def test_strptime_leap_year(self):
29722972
with self._assertNotWarns(DeprecationWarning):
29732973
self.theclass.strptime('02-29,2024', '%m-%d,%Y')
29742974

2975+
def test_strptime_z_empty(self):
2976+
for directive in ('z',):
2977+
string = '2025-04-25 11:42:47'
2978+
format = f'%Y-%m-%d %H:%M:%S%{directive}'
2979+
target = self.theclass(2025, 4, 25, 11, 42, 47)
2980+
with self.subTest(string=string,
2981+
format=format,
2982+
target=target):
2983+
result = self.theclass.strptime(string, format)
2984+
self.assertEqual(result, target)
2985+
29752986
def test_more_timetuple(self):
29762987
# This tests fields beyond those tested by the TestDate.test_timetuple.
29772988
t = self.theclass(2004, 12, 31, 6, 22, 33)

Lib/test/test_threading.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1352,6 +1352,34 @@ def do_flush(*args, **kwargs):
13521352
''')
13531353
assert_python_ok("-c", script)
13541354

1355+
@skip_unless_reliable_fork
1356+
def test_native_id_after_fork(self):
1357+
script = """if True:
1358+
import threading
1359+
import os
1360+
from test import support
1361+
1362+
parent_thread_native_id = threading.current_thread().native_id
1363+
print(parent_thread_native_id, flush=True)
1364+
assert parent_thread_native_id == threading.get_native_id()
1365+
childpid = os.fork()
1366+
if childpid == 0:
1367+
print(threading.current_thread().native_id, flush=True)
1368+
assert threading.current_thread().native_id == threading.get_native_id()
1369+
else:
1370+
try:
1371+
assert parent_thread_native_id == threading.current_thread().native_id
1372+
assert parent_thread_native_id == threading.get_native_id()
1373+
finally:
1374+
support.wait_process(childpid, exitcode=0)
1375+
"""
1376+
rc, out, err = assert_python_ok('-c', script)
1377+
self.assertEqual(rc, 0)
1378+
self.assertEqual(err, b"")
1379+
native_ids = out.strip().splitlines()
1380+
self.assertEqual(len(native_ids), 2)
1381+
self.assertNotEqual(native_ids[0], native_ids[1])
1382+
13551383
class ThreadJoinOnShutdown(BaseTestCase):
13561384

13571385
def _run_and_join(self, script):

Lib/threading.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -944,6 +944,8 @@ def _after_fork(self, new_ident=None):
944944
# This thread is alive.
945945
self._ident = new_ident
946946
assert self._os_thread_handle.ident == new_ident
947+
if _HAVE_THREAD_NATIVE_ID:
948+
self._set_native_id()
947949
else:
948950
# Otherwise, the thread is dead, Jim. _PyThread_AfterFork()
949951
# already marked our handle done.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Update :attr:`Thread.native_id <threading.Thread.native_id>` after
2+
:manpage:`fork(2)` to ensure accuracy. Patch by Noam Cohen.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix ``%z`` directive in :func:`datetime.datetime.strptime` to allow for no provided
2+
offset as was documented.

PCbuild/regen.targets

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -125,8 +125,7 @@
125125
<JITArgs Condition="$(Platform) == 'x64'">x86_64-pc-windows-msvc</JITArgs>
126126
<JITArgs Condition="$(Configuration) == 'Debug'">$(JITArgs) --debug</JITArgs>
127127
</PropertyGroup>
128-
<Exec Command='$(PythonForBuild) "$(PySourcePath)Tools\jit\build.py" $(JITArgs)'
129-
WorkingDirectory="$(GeneratedJitStencilsDir)"/>
128+
<Exec Command='$(PythonForBuild) "$(PySourcePath)Tools\jit\build.py" $(JITArgs) --output-dir "$(GeneratedJitStencilsDir)" --pyconfig-dir "$(PySourcePath)PC"'/>
130129
</Target>
131130
<Target Name="_CleanJIT" AfterTargets="Clean">
132131
<Delete Files="@(_JITOutputs)"/>

Tools/jit/_targets.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ class _Target(typing.Generic[_S, _R]):
4747
debug: bool = False
4848
verbose: bool = False
4949
known_symbols: dict[str, int] = dataclasses.field(default_factory=dict)
50+
pyconfig_dir: pathlib.Path = pathlib.Path.cwd().resolve()
5051

5152
def _get_nop(self) -> bytes:
5253
if re.fullmatch(r"aarch64-.*", self.triple):
@@ -57,13 +58,13 @@ def _get_nop(self) -> bytes:
5758
raise ValueError(f"NOP not defined for {self.triple}")
5859
return nop
5960

60-
def _compute_digest(self, out: pathlib.Path) -> str:
61+
def _compute_digest(self) -> str:
6162
hasher = hashlib.sha256()
6263
hasher.update(self.triple.encode())
6364
hasher.update(self.debug.to_bytes())
6465
# These dependencies are also reflected in _JITSources in regen.targets:
6566
hasher.update(PYTHON_EXECUTOR_CASES_C_H.read_bytes())
66-
hasher.update((out / "pyconfig.h").read_bytes())
67+
hasher.update((self.pyconfig_dir / "pyconfig.h").read_bytes())
6768
for dirpath, _, filenames in sorted(os.walk(TOOLS_JIT)):
6869
for filename in filenames:
6970
hasher.update(pathlib.Path(dirpath, filename).read_bytes())
@@ -125,7 +126,7 @@ async def _compile(
125126
f"-D_JIT_OPCODE={opname}",
126127
"-D_PyJIT_ACTIVE",
127128
"-D_Py_JIT",
128-
"-I.",
129+
f"-I{self.pyconfig_dir}",
129130
f"-I{CPYTHON / 'Include'}",
130131
f"-I{CPYTHON / 'Include' / 'internal'}",
131132
f"-I{CPYTHON / 'Include' / 'internal' / 'mimalloc'}",
@@ -193,28 +194,27 @@ async def _build_stencils(self) -> dict[str, _stencils.StencilGroup]:
193194

194195
def build(
195196
self,
196-
out: pathlib.Path,
197197
*,
198198
comment: str = "",
199199
force: bool = False,
200-
stencils_h: str = "jit_stencils.h",
200+
jit_stencils: pathlib.Path,
201201
) -> None:
202202
"""Build jit_stencils.h in the given directory."""
203+
jit_stencils.parent.mkdir(parents=True, exist_ok=True)
203204
if not self.stable:
204205
warning = f"JIT support for {self.triple} is still experimental!"
205206
request = "Please report any issues you encounter.".center(len(warning))
206207
outline = "=" * len(warning)
207208
print("\n".join(["", outline, warning, request, outline, ""]))
208-
digest = f"// {self._compute_digest(out)}\n"
209-
jit_stencils = out / stencils_h
209+
digest = f"// {self._compute_digest()}\n"
210210
if (
211211
not force
212212
and jit_stencils.exists()
213213
and jit_stencils.read_text().startswith(digest)
214214
):
215215
return
216216
stencil_groups = ASYNCIO_RUNNER.run(self._build_stencils())
217-
jit_stencils_new = out / "jit_stencils.h.new"
217+
jit_stencils_new = jit_stencils.parent / "jit_stencils.h.new"
218218
try:
219219
with jit_stencils_new.open("w") as file:
220220
file.write(digest)

0 commit comments

Comments
 (0)