Skip to content

Commit 7719bc5

Browse files
authored
Merge branch 'main' into fix-curses-initscr-screen-ref
2 parents ed144a5 + b8862ae commit 7719bc5

17 files changed

Lines changed: 151 additions & 50 deletions

Doc/whatsnew/3.15.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -873,6 +873,14 @@ Other language changes
873873
imports ``pkg.sub.mod``.
874874
(Contributed by Gregory P. Smith in :gh:`83065`.)
875875

876+
* File names of Stable ABI extensions that use the ``.so`` suffix may now
877+
include a multiarch tuple, for example, ``foo.abi3-x86-64-linux-gnu.so``.
878+
This permits stable ABI extensions for multiple architectures to be
879+
co-installed into the same directory, without clashing with each
880+
other, as regular dynamic extensions do.
881+
(Contributed by Stefano Rivera in :gh:`122931`.)
882+
883+
876884

877885
Default interactive shell
878886
=========================

Include/internal/pycore_interp_structs.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -977,7 +977,6 @@ struct _is {
977977
struct _obmalloc_state *obmalloc;
978978

979979
PyObject *audit_hooks;
980-
PyMutex audit_hooks_mutex;
981980
PyType_WatchCallback type_watchers[TYPE_MAX_WATCHERS];
982981
PyCode_WatchCallback code_watchers[CODE_MAX_WATCHERS];
983982
PyContext_WatchCallback context_watchers[CONTEXT_MAX_WATCHERS];

Lib/csv.py

Lines changed: 33 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -563,22 +563,40 @@ def _detect_doublequote(self, lines, delimiter, quotechar, escapechar):
563563
def _detect_skipinitialspace(self, lines, delimiter, quotechar,
564564
escapechar, doublequote):
565565
"""
566-
True only if every field following a delimiter starts with
567-
a space.
566+
Detect whether the spaces following a delimiter are a part of
567+
the format or of the data.
568568
"""
569-
skipinitialspace = False
570-
try:
571-
for row in self._make_reader(lines, delimiter, quotechar,
572-
escapechar,
573-
doublequote=doublequote,
574-
skipinitialspace=False):
575-
for field in row[1:]:
576-
if not field.startswith(' '):
577-
return False
578-
skipinitialspace = True
579-
except Error:
580-
pass
581-
return skipinitialspace
569+
results = []
570+
for skipinitialspace in False, True:
571+
rows = []
572+
try:
573+
rows.extend(self._make_reader(
574+
lines, delimiter, quotechar, escapechar,
575+
doublequote=doublequote,
576+
skipinitialspace=skipinitialspace))
577+
except Error:
578+
# Keep the rows parsed before the error.
579+
pass
580+
results.append([row for row in rows if row])
581+
if results[0] == results[1]:
582+
return False # No evidence.
583+
counts = [[len(row) for row in rows] for rows in results]
584+
if counts[0] != counts[1]:
585+
# Prefer the more consistent row widths.
586+
return len(set(counts[1])) <= len(set(counts[0]))
587+
# Only some spaces are stripped. A field differs only if
588+
# a space was skipped at its start, which tells the padding
589+
# apart from the spaces inside quoted or escaped fields.
590+
if not all(kept_field != skipped_field
591+
for kept_row, skipped_row in zip(*results)
592+
for kept_field, skipped_field in zip(kept_row[1:],
593+
skipped_row[1:])):
594+
return False
595+
# The first field of a row is commonly not padded ('a, b, c'),
596+
# so the first fields need only agree with each other.
597+
first = [kept_row[0] != skipped_row[0]
598+
for kept_row, skipped_row in zip(*results)]
599+
return all(first) or not any(first)
582600

583601
def has_header(self, sample):
584602
# Creates a dictionary of types of data in each column. If any

Lib/test/pythoninfo.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -570,6 +570,7 @@ def collect_sysconfig(info_add):
570570

571571
for name in (
572572
'ABIFLAGS',
573+
'ALT_SOABI',
573574
'ANDROID_API_LEVEL',
574575
'CC',
575576
'CCSHARED',
@@ -595,6 +596,7 @@ def collect_sysconfig(info_add):
595596
'Py_REMOTE_DEBUG',
596597
'SHELL',
597598
'SOABI',
599+
'SOABI_PLATFORM',
598600
'TEST_MODULES',
599601
'VAPTH',
600602
'abs_builddir',
@@ -1315,6 +1317,12 @@ def collect_system(info_add):
13151317
info_add('system.hardware', hardware)
13161318

13171319

1320+
def collect_importlib(info_add):
1321+
import importlib.machinery
1322+
info_add('importlib.extension_suffixes',
1323+
importlib.machinery.EXTENSION_SUFFIXES)
1324+
1325+
13181326
def collect_info(info):
13191327
error = False
13201328
info_add = info.add
@@ -1357,6 +1365,7 @@ def collect_info(info):
13571365
collect_zstd,
13581366
collect_libregrtest_utils,
13591367
collect_system,
1368+
collect_importlib,
13601369

13611370
# Collecting from tests should be last as they have side effects.
13621371
collect_test_socket,

Lib/test/test_csv.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1688,6 +1688,60 @@ def test_sniff_skipinitialspace_quoted(self):
16881688
self.assertEqual(dialect.quotechar, "'")
16891689
self.assertIs(dialect.skipinitialspace, True)
16901690

1691+
def test_sniff_skipinitialspace_quoted_fields(self):
1692+
# A quote is only a quote at the very start of a field, so not
1693+
# skipping the space splits the quoted field.
1694+
sniffer = csv.Sniffer()
1695+
sample = 'a, "b,c"\nd,e\nf,g\n'
1696+
dialect = sniffer.sniff(sample)
1697+
self.assertEqual(dialect.delimiter, ',')
1698+
self.assertEqual(dialect.quotechar, '"')
1699+
self.assertIs(dialect.skipinitialspace, True)
1700+
self.assertEqual(next(csv.reader(StringIO(sample), dialect)),
1701+
['a', 'b,c'])
1702+
1703+
# But without a delimiter inside the quotes nothing is split,
1704+
# so only the padding counts.
1705+
sample = 'a, "b"\nd,e\nf,g\n'
1706+
dialect = sniffer.sniff(sample)
1707+
self.assertEqual(dialect.delimiter, ',')
1708+
self.assertEqual(dialect.quotechar, '"')
1709+
self.assertIs(dialect.skipinitialspace, False)
1710+
self.assertEqual(next(csv.reader(StringIO(sample), dialect)),
1711+
['a', ' "b"'])
1712+
1713+
def test_sniff_skipinitialspace_data(self):
1714+
# The spaces are a part of the data if only some fields
1715+
# are padded.
1716+
sniffer = csv.Sniffer()
1717+
sample = 'a, b\nc,d\ne, f\n'
1718+
dialect = sniffer.sniff(sample)
1719+
self.assertEqual(dialect.delimiter, ',')
1720+
self.assertIs(dialect.skipinitialspace, False)
1721+
self.assertEqual(next(csv.reader(StringIO(sample), dialect)),
1722+
['a', ' b'])
1723+
1724+
def test_sniff_skipinitialspace_not_skipped(self):
1725+
# A quoted or escaped space is not skipped, so it is not
1726+
# an evidence of the padding.
1727+
sniffer = csv.Sniffer()
1728+
sample = 'a," b"\nc, d\n'
1729+
dialect = sniffer.sniff(sample)
1730+
self.assertEqual(dialect.delimiter, ',')
1731+
self.assertEqual(dialect.quotechar, '"')
1732+
self.assertIs(dialect.skipinitialspace, False)
1733+
self.assertEqual(list(csv.reader(StringIO(sample), dialect)),
1734+
[['a', ' b'], ['c', ' d']])
1735+
1736+
# The escaped delimiter forces the escapechar detection.
1737+
sample = 'a,\\ b\\,c\nd, e\n'
1738+
dialect = sniffer.sniff(sample)
1739+
self.assertEqual(dialect.delimiter, ',')
1740+
self.assertEqual(dialect.escapechar, '\\')
1741+
self.assertIs(dialect.skipinitialspace, False)
1742+
self.assertEqual(list(csv.reader(StringIO(sample), dialect)),
1743+
[['a', ' b,c'], ['d', ' e']])
1744+
16911745
def test_sniff_regex_backtracking(self):
16921746
# gh-109638: this artificial sample used to take minutes.
16931747
sniffer = csv.Sniffer()

Lib/test/test_free_threading/test_sys.py

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -44,20 +44,6 @@ def worker(worker_id):
4444
workers = [lambda: worker(i) for i in range(5)]
4545
threading_helper.run_concurrently(workers)
4646

47-
def test_sys_audit_hooks(self):
48-
def _hook(*args):
49-
return None
50-
51-
def adder():
52-
for _ in range(100):
53-
sys.addaudithook(_hook)
54-
55-
def auditor():
56-
for _ in range(2000):
57-
sys.audit("fusil.tsan.test")
58-
59-
threading_helper.run_concurrently([adder, auditor])
60-
6147

6248
if __name__ == "__main__":
6349
unittest.main()

Lib/test/test_importlib/extension/test_finder.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import unittest
77
import sys
8+
import sysconfig
89

910

1011
class FinderTests(abc.FinderTests):
@@ -61,6 +62,7 @@ def test_failure(self):
6162

6263
def test_abi3_extension_suffixes(self):
6364
suffixes = self.machinery.EXTENSION_SUFFIXES
65+
platform = sysconfig.get_config_var("SOABI_PLATFORM")
6466
if 'win32' in sys.platform:
6567
# Either "_d.pyd" or ".pyd" must be in suffixes
6668
self.assertTrue({"_d.pyd", ".pyd"}.intersection(suffixes))
@@ -73,6 +75,13 @@ def test_abi3_extension_suffixes(self):
7375
self.assertIn(".abi3.so", suffixes)
7476
self.assertIn(".abi3t.so", suffixes)
7577

78+
if platform:
79+
if Py_GIL_DISABLED:
80+
self.assertNotIn(f".abi3-{platform}.so", suffixes)
81+
else:
82+
self.assertIn(f".abi3-{platform}.so", suffixes)
83+
self.assertIn(f".abi3t-{platform}.so", suffixes)
84+
7685

7786
(Frozen_FinderTests,
7887
Source_FinderTests

Lib/test/test_sysconfig.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -457,6 +457,15 @@ def test_soabi(self):
457457
soabi = sysconfig.get_config_var('SOABI')
458458
self.assertIn(soabi, _imp.extension_suffixes()[0])
459459

460+
@unittest.skipIf(not _imp.extension_suffixes(), "stub loader has no suffixes")
461+
@unittest.skipIf(sys.platform == "win32", "Does not apply to Windows")
462+
@unittest.skipIf(sysconfig.get_config_var('SOABI_PLATFORM') == 0,
463+
"SOABI_PLATFORM is undefined")
464+
def test_soabi_platform(self):
465+
soabi_platform = sysconfig.get_config_var('SOABI_PLATFORM')
466+
soabi = sysconfig.get_config_var('SOABI')
467+
self.assertIn(soabi_platform, soabi)
468+
460469
def test_library(self):
461470
library = sysconfig.get_config_var('LIBRARY')
462471
ldlibrary = sysconfig.get_config_var('LDLIBRARY')
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Allow importing stable ABI C extensions that include a multiarch tuple in their filename, e.g. ``foo.abi3-x86-64-linux-gnu.so``.

Misc/NEWS.d/next/Library/2026-07-22-12-42-53.gh-issue-154431.U2kXXZ.rst

Lines changed: 0 additions & 1 deletion
This file was deleted.

0 commit comments

Comments
 (0)