Skip to content

Commit 175db64

Browse files
generatedunixname1734921407115435meta-codesync[bot]
authored andcommitted
Sync pre-release CPython 3.15 branch from GitHub (2026-07-08)
Summary: Imported python/cpython `3.15.0b3+dev` from upstream rev [`e597358`](https://www.github.com/python/cpython/commit/e597358779cb965a687ba26422b434d70e3cd201) (committed 2026-07-08 01:40:03+00:00). # Commit Info - Base: (`3.15.0b3+dev`) - [`017e4e9`](https://www.github.com/python/cpython/commit/017e4e9bb8152bf09acd31b5fcaafceac002d84f) (commit date: 2026-07-07 00:24:44+00:00) - Imported: (`3.15.0b3+dev`) - [`e597358`](https://www.github.com/python/cpython/commit/e597358779cb965a687ba26422b434d70e3cd201) (commit date: 2026-07-08 01:40:03+00:00) # Noteworthy file changes - Test files (1 added) - Low-signal files (4 added) (NEWS.d, docs, .github) Complete list of added/removed files: https://www.internalfb.com/intern/everpaste/?color=0&handle=GKynkiEO1CK4nrwFAL8lSt5yoS1Abr0LAAAz Differential Revision: D110993969 fbshipit-source-id: 2f125926903ef71de6fff4ddcb27dca7af028e54
1 parent b400516 commit 175db64

19 files changed

Lines changed: 198 additions & 42 deletions

.github/workflows/build.yml

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -601,7 +601,6 @@ jobs:
601601
- Thread
602602
free-threading:
603603
- false
604-
- true
605604
sanitizer:
606605
- TSan
607606
include:
@@ -613,6 +612,17 @@ jobs:
613612
sanitizer: ${{ matrix.sanitizer }}
614613
free-threading: ${{ matrix.free-threading }}
615614

615+
# XXX: Temporarily allow this job to fail to not block PRs.
616+
build-san-free-threading:
617+
# ${{ '' } is a hack to nest jobs under the same sidebar category.
618+
name: Sanitizers${{ '' }} # zizmor: ignore[obfuscation]
619+
needs: build-context
620+
if: needs.build-context.outputs.run-ubuntu == 'true'
621+
uses: ./.github/workflows/reusable-san.yml
622+
with:
623+
sanitizer: TSan
624+
free-threading: true
625+
616626
cross-build-linux:
617627
name: Cross build Linux
618628
runs-on: ubuntu-latest
@@ -716,6 +726,7 @@ jobs:
716726
- test-hypothesis
717727
- build-asan
718728
- build-san
729+
- build-san-free-threading
719730
- cross-build-linux
720731
- cifuzz
721732
if: always()
@@ -727,6 +738,7 @@ jobs:
727738
allowed-failures: >-
728739
build-android,
729740
build-emscripten,
741+
build-san-free-threading,
730742
build-windows-msi,
731743
build-ubuntu-ssltests,
732744
test-hypothesis,

.github/workflows/reusable-docs.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,17 @@ name: Reusable Docs
33
on:
44
workflow_call:
55
workflow_dispatch:
6+
# Pushes to CPython branches seed the pip caches under the exact keys every
7+
# docs PR restores from. Without a branch-scoped copy, each PR saves a
8+
# duplicate into its own refs/pull/N/merge scope that nothing else can read.
9+
push:
10+
branches:
11+
- main
12+
- '3.*'
13+
paths:
14+
- 'Doc/pylock.toml'
15+
- 'Doc/requirements.txt'
16+
- '.github/workflows/reusable-docs.yml'
617

718
permissions:
819
contents: read

Doc/library/string.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -971,7 +971,8 @@ attributes:
971971

972972
Alternatively, you can provide the entire regular expression pattern by
973973
overriding the class attribute *pattern*. If you do this, the value must be a
974-
regular expression object with four named capturing groups. The capturing
974+
regular expression pattern string, or a compiled regular expression
975+
object, with four named capturing groups. The capturing
975976
groups correspond to the rules given above, along with the invalid placeholder
976977
rule:
977978

Doc/pylock.toml

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
# uv pip compile Doc/requirements.txt --exclude-newer P14D --exclude-newer-package linklint=PT0S --exclude-newer-package python-docs-theme=PT0S --no-cache --output-file Doc/pylock.toml --python-version 3.12 --universal
33
lock-version = "1.0"
44
created-by = "uv"
5+
requires-python = ">=3.12"
56

67
[[packages]]
78
name = "alabaster"
@@ -23,9 +24,9 @@ wheels = [{ url = "https://files.pythonhosted.org/packages/b4/03/374bd9e31b58e8a
2324

2425
[[packages]]
2526
name = "certifi"
26-
version = "2026.4.22"
27-
sdist = { url = "https://files.pythonhosted.org/packages/25/ee/6caf7a40c36a1220410afe15a1cc64993a1f864871f698c0f93acb72842a/certifi-2026.4.22.tar.gz", upload-time = 2026-04-22T11:26:11Z, size = 137077, hashes = { sha256 = "8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580" } }
28-
wheels = [{ url = "https://files.pythonhosted.org/packages/22/30/7cd8fdcdfbc5b869528b079bfb76dcdf6056b1a2097a662e5e8c04f42965/certifi-2026.4.22-py3-none-any.whl", upload-time = 2026-04-22T11:26:09Z, size = 135707, hashes = { sha256 = "3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a" } }]
27+
version = "2026.6.17"
28+
sdist = { url = "https://files.pythonhosted.org/packages/c9/c7/424b75da314c1045981bd9777432fad05a9e0c69daa4ed7e308bbaffe405/certifi-2026.6.17.tar.gz", upload-time = 2026-06-17T10:31:07Z, size = 134594, hashes = { sha256 = "024c88eeec92ca068db80f02b8b07c9cef7b9fe261d1d535abfd5abd6f6af432" } }
29+
wheels = [{ url = "https://files.pythonhosted.org/packages/ef/2f/c5464532e965badff2f4c4c1a3a83f5697f0d7c407ed0cda44aaa99bb451/certifi-2026.6.17-py3-none-any.whl", upload-time = 2026-06-17T10:31:06Z, size = 133289, hashes = { sha256 = "2227dcbaafe0d2f59279d1762ddddc37783ed4354594f194ffc31d20f41fc3db" } }]
2930

3031
[[packages]]
3132
name = "charset-normalizer"
@@ -114,9 +115,9 @@ wheels = [{ url = "https://files.pythonhosted.org/packages/8f/d7/9322c609343d929
114115

115116
[[packages]]
116117
name = "idna"
117-
version = "3.13"
118-
sdist = { url = "https://files.pythonhosted.org/packages/ce/cc/762dfb036166873f0059f3b7de4565e1b5bc3d6f28a414c13da27e442f99/idna-3.13.tar.gz", upload-time = 2026-04-22T16:42:42Z, size = 194210, hashes = { sha256 = "585ea8fe5d69b9181ec1afba340451fba6ba764af97026f92a91d4eef164a242" } }
119-
wheels = [{ url = "https://files.pythonhosted.org/packages/5d/13/ad7d7ca3808a898b4612b6fe93cde56b53f3034dcde235acb1f0e1df24c6/idna-3.13-py3-none-any.whl", upload-time = 2026-04-22T16:42:40Z, size = 68629, hashes = { sha256 = "892ea0cde124a99ce773decba204c5552b69c3c67ffd5f232eb7696135bc8bb3" } }]
118+
version = "3.18"
119+
sdist = { url = "https://files.pythonhosted.org/packages/cd/63/9496c57188a2ee585e0f1db071d75089a11e98aa86eb99d9d7618fc1edce/idna-3.18.tar.gz", upload-time = 2026-06-02T14:34:07Z, size = 196711, hashes = { sha256 = "ffb385a7e039654cef1ab9ef32c6fafe283c0c0467bba1d9029738ce4a14a848" } }
120+
wheels = [{ url = "https://files.pythonhosted.org/packages/1e/5e/d4e9f1a599fb8e573b7b87160658329fbf28d19eac2718f51fc3def3aa5a/idna-3.18-py3-none-any.whl", upload-time = 2026-06-02T14:34:06Z, size = 65455, hashes = { sha256 = "7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2" } }]
120121

121122
[[packages]]
122123
name = "imagesize"
@@ -132,9 +133,9 @@ wheels = [{ url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f
132133

133134
[[packages]]
134135
name = "linklint"
135-
version = "1.0.0"
136-
sdist = { url = "https://files.pythonhosted.org/packages/f3/59/a2bb261b3fc0e3bfdc9d3e6f8a37bdf1fb6eca8d992f23f55a83bf19acb1/linklint-1.0.0.tar.gz", upload-time = 2026-05-02T11:39:39Z, size = 21310, hashes = { sha256 = "52dc292f27b7eb4f3825d23ec1222d4a17bd116945b77c486406d97bb936e6c2" } }
137-
wheels = [{ url = "https://files.pythonhosted.org/packages/7b/78/98e52e4262416060a0eedc2e5514035cfcfc2fd962d09b84e27848c1aef3/linklint-1.0.0-py3-none-any.whl", upload-time = 2026-05-02T11:39:37Z, size = 12521, hashes = { sha256 = "bbb3f589ab65709cf23655ef6097bdc25180f80c0d42a4d9a1714366d2d3edab" } }]
136+
version = "1.0.1"
137+
sdist = { url = "https://files.pythonhosted.org/packages/62/22/c65a97b2192c3c317c472058bf52d7ca88e421998fa8d5c183b2280ab5d4/linklint-1.0.1.tar.gz", upload-time = 2026-06-09T12:30:47Z, size = 22412, hashes = { sha256 = "10201b4366edecfbeb3281883aff857dbe77d40bb56f16f212d372f597342d5f" } }
138+
wheels = [{ url = "https://files.pythonhosted.org/packages/7b/d8/b2f1708f55f4dd5358b48d28be07ff8e0dadd6d23e41ead2ed1424e90256/linklint-1.0.1-py3-none-any.whl", upload-time = 2026-06-09T12:30:46Z, size = 12828, hashes = { sha256 = "c931f110482f1d17808aa2df78b116adc44d7a14e9e79922c57bf9a7cba40a2d" } }]
138139

139140
[[packages]]
140141
name = "markupsafe"
@@ -162,8 +163,7 @@ wheels = [{ url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c
162163
[[packages]]
163164
name = "pygments"
164165
version = "2.20.0"
165-
sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", upload-time = 2026-03-29T13:29:33Z, size = 4955991, hashes = { sha256 = "6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f" } }
166-
wheels = [{ url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", upload-time = 2026-03-29T13:29:30Z, size = 1231151, hashes = { sha256 = "81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176" } }]
166+
archive = { url = "https://github.com/pygments/pygments/archive/2cad2642058441b59782a6a18f03c98c42d081f1.tar.gz", hashes = { sha256 = "e6ae46831285e86355eabb969bf2d0520655b001dd882147637f4bc500e56bfb" } }
167167

168168
[[packages]]
169169
name = "python-docs-theme"
@@ -173,9 +173,9 @@ wheels = [{ url = "https://files.pythonhosted.org/packages/db/05/b9298eb9330c70a
173173

174174
[[packages]]
175175
name = "requests"
176-
version = "2.33.1"
177-
sdist = { url = "https://files.pythonhosted.org/packages/5f/a4/98b9c7c6428a668bf7e42ebb7c79d576a1c3c1e3ae2d47e674b468388871/requests-2.33.1.tar.gz", upload-time = 2026-03-30T16:09:15Z, size = 134120, hashes = { sha256 = "18817f8c57c6263968bc123d237e3b8b08ac046f5456bd1e307ee8f4250d3517" } }
178-
wheels = [{ url = "https://files.pythonhosted.org/packages/d7/8e/7540e8a2036f79a125c1d2ebadf69ed7901608859186c856fa0388ef4197/requests-2.33.1-py3-none-any.whl", upload-time = 2026-03-30T16:09:13Z, size = 64947, hashes = { sha256 = "4e6d1ef462f3626a1f0a0a9c42dd93c63bad33f9f1c1937509b8c5c8718ab56a" } }]
176+
version = "2.34.2"
177+
sdist = { url = "https://files.pythonhosted.org/packages/ac/c3/e2a2b89f2d3e2179abd6d00ebd70bff6273f37fb3e0cc209f48b39d00cbf/requests-2.34.2.tar.gz", upload-time = 2026-05-14T19:25:27Z, size = 142856, hashes = { sha256 = "f288924cae4e29463698d6d60bc6a4da69c89185ad1e0bcc4104f584e960b9ed" } }
178+
wheels = [{ url = "https://files.pythonhosted.org/packages/a0/f4/c67b0b3f1b9245e8d266f0f112c500d50e5b4e83cb6f3b71b6528104182a/requests-2.34.2-py3-none-any.whl", upload-time = 2026-05-14T19:25:26Z, size = 73075, hashes = { sha256 = "2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0" } }]
179179

180180
[[packages]]
181181
name = "roman-numerals"
@@ -251,6 +251,6 @@ wheels = [{ url = "https://files.pythonhosted.org/packages/bf/a4/66c1fd4f8fab88f
251251

252252
[[packages]]
253253
name = "urllib3"
254-
version = "2.6.3"
255-
sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", upload-time = 2026-01-07T16:24:43Z, size = 435556, hashes = { sha256 = "1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed" } }
256-
wheels = [{ url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", upload-time = 2026-01-07T16:24:42Z, size = 131584, hashes = { sha256 = "bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4" } }]
254+
version = "2.7.0"
255+
sdist = { url = "https://files.pythonhosted.org/packages/53/0c/06f8b233b8fd13b9e5ee11424ef85419ba0d8ba0b3138bf360be2ff56953/urllib3-2.7.0.tar.gz", upload-time = 2026-05-07T16:13:18Z, size = 433602, hashes = { sha256 = "231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c" } }
256+
wheels = [{ url = "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", upload-time = 2026-05-07T16:13:17Z, size = 131087, hashes = { sha256 = "9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897" } }]

Doc/requirements.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@
99
# Keep this version in sync with ``Doc/conf.py``.
1010
sphinx<9.0.0
1111

12+
# Temporary direct requirement, pending release of Pygments > 2.20.0
13+
# https://github.com/pygments/pygments/discussions/3145
14+
pygments @ https://github.com/pygments/pygments/archive/2cad2642058441b59782a6a18f03c98c42d081f1.tar.gz
15+
1216
blurb
1317

1418
sphinxext-opengraph~=0.13.0

Lib/string/__init__.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,14 @@ def __init_subclass__(cls):
8383
def _compile_pattern(cls):
8484
import re # deferred import, for performance
8585

86+
# `pattern` may be the `_TemplatePattern` sentinel (not yet compiled), an
87+
# already-compiled regular expression object (as documented), or a string
88+
# regular expression. An already-compiled object is returned as-is; the
89+
# other two are compiled and cached back on the class.
8690
pattern = cls.__dict__.get('pattern', _TemplatePattern)
91+
if isinstance(pattern, re.Pattern):
92+
# re.compile() rejects flags on an already-compiled pattern.
93+
return pattern
8794
if pattern is _TemplatePattern:
8895
delim = re.escape(cls.delimiter)
8996
id = cls.idpattern

Lib/test/test_array.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import collections.abc
66
import unittest
77
from test import support
8-
from test.support import import_helper
8+
from test.support import import_helper, script_helper
99
from test.support import os_helper
1010
from test.support import _2G
1111
from test.support import subTests
@@ -38,6 +38,11 @@ def __init__(self, typecode, newarg=None):
3838

3939
class MiscTest(unittest.TestCase):
4040

41+
def test_array_type_importable(self):
42+
from array import ArrayType
43+
44+
self.assertIs(array.array, ArrayType)
45+
4146
def test_array_is_sequence(self):
4247
self.assertIsInstance(array.array("B"), collections.abc.MutableSequence)
4348
self.assertIsInstance(array.array("B"), collections.abc.Reversible)
@@ -49,6 +54,23 @@ def test_bad_constructor(self):
4954
self.assertRaises(ValueError, array.array, 'x')
5055
self.assertRaises(ValueError, array.array, 'Z')
5156

57+
@support.cpython_only
58+
def test_does_not_crash_on_broken_imports(self):
59+
# gh-153210
60+
code = """if 1:
61+
import collections.abc
62+
63+
del collections.abc.MutableSequence
64+
65+
try:
66+
import array # it used to crash before
67+
except AttributeError:
68+
pass
69+
else:
70+
raise AssertionError('AttributeError was not raised')
71+
"""
72+
script_helper.assert_python_ok('-c', code)
73+
5274
@support.cpython_only
5375
def test_disallow_instantiation(self):
5476
my_array = array.array("I")
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import string
2+
import unittest
3+
from string import Template
4+
5+
from test.support import threading_helper
6+
7+
8+
@threading_helper.requires_working_threading()
9+
class TestTemplateCompileRace(unittest.TestCase):
10+
def test_concurrent_first_use(self):
11+
# Racing the lazy pattern compile must not raise a spurious ValueError
12+
# from recompiling an already-compiled pattern. A throwaway subclass,
13+
# re-armed to the sentinel each round, keeps string.Template unmutated
14+
# (subclasses precompile eagerly in __init_subclass__).
15+
uncompiled = string._TemplatePattern
16+
errors = []
17+
18+
def use_template(cls):
19+
try:
20+
cls("$x and ${y}").substitute(x=1, y=2)
21+
except Exception as e:
22+
errors.append(e)
23+
24+
for _ in range(20):
25+
class T(Template):
26+
pass
27+
T.pattern = uncompiled
28+
T.flags = None
29+
threading_helper.run_concurrently(use_template, nthreads=10, args=(T,))
30+
31+
self.assertEqual(errors, [], msg=f"unexpected errors: {errors}")
32+
33+
34+
if __name__ == "__main__":
35+
unittest.main()

Lib/test/test_ssl.py

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1606,19 +1606,24 @@ def dummycallback(sock, servername, ctx, cycle=ctx):
16061606
gc.collect()
16071607
self.assertIs(wr(), None)
16081608

1609-
@unittest.skipUnless(support.Py_GIL_DISABLED,
1610-
"test is only useful if the GIL is disabled")
16111609
@threading_helper.requires_working_threading()
16121610
def test_sni_callback_race(self):
1613-
# Replacing sni_callback while handshakes are in-flight must not
1611+
# Replacing sni_callback while a handshake is in-flight must not
16141612
# crash (use-after-free on the callback in free-threaded builds).
1613+
#
1614+
# Use a single handshake thread: OpenSSL has internal data races
1615+
# on shared SSL_CTX state when multiple handshakes run
1616+
# concurrently against the same context (gh-150191). Concurrency
1617+
# on the *setter* is what exercises the fix from gh-149816, so
1618+
# multiple toggler threads race against each other and against
1619+
# the one handshake worker.
16151620
client_ctx, server_ctx, hostname = testing_context()
16161621

16171622
server_ctx.sni_callback = lambda *a: None
1618-
done = threading.Event()
1623+
deadline = time.monotonic() + 0.1
16191624

16201625
def do_handshakes():
1621-
while not done.is_set():
1626+
while time.monotonic() < deadline:
16221627
c_in = ssl.MemoryBIO()
16231628
c_out = ssl.MemoryBIO()
16241629
s_in = ssl.MemoryBIO()
@@ -1645,19 +1650,11 @@ def do_handshakes():
16451650
c_in.write(s_out.read())
16461651

16471652
def toggle_callback():
1648-
while not done.is_set():
1653+
while time.monotonic() < deadline:
16491654
server_ctx.sni_callback = lambda *a: None
16501655
server_ctx.sni_callback = None
16511656

1652-
workers = max(4, (os.cpu_count() or 4) * 2)
1653-
threads = [threading.Thread(target=do_handshakes)
1654-
for _ in range(workers)]
1655-
threads.append(threading.Thread(target=toggle_callback))
1656-
1657-
with threading_helper.catch_threading_exception() as cm:
1658-
with threading_helper.start_threads(threads):
1659-
done.set()
1660-
self.assertIsNone(cm.exc_value)
1657+
threading_helper.run_concurrently([do_handshakes] + 4 * [toggle_callback])
16611658

16621659
def test_cert_store_stats(self):
16631660
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)

Lib/test/test_string/test_string.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,20 @@ def test_SafeTemplate(self):
299299
eq(s.safe_substitute(dict(who='tim', what='ham', meal='dinner')),
300300
'tim likes ham for dinner')
301301

302+
def test_precompiled_pattern(self):
303+
# A subclass may supply an already-compiled pattern; it must be reused,
304+
# not recompiled (re.compile() rejects flags on a compiled pattern).
305+
import re
306+
compiled = re.compile(
307+
r'\$(?:(?P<escaped>\$)|(?P<named>[a-z]+)|'
308+
r'\{(?P<braced>[a-z]+)\}|(?P<invalid>))')
309+
class MyTemplate(Template):
310+
pattern = compiled
311+
self.assertIs(MyTemplate.pattern, compiled)
312+
self.assertEqual(
313+
MyTemplate('$who likes $what').substitute(who='tim', what='ham'),
314+
'tim likes ham')
315+
302316
def test_invalid_placeholders(self):
303317
raises = self.assertRaises
304318
s = Template('$who likes $')

0 commit comments

Comments
 (0)