Compare commits
117 Commits
| Author | SHA256 | Date | |
|---|---|---|---|
| 933c09c6e7 | |||
| 8dfa69894b | |||
| 546b461cac | |||
| 6b208148d5 | |||
| 7d7918da5c | |||
| c0ce28b987 | |||
| 1e4ee16388 | |||
| f6d2851f6c | |||
| da0b04baf2 | |||
| 53c6b5806f | |||
| 77f76833e3 | |||
| 9a667c4c20 | |||
| 999420a490 | |||
| 9ca9812624 | |||
| a20e070127 | |||
| d11614d4ed | |||
| 9c7fb3fd1c | |||
| 5387496967 | |||
| 5809d58f85 | |||
| 1165662ec5 | |||
| 41648a15a2 | |||
| 4b7d1d28de | |||
| f28c965458 | |||
| 7dd198cfe7 | |||
| f5a97f08ca | |||
| a7f0a95500 | |||
| d211ef5723 | |||
| 25505ee605 | |||
| 12bd851ee2 | |||
| dd5fb925b7 | |||
| 1f08314c3a | |||
| 554da39fc3 | |||
| dd131b5d35 | |||
| a3911dcafa | |||
| 9a349f4467 | |||
| 76fae75573 | |||
| 830570cba4 | |||
| 7f5cc18c50 | |||
| 3abbc71ec9 | |||
| 868cadf173 | |||
| 66523dd95b | |||
| 342a968636 | |||
| ec1e529a92 | |||
| 6eac0e88ad | |||
| c23aea39af | |||
| 832cb13bad | |||
| e31c619a21 | |||
| c0defb2811 | |||
| cd770d6e57 | |||
| 03b258fb2c | |||
| c91a5f8197 | |||
| e1685fbbf8 | |||
| c8710f6111 | |||
| 278c7153d7 | |||
| 321bb0d7e9 | |||
| e770acb6f0 | |||
| 3047b26157 | |||
| aea442a9a6 | |||
| 833fa863f5 | |||
| 441cd92662 | |||
| 5711af7c49 | |||
| 9e0ee087ce | |||
| 623d45c280 | |||
| c706fb2968 | |||
| 18432aa658 | |||
| 1fd33ac06f | |||
| e823fc6427 | |||
| 24b66c2b93 | |||
| f09a292447 | |||
| 0582990d1a | |||
| fd283e4baa | |||
| 67114aec5e | |||
| 87c6986563 | |||
| e705cd6f93 | |||
| db4a1d07d8 | |||
| 3cf4cdb546 | |||
| 2a69083063 | |||
| 9096e514fd | |||
| fa7f9c6304 | |||
| 1b69df49d2 | |||
| 21cc2a2ea3 | |||
| 72aedb49c2 | |||
| 5f17a934b8 | |||
| e807b3631c | |||
| 6684e0ba8c | |||
| a2de0d65e5 | |||
| 21526d4907 | |||
| 0d21f9b33d | |||
| 614c9baabb | |||
| abecf500df | |||
| 3f82268a6b | |||
| a197d1577e | |||
| 40a4810e13 | |||
| 22d1eff602 | |||
| 74b8c399cf | |||
| 646de307b3 | |||
| d32afb4a41 | |||
| fc1cddc864 | |||
| 5f331fbeb8 | |||
| 0514b88387 | |||
| d13041585b | |||
| ffe5956a88 | |||
| 427d18bfde | |||
| 36c8afb201 | |||
| d6b3877c9d | |||
| 220c89fe66 | |||
| ff731fd9f3 | |||
| 441cc729f1 | |||
| 452f757e4d | |||
| 6023fe50f8 | |||
| 5b804c6d2c | |||
| f4085a5bd2 | |||
| 6ed330f964 | |||
| 2759337970 | |||
| 842bef45da | |||
| 71618bc715 | |||
| eceb111a52 |
@@ -1,474 +0,0 @@
|
||||
From 4a153a1d3b18803a684cd1bcc2cdf3ede3dbae19 Mon Sep 17 00:00:00 2001
|
||||
From: Victor Stinner <vstinner@python.org>
|
||||
Date: Fri, 15 Dec 2023 16:10:40 +0100
|
||||
Subject: [PATCH] [CVE-2023-27043] gh-102988: Reject malformed addresses in
|
||||
email.parseaddr() (#111116)
|
||||
|
||||
Detect email address parsing errors and return empty tuple to
|
||||
indicate the parsing error (old API). Add an optional 'strict'
|
||||
parameter to getaddresses() and parseaddr() functions. Patch by
|
||||
Thomas Dwyer.
|
||||
|
||||
Co-Authored-By: Thomas Dwyer <github@tomd.tel>
|
||||
---
|
||||
Doc/library/email.utils.rst | 19 -
|
||||
Lib/email/utils.py | 151 +++++++-
|
||||
Lib/test/test_email/test_email.py | 187 +++++++++-
|
||||
Misc/NEWS.d/next/Library/2023-10-20-15-28-08.gh-issue-102988.dStNO7.rst | 8
|
||||
4 files changed, 344 insertions(+), 21 deletions(-)
|
||||
create mode 100644 Misc/NEWS.d/next/Library/2023-10-20-15-28-08.gh-issue-102988.dStNO7.rst
|
||||
|
||||
--- a/Doc/library/email.utils.rst
|
||||
+++ b/Doc/library/email.utils.rst
|
||||
@@ -58,13 +58,18 @@ of the new API.
|
||||
begins with angle brackets, they are stripped off.
|
||||
|
||||
|
||||
-.. function:: parseaddr(address)
|
||||
+.. function:: parseaddr(address, *, strict=True)
|
||||
|
||||
Parse address -- which should be the value of some address-containing field such
|
||||
as :mailheader:`To` or :mailheader:`Cc` -- into its constituent *realname* and
|
||||
*email address* parts. Returns a tuple of that information, unless the parse
|
||||
fails, in which case a 2-tuple of ``('', '')`` is returned.
|
||||
|
||||
+ If *strict* is true, use a strict parser which rejects malformed inputs.
|
||||
+
|
||||
+ .. versionchanged:: 3.13
|
||||
+ Add *strict* optional parameter and reject malformed inputs by default.
|
||||
+
|
||||
|
||||
.. function:: formataddr(pair, charset='utf-8')
|
||||
|
||||
@@ -82,12 +87,15 @@ of the new API.
|
||||
Added the *charset* option.
|
||||
|
||||
|
||||
-.. function:: getaddresses(fieldvalues)
|
||||
+.. function:: getaddresses(fieldvalues, *, strict=True)
|
||||
|
||||
This method returns a list of 2-tuples of the form returned by ``parseaddr()``.
|
||||
*fieldvalues* is a sequence of header field values as might be returned by
|
||||
- :meth:`Message.get_all <email.message.Message.get_all>`. Here's a simple
|
||||
- example that gets all the recipients of a message::
|
||||
+ :meth:`Message.get_all <email.message.Message.get_all>`.
|
||||
+
|
||||
+ If *strict* is true, use a strict parser which rejects malformed inputs.
|
||||
+
|
||||
+ Here's a simple example that gets all the recipients of a message::
|
||||
|
||||
from email.utils import getaddresses
|
||||
|
||||
@@ -97,6 +105,9 @@ of the new API.
|
||||
resent_ccs = msg.get_all('resent-cc', [])
|
||||
all_recipients = getaddresses(tos + ccs + resent_tos + resent_ccs)
|
||||
|
||||
+ .. versionchanged:: 3.13
|
||||
+ Add *strict* optional parameter and reject malformed inputs by default.
|
||||
+
|
||||
|
||||
.. function:: parsedate(date)
|
||||
|
||||
--- a/Lib/email/utils.py
|
||||
+++ b/Lib/email/utils.py
|
||||
@@ -48,6 +48,7 @@ TICK = "'"
|
||||
specialsre = re.compile(r'[][\\()<>@,:;".]')
|
||||
escapesre = re.compile(r'[\\"]')
|
||||
|
||||
+
|
||||
def _has_surrogates(s):
|
||||
"""Return True if s may contain surrogate-escaped binary data."""
|
||||
# This check is based on the fact that unless there are surrogates, utf8
|
||||
@@ -106,12 +107,127 @@ def formataddr(pair, charset='utf-8'):
|
||||
return address
|
||||
|
||||
|
||||
+def _iter_escaped_chars(addr):
|
||||
+ pos = 0
|
||||
+ escape = False
|
||||
+ for pos, ch in enumerate(addr):
|
||||
+ if escape:
|
||||
+ yield (pos, '\\' + ch)
|
||||
+ escape = False
|
||||
+ elif ch == '\\':
|
||||
+ escape = True
|
||||
+ else:
|
||||
+ yield (pos, ch)
|
||||
+ if escape:
|
||||
+ yield (pos, '\\')
|
||||
+
|
||||
+
|
||||
+def _strip_quoted_realnames(addr):
|
||||
+ """Strip real names between quotes."""
|
||||
+ if '"' not in addr:
|
||||
+ # Fast path
|
||||
+ return addr
|
||||
+
|
||||
+ start = 0
|
||||
+ open_pos = None
|
||||
+ result = []
|
||||
+ for pos, ch in _iter_escaped_chars(addr):
|
||||
+ if ch == '"':
|
||||
+ if open_pos is None:
|
||||
+ open_pos = pos
|
||||
+ else:
|
||||
+ if start != open_pos:
|
||||
+ result.append(addr[start:open_pos])
|
||||
+ start = pos + 1
|
||||
+ open_pos = None
|
||||
|
||||
-def getaddresses(fieldvalues):
|
||||
- """Return a list of (REALNAME, EMAIL) for each fieldvalue."""
|
||||
- all = COMMASPACE.join(str(v) for v in fieldvalues)
|
||||
- a = _AddressList(all)
|
||||
- return a.addresslist
|
||||
+ if start < len(addr):
|
||||
+ result.append(addr[start:])
|
||||
+
|
||||
+ return ''.join(result)
|
||||
+
|
||||
+
|
||||
+supports_strict_parsing = True
|
||||
+
|
||||
+def getaddresses(fieldvalues, *, strict=True):
|
||||
+ """Return a list of (REALNAME, EMAIL) or ('','') for each fieldvalue.
|
||||
+
|
||||
+ When parsing fails for a fieldvalue, a 2-tuple of ('', '') is returned in
|
||||
+ its place.
|
||||
+
|
||||
+ If strict is true, use a strict parser which rejects malformed inputs.
|
||||
+ """
|
||||
+
|
||||
+ # If strict is true, if the resulting list of parsed addresses is greater
|
||||
+ # than the number of fieldvalues in the input list, a parsing error has
|
||||
+ # occurred and consequently a list containing a single empty 2-tuple [('',
|
||||
+ # '')] is returned in its place. This is done to avoid invalid output.
|
||||
+ #
|
||||
+ # Malformed input: getaddresses(['alice@example.com <bob@example.com>'])
|
||||
+ # Invalid output: [('', 'alice@example.com'), ('', 'bob@example.com')]
|
||||
+ # Safe output: [('', '')]
|
||||
+
|
||||
+ if not strict:
|
||||
+ all = COMMASPACE.join(str(v) for v in fieldvalues)
|
||||
+ a = _AddressList(all)
|
||||
+ return a.addresslist
|
||||
+
|
||||
+ fieldvalues = [str(v) for v in fieldvalues]
|
||||
+ fieldvalues = _pre_parse_validation(fieldvalues)
|
||||
+ addr = COMMASPACE.join(fieldvalues)
|
||||
+ a = _AddressList(addr)
|
||||
+ result = _post_parse_validation(a.addresslist)
|
||||
+
|
||||
+ # Treat output as invalid if the number of addresses is not equal to the
|
||||
+ # expected number of addresses.
|
||||
+ n = 0
|
||||
+ for v in fieldvalues:
|
||||
+ # When a comma is used in the Real Name part it is not a deliminator.
|
||||
+ # So strip those out before counting the commas.
|
||||
+ v = _strip_quoted_realnames(v)
|
||||
+ # Expected number of addresses: 1 + number of commas
|
||||
+ n += 1 + v.count(',')
|
||||
+ if len(result) != n:
|
||||
+ return [('', '')]
|
||||
+
|
||||
+ return result
|
||||
+
|
||||
+
|
||||
+def _check_parenthesis(addr):
|
||||
+ # Ignore parenthesis in quoted real names.
|
||||
+ addr = _strip_quoted_realnames(addr)
|
||||
+
|
||||
+ opens = 0
|
||||
+ for pos, ch in _iter_escaped_chars(addr):
|
||||
+ if ch == '(':
|
||||
+ opens += 1
|
||||
+ elif ch == ')':
|
||||
+ opens -= 1
|
||||
+ if opens < 0:
|
||||
+ return False
|
||||
+ return (opens == 0)
|
||||
+
|
||||
+
|
||||
+def _pre_parse_validation(email_header_fields):
|
||||
+ accepted_values = []
|
||||
+ for v in email_header_fields:
|
||||
+ if not _check_parenthesis(v):
|
||||
+ v = "('', '')"
|
||||
+ accepted_values.append(v)
|
||||
+
|
||||
+ return accepted_values
|
||||
+
|
||||
+
|
||||
+def _post_parse_validation(parsed_email_header_tuples):
|
||||
+ accepted_values = []
|
||||
+ # The parser would have parsed a correctly formatted domain-literal
|
||||
+ # The existence of an [ after parsing indicates a parsing failure
|
||||
+ for v in parsed_email_header_tuples:
|
||||
+ if '[' in v[1]:
|
||||
+ v = ('', '')
|
||||
+ accepted_values.append(v)
|
||||
+
|
||||
+ return accepted_values
|
||||
|
||||
|
||||
def _format_timetuple_and_zone(timetuple, zone):
|
||||
@@ -205,16 +321,33 @@ def parsedate_to_datetime(data):
|
||||
tzinfo=datetime.timezone(datetime.timedelta(seconds=tz)))
|
||||
|
||||
|
||||
-def parseaddr(addr):
|
||||
+def parseaddr(addr, *, strict=True):
|
||||
"""
|
||||
Parse addr into its constituent realname and email address parts.
|
||||
|
||||
Return a tuple of realname and email address, unless the parse fails, in
|
||||
which case return a 2-tuple of ('', '').
|
||||
+
|
||||
+ If strict is True, use a strict parser which rejects malformed inputs.
|
||||
"""
|
||||
- addrs = _AddressList(addr).addresslist
|
||||
- if not addrs:
|
||||
- return '', ''
|
||||
+ if not strict:
|
||||
+ addrs = _AddressList(addr).addresslist
|
||||
+ if not addrs:
|
||||
+ return ('', '')
|
||||
+ return addrs[0]
|
||||
+
|
||||
+ if isinstance(addr, list):
|
||||
+ addr = addr[0]
|
||||
+
|
||||
+ if not isinstance(addr, str):
|
||||
+ return ('', '')
|
||||
+
|
||||
+ addr = _pre_parse_validation([addr])[0]
|
||||
+ addrs = _post_parse_validation(_AddressList(addr).addresslist)
|
||||
+
|
||||
+ if not addrs or len(addrs) > 1:
|
||||
+ return ('', '')
|
||||
+
|
||||
return addrs[0]
|
||||
|
||||
|
||||
--- a/Lib/test/test_email/test_email.py
|
||||
+++ b/Lib/test/test_email/test_email.py
|
||||
@@ -16,6 +16,7 @@ from unittest.mock import patch
|
||||
|
||||
import email
|
||||
import email.policy
|
||||
+import email.utils
|
||||
|
||||
from email.charset import Charset
|
||||
from email.generator import Generator, DecodedGenerator, BytesGenerator
|
||||
@@ -3352,15 +3353,137 @@ Foo
|
||||
],
|
||||
)
|
||||
|
||||
+ def test_parsing_errors(self):
|
||||
+ """Test for parsing errors from CVE-2023-27043 and CVE-2019-16056"""
|
||||
+ alice = 'alice@example.org'
|
||||
+ bob = 'bob@example.com'
|
||||
+ empty = ('', '')
|
||||
+
|
||||
+ # Test utils.getaddresses() and utils.parseaddr() on malformed email
|
||||
+ # addresses: default behavior (strict=True) rejects malformed address,
|
||||
+ # and strict=False which tolerates malformed address.
|
||||
+ for invalid_separator, expected_non_strict in (
|
||||
+ ('(', [(f'<{bob}>', alice)]),
|
||||
+ (')', [('', alice), empty, ('', bob)]),
|
||||
+ ('<', [('', alice), empty, ('', bob), empty]),
|
||||
+ ('>', [('', alice), empty, ('', bob)]),
|
||||
+ ('[', [('', f'{alice}[<{bob}>]')]),
|
||||
+ (']', [('', alice), empty, ('', bob)]),
|
||||
+ ('@', [empty, empty, ('', bob)]),
|
||||
+ (';', [('', alice), empty, ('', bob)]),
|
||||
+ (':', [('', alice), ('', bob)]),
|
||||
+ ('.', [('', alice + '.'), ('', bob)]),
|
||||
+ ('"', [('', alice), ('', f'<{bob}>')]),
|
||||
+ ):
|
||||
+ address = f'{alice}{invalid_separator}<{bob}>'
|
||||
+ with self.subTest(address=address):
|
||||
+ self.assertEqual(utils.getaddresses([address]),
|
||||
+ [empty])
|
||||
+ self.assertEqual(utils.getaddresses([address], strict=False),
|
||||
+ expected_non_strict)
|
||||
+
|
||||
+ self.assertEqual(utils.parseaddr([address]),
|
||||
+ empty)
|
||||
+ self.assertEqual(utils.parseaddr([address], strict=False),
|
||||
+ ('', address))
|
||||
+
|
||||
+ # Comma (',') is treated differently depending on strict parameter.
|
||||
+ # Comma without quotes.
|
||||
+ address = f'{alice},<{bob}>'
|
||||
+ self.assertEqual(utils.getaddresses([address]),
|
||||
+ [('', alice), ('', bob)])
|
||||
+ self.assertEqual(utils.getaddresses([address], strict=False),
|
||||
+ [('', alice), ('', bob)])
|
||||
+ self.assertEqual(utils.parseaddr([address]),
|
||||
+ empty)
|
||||
+ self.assertEqual(utils.parseaddr([address], strict=False),
|
||||
+ ('', address))
|
||||
+
|
||||
+ # Real name between quotes containing comma.
|
||||
+ address = '"Alice, alice@example.org" <bob@example.com>'
|
||||
+ expected_strict = ('Alice, alice@example.org', 'bob@example.com')
|
||||
+ self.assertEqual(utils.getaddresses([address]), [expected_strict])
|
||||
+ self.assertEqual(utils.getaddresses([address], strict=False), [expected_strict])
|
||||
+ self.assertEqual(utils.parseaddr([address]), expected_strict)
|
||||
+ self.assertEqual(utils.parseaddr([address], strict=False),
|
||||
+ ('', address))
|
||||
+
|
||||
+ # Valid parenthesis in comments.
|
||||
+ address = 'alice@example.org (Alice)'
|
||||
+ expected_strict = ('Alice', 'alice@example.org')
|
||||
+ self.assertEqual(utils.getaddresses([address]), [expected_strict])
|
||||
+ self.assertEqual(utils.getaddresses([address], strict=False), [expected_strict])
|
||||
+ self.assertEqual(utils.parseaddr([address]), expected_strict)
|
||||
+ self.assertEqual(utils.parseaddr([address], strict=False),
|
||||
+ ('', address))
|
||||
+
|
||||
+ # Invalid parenthesis in comments.
|
||||
+ address = 'alice@example.org )Alice('
|
||||
+ self.assertEqual(utils.getaddresses([address]), [empty])
|
||||
+ self.assertEqual(utils.getaddresses([address], strict=False),
|
||||
+ [('', 'alice@example.org'), ('', ''), ('', 'Alice')])
|
||||
+ self.assertEqual(utils.parseaddr([address]), empty)
|
||||
+ self.assertEqual(utils.parseaddr([address], strict=False),
|
||||
+ ('', address))
|
||||
+
|
||||
+ # Two addresses with quotes separated by comma.
|
||||
+ address = '"Jane Doe" <jane@example.net>, "John Doe" <john@example.net>'
|
||||
+ self.assertEqual(utils.getaddresses([address]),
|
||||
+ [('Jane Doe', 'jane@example.net'),
|
||||
+ ('John Doe', 'john@example.net')])
|
||||
+ self.assertEqual(utils.getaddresses([address], strict=False),
|
||||
+ [('Jane Doe', 'jane@example.net'),
|
||||
+ ('John Doe', 'john@example.net')])
|
||||
+ self.assertEqual(utils.parseaddr([address]), empty)
|
||||
+ self.assertEqual(utils.parseaddr([address], strict=False),
|
||||
+ ('', address))
|
||||
+
|
||||
+ # Test email.utils.supports_strict_parsing attribute
|
||||
+ self.assertEqual(email.utils.supports_strict_parsing, True)
|
||||
+
|
||||
def test_getaddresses_nasty(self):
|
||||
- eq = self.assertEqual
|
||||
- eq(utils.getaddresses(['foo: ;']), [('', '')])
|
||||
- eq(utils.getaddresses(
|
||||
- ['[]*-- =~$']),
|
||||
- [('', ''), ('', ''), ('', '*--')])
|
||||
- eq(utils.getaddresses(
|
||||
- ['foo: ;', '"Jason R. Mastaler" <jason@dom.ain>']),
|
||||
- [('', ''), ('Jason R. Mastaler', 'jason@dom.ain')])
|
||||
+ for addresses, expected in (
|
||||
+ (['"Sürname, Firstname" <to@example.com>'],
|
||||
+ [('Sürname, Firstname', 'to@example.com')]),
|
||||
+
|
||||
+ (['foo: ;'],
|
||||
+ [('', '')]),
|
||||
+
|
||||
+ (['foo: ;', '"Jason R. Mastaler" <jason@dom.ain>'],
|
||||
+ [('', ''), ('Jason R. Mastaler', 'jason@dom.ain')]),
|
||||
+
|
||||
+ ([r'Pete(A nice \) chap) <pete(his account)@silly.test(his host)>'],
|
||||
+ [('Pete (A nice ) chap his account his host)', 'pete@silly.test')]),
|
||||
+
|
||||
+ (['(Empty list)(start)Undisclosed recipients :(nobody(I know))'],
|
||||
+ [('', '')]),
|
||||
+
|
||||
+ (['Mary <@machine.tld:mary@example.net>, , jdoe@test . example'],
|
||||
+ [('Mary', 'mary@example.net'), ('', ''), ('', 'jdoe@test.example')]),
|
||||
+
|
||||
+ (['John Doe <jdoe@machine(comment). example>'],
|
||||
+ [('John Doe (comment)', 'jdoe@machine.example')]),
|
||||
+
|
||||
+ (['"Mary Smith: Personal Account" <smith@home.example>'],
|
||||
+ [('Mary Smith: Personal Account', 'smith@home.example')]),
|
||||
+
|
||||
+ (['Undisclosed recipients:;'],
|
||||
+ [('', '')]),
|
||||
+
|
||||
+ ([r'<boss@nil.test>, "Giant; \"Big\" Box" <bob@example.net>'],
|
||||
+ [('', 'boss@nil.test'), ('Giant; "Big" Box', 'bob@example.net')]),
|
||||
+ ):
|
||||
+ with self.subTest(addresses=addresses):
|
||||
+ self.assertEqual(utils.getaddresses(addresses),
|
||||
+ expected)
|
||||
+ self.assertEqual(utils.getaddresses(addresses, strict=False),
|
||||
+ expected)
|
||||
+
|
||||
+ addresses = ['[]*-- =~$']
|
||||
+ self.assertEqual(utils.getaddresses(addresses),
|
||||
+ [('', '')])
|
||||
+ self.assertEqual(utils.getaddresses(addresses, strict=False),
|
||||
+ [('', ''), ('', ''), ('', '*--')])
|
||||
|
||||
def test_getaddresses_embedded_comment(self):
|
||||
"""Test proper handling of a nested comment"""
|
||||
@@ -3551,6 +3674,54 @@ multipart/report
|
||||
m = cls(*constructor, policy=email.policy.default)
|
||||
self.assertIs(m.policy, email.policy.default)
|
||||
|
||||
+ def test_iter_escaped_chars(self):
|
||||
+ self.assertEqual(list(utils._iter_escaped_chars(r'a\\b\"c\\"d')),
|
||||
+ [(0, 'a'),
|
||||
+ (2, '\\\\'),
|
||||
+ (3, 'b'),
|
||||
+ (5, '\\"'),
|
||||
+ (6, 'c'),
|
||||
+ (8, '\\\\'),
|
||||
+ (9, '"'),
|
||||
+ (10, 'd')])
|
||||
+ self.assertEqual(list(utils._iter_escaped_chars('a\\')),
|
||||
+ [(0, 'a'), (1, '\\')])
|
||||
+
|
||||
+ def test_strip_quoted_realnames(self):
|
||||
+ def check(addr, expected):
|
||||
+ self.assertEqual(utils._strip_quoted_realnames(addr), expected)
|
||||
+
|
||||
+ check('"Jane Doe" <jane@example.net>, "John Doe" <john@example.net>',
|
||||
+ ' <jane@example.net>, <john@example.net>')
|
||||
+ check(r'"Jane \"Doe\"." <jane@example.net>',
|
||||
+ ' <jane@example.net>')
|
||||
+
|
||||
+ # special cases
|
||||
+ check(r'before"name"after', 'beforeafter')
|
||||
+ check(r'before"name"', 'before')
|
||||
+ check(r'b"name"', 'b') # single char
|
||||
+ check(r'"name"after', 'after')
|
||||
+ check(r'"name"a', 'a') # single char
|
||||
+ check(r'"name"', '')
|
||||
+
|
||||
+ # no change
|
||||
+ for addr in (
|
||||
+ 'Jane Doe <jane@example.net>, John Doe <john@example.net>',
|
||||
+ 'lone " quote',
|
||||
+ ):
|
||||
+ self.assertEqual(utils._strip_quoted_realnames(addr), addr)
|
||||
+
|
||||
+
|
||||
+ def test_check_parenthesis(self):
|
||||
+ addr = 'alice@example.net'
|
||||
+ self.assertTrue(utils._check_parenthesis(f'{addr} (Alice)'))
|
||||
+ self.assertFalse(utils._check_parenthesis(f'{addr} )Alice('))
|
||||
+ self.assertFalse(utils._check_parenthesis(f'{addr} (Alice))'))
|
||||
+ self.assertFalse(utils._check_parenthesis(f'{addr} ((Alice)'))
|
||||
+
|
||||
+ # Ignore real name between quotes
|
||||
+ self.assertTrue(utils._check_parenthesis(f'")Alice((" {addr}'))
|
||||
+
|
||||
|
||||
# Test the iterator/generators
|
||||
class TestIterators(TestEmailBase):
|
||||
--- /dev/null
|
||||
+++ b/Misc/NEWS.d/next/Library/2023-10-20-15-28-08.gh-issue-102988.dStNO7.rst
|
||||
@@ -0,0 +1,8 @@
|
||||
+:func:`email.utils.getaddresses` and :func:`email.utils.parseaddr` now
|
||||
+return ``('', '')`` 2-tuples in more situations where invalid email
|
||||
+addresses are encountered instead of potentially inaccurate values. Add
|
||||
+optional *strict* parameter to these two functions: use ``strict=False`` to
|
||||
+get the old behavior, accept malformed inputs.
|
||||
+``getattr(email.utils, 'supports_strict_parsing', False)`` can be use to check
|
||||
+if the *strict* paramater is available. Patch by Thomas Dwyer and Victor
|
||||
+Stinner to improve the CVE-2023-27043 fix.
|
||||
@@ -1,67 +0,0 @@
|
||||
Index: Python-3.12.3/Lib/test/test_xml_etree.py
|
||||
===================================================================
|
||||
--- Python-3.12.3.orig/Lib/test/test_xml_etree.py
|
||||
+++ Python-3.12.3/Lib/test/test_xml_etree.py
|
||||
@@ -121,6 +121,11 @@ ATTLIST_XML = """\
|
||||
</foo>
|
||||
"""
|
||||
|
||||
+IS_SLE_15_6 = os.environ.get("SLE_VERSION", "") == "0150600"
|
||||
+fails_with_expat_2_6_0 = (unittest.expectedFailure
|
||||
+ # 2.4 version patched in SLE
|
||||
+ if IS_SLE_15_6 and pyexpat.version_info >= (2, 4, 0) else
|
||||
+ lambda test: test)
|
||||
def checkwarnings(*filters, quiet=False):
|
||||
def decorator(test):
|
||||
def newtest(*args, **kwargs):
|
||||
@@ -1424,9 +1429,11 @@ class XMLPullParserTest(unittest.TestCas
|
||||
self.assert_event_tags(parser, [('end', 'root')])
|
||||
self.assertIsNone(parser.close())
|
||||
|
||||
+ @fails_with_expat_2_6_0
|
||||
def test_simple_xml_chunk_1(self):
|
||||
self.test_simple_xml(chunk_size=1, flush=True)
|
||||
|
||||
+ @fails_with_expat_2_6_0
|
||||
def test_simple_xml_chunk_5(self):
|
||||
self.test_simple_xml(chunk_size=5, flush=True)
|
||||
|
||||
@@ -1651,6 +1658,9 @@ class XMLPullParserTest(unittest.TestCas
|
||||
|
||||
self.assert_event_tags(parser, [('end', 'doc')])
|
||||
|
||||
+ @unittest.skipIf(pyexpat.version_info < (2, 6, 0),
|
||||
+ f'Expat {pyexpat.version_info} does not '
|
||||
+ 'support reparse deferral')
|
||||
def test_flush_reparse_deferral_disabled(self):
|
||||
parser = ET.XMLPullParser(events=('start', 'end'))
|
||||
|
||||
Index: Python-3.12.3/Lib/test/test_sax.py
|
||||
===================================================================
|
||||
--- Python-3.12.3.orig/Lib/test/test_sax.py
|
||||
+++ Python-3.12.3/Lib/test/test_sax.py
|
||||
@@ -1240,6 +1240,9 @@ class ExpatReaderTest(XmlTestBase):
|
||||
|
||||
self.assertEqual(result.getvalue(), start + b"<doc></doc>")
|
||||
|
||||
+ @unittest.skipIf(pyexpat.version_info < (2, 6, 0),
|
||||
+ f'Expat {pyexpat.version_info} does not '
|
||||
+ 'support reparse deferral')
|
||||
def test_flush_reparse_deferral_disabled(self):
|
||||
result = BytesIO()
|
||||
xmlgen = XMLGenerator(result)
|
||||
Index: Python-3.12.3/Lib/test/test_pyexpat.py
|
||||
===================================================================
|
||||
--- Python-3.12.3.orig/Lib/test/test_pyexpat.py
|
||||
+++ Python-3.12.3/Lib/test/test_pyexpat.py
|
||||
@@ -794,6 +794,10 @@ class ReparseDeferralTest(unittest.TestC
|
||||
self.assertEqual(started, ['doc'])
|
||||
|
||||
def test_reparse_deferral_disabled(self):
|
||||
+ if expat.version_info < (2, 6, 0):
|
||||
+ self.skipTest(f'Expat {expat.version_info} does not '
|
||||
+ 'support reparse deferral')
|
||||
+
|
||||
started = []
|
||||
|
||||
def start_element(name, _):
|
||||
@@ -1,165 +0,0 @@
|
||||
---
|
||||
Lib/tempfile.py | 16 +
|
||||
Lib/test/test_tempfile.py | 113 ++++++++++
|
||||
Misc/NEWS.d/next/Library/2022-12-01-16-57-44.gh-issue-91133.LKMVCV.rst | 2
|
||||
3 files changed, 131 insertions(+)
|
||||
|
||||
--- a/Lib/tempfile.py
|
||||
+++ b/Lib/tempfile.py
|
||||
@@ -285,6 +285,22 @@ def _resetperms(path):
|
||||
_dont_follow_symlinks(chflags, path, 0)
|
||||
_dont_follow_symlinks(_os.chmod, path, 0o700)
|
||||
|
||||
+def _dont_follow_symlinks(func, path, *args):
|
||||
+ # Pass follow_symlinks=False, unless not supported on this platform.
|
||||
+ if func in _os.supports_follow_symlinks:
|
||||
+ func(path, *args, follow_symlinks=False)
|
||||
+ elif _os.name == 'nt' or not _os.path.islink(path):
|
||||
+ func(path, *args)
|
||||
+
|
||||
+def _resetperms(path):
|
||||
+ try:
|
||||
+ chflags = _os.chflags
|
||||
+ except AttributeError:
|
||||
+ pass
|
||||
+ else:
|
||||
+ _dont_follow_symlinks(chflags, path, 0)
|
||||
+ _dont_follow_symlinks(_os.chmod, path, 0o700)
|
||||
+
|
||||
|
||||
# User visible interfaces.
|
||||
|
||||
--- a/Lib/test/test_tempfile.py
|
||||
+++ b/Lib/test/test_tempfile.py
|
||||
@@ -1781,6 +1781,103 @@ class TestTemporaryDirectory(BaseTestCas
|
||||
new_flags = os.stat(dir1).st_flags
|
||||
self.assertEqual(new_flags, old_flags)
|
||||
|
||||
+ @os_helper.skip_unless_symlink
|
||||
+ def test_cleanup_with_symlink_modes(self):
|
||||
+ # cleanup() should not follow symlinks when fixing mode bits (#91133)
|
||||
+ with self.do_create(recurse=0) as d2:
|
||||
+ file1 = os.path.join(d2, 'file1')
|
||||
+ open(file1, 'wb').close()
|
||||
+ dir1 = os.path.join(d2, 'dir1')
|
||||
+ os.mkdir(dir1)
|
||||
+ for mode in range(8):
|
||||
+ mode <<= 6
|
||||
+ with self.subTest(mode=format(mode, '03o')):
|
||||
+ def test(target, target_is_directory):
|
||||
+ d1 = self.do_create(recurse=0)
|
||||
+ symlink = os.path.join(d1.name, 'symlink')
|
||||
+ os.symlink(target, symlink,
|
||||
+ target_is_directory=target_is_directory)
|
||||
+ try:
|
||||
+ os.chmod(symlink, mode, follow_symlinks=False)
|
||||
+ except NotImplementedError:
|
||||
+ pass
|
||||
+ try:
|
||||
+ os.chmod(symlink, mode)
|
||||
+ except FileNotFoundError:
|
||||
+ pass
|
||||
+ os.chmod(d1.name, mode)
|
||||
+ d1.cleanup()
|
||||
+ self.assertFalse(os.path.exists(d1.name))
|
||||
+
|
||||
+ with self.subTest('nonexisting file'):
|
||||
+ test('nonexisting', target_is_directory=False)
|
||||
+ with self.subTest('nonexisting dir'):
|
||||
+ test('nonexisting', target_is_directory=True)
|
||||
+
|
||||
+ with self.subTest('existing file'):
|
||||
+ os.chmod(file1, mode)
|
||||
+ old_mode = os.stat(file1).st_mode
|
||||
+ test(file1, target_is_directory=False)
|
||||
+ new_mode = os.stat(file1).st_mode
|
||||
+ self.assertEqual(new_mode, old_mode,
|
||||
+ '%03o != %03o' % (new_mode, old_mode))
|
||||
+
|
||||
+ with self.subTest('existing dir'):
|
||||
+ os.chmod(dir1, mode)
|
||||
+ old_mode = os.stat(dir1).st_mode
|
||||
+ test(dir1, target_is_directory=True)
|
||||
+ new_mode = os.stat(dir1).st_mode
|
||||
+ self.assertEqual(new_mode, old_mode,
|
||||
+ '%03o != %03o' % (new_mode, old_mode))
|
||||
+
|
||||
+ @unittest.skipUnless(hasattr(os, 'chflags'), 'requires os.chflags')
|
||||
+ @os_helper.skip_unless_symlink
|
||||
+ def test_cleanup_with_symlink_flags(self):
|
||||
+ # cleanup() should not follow symlinks when fixing flags (#91133)
|
||||
+ flags = stat.UF_IMMUTABLE | stat.UF_NOUNLINK
|
||||
+ self.check_flags(flags)
|
||||
+
|
||||
+ with self.do_create(recurse=0) as d2:
|
||||
+ file1 = os.path.join(d2, 'file1')
|
||||
+ open(file1, 'wb').close()
|
||||
+ dir1 = os.path.join(d2, 'dir1')
|
||||
+ os.mkdir(dir1)
|
||||
+ def test(target, target_is_directory):
|
||||
+ d1 = self.do_create(recurse=0)
|
||||
+ symlink = os.path.join(d1.name, 'symlink')
|
||||
+ os.symlink(target, symlink,
|
||||
+ target_is_directory=target_is_directory)
|
||||
+ try:
|
||||
+ os.chflags(symlink, flags, follow_symlinks=False)
|
||||
+ except NotImplementedError:
|
||||
+ pass
|
||||
+ try:
|
||||
+ os.chflags(symlink, flags)
|
||||
+ except FileNotFoundError:
|
||||
+ pass
|
||||
+ os.chflags(d1.name, flags)
|
||||
+ d1.cleanup()
|
||||
+ self.assertFalse(os.path.exists(d1.name))
|
||||
+
|
||||
+ with self.subTest('nonexisting file'):
|
||||
+ test('nonexisting', target_is_directory=False)
|
||||
+ with self.subTest('nonexisting dir'):
|
||||
+ test('nonexisting', target_is_directory=True)
|
||||
+
|
||||
+ with self.subTest('existing file'):
|
||||
+ os.chflags(file1, flags)
|
||||
+ old_flags = os.stat(file1).st_flags
|
||||
+ test(file1, target_is_directory=False)
|
||||
+ new_flags = os.stat(file1).st_flags
|
||||
+ self.assertEqual(new_flags, old_flags)
|
||||
+
|
||||
+ with self.subTest('existing dir'):
|
||||
+ os.chflags(dir1, flags)
|
||||
+ old_flags = os.stat(dir1).st_flags
|
||||
+ test(dir1, target_is_directory=True)
|
||||
+ new_flags = os.stat(dir1).st_flags
|
||||
+ self.assertEqual(new_flags, old_flags)
|
||||
+
|
||||
@support.cpython_only
|
||||
def test_del_on_collection(self):
|
||||
# A TemporaryDirectory is deleted when garbage collected
|
||||
@@ -1955,6 +2052,22 @@ class TestTemporaryDirectory(BaseTestCas
|
||||
|
||||
def check_flags(self, flags):
|
||||
# skip the test if these flags are not supported (ex: FreeBSD 13)
|
||||
+ filename = os_helper.TESTFN
|
||||
+ try:
|
||||
+ open(filename, "w").close()
|
||||
+ try:
|
||||
+ os.chflags(filename, flags)
|
||||
+ except OSError as exc:
|
||||
+ # "OSError: [Errno 45] Operation not supported"
|
||||
+ self.skipTest(f"chflags() doesn't support flags "
|
||||
+ f"{flags:#b}: {exc}")
|
||||
+ else:
|
||||
+ os.chflags(filename, 0)
|
||||
+ finally:
|
||||
+ os_helper.unlink(filename)
|
||||
+
|
||||
+ def check_flags(self, flags):
|
||||
+ # skip the test if these flags are not supported (ex: FreeBSD 13)
|
||||
filename = os_helper.TESTFN
|
||||
try:
|
||||
open(filename, "w").close()
|
||||
--- /dev/null
|
||||
+++ b/Misc/NEWS.d/next/Library/2022-12-01-16-57-44.gh-issue-91133.LKMVCV.rst
|
||||
@@ -0,0 +1,2 @@
|
||||
+Fix a bug in :class:`tempfile.TemporaryDirectory` cleanup, which now no longer
|
||||
+dereferences symlinks when working around file system permission errors.
|
||||
160
F00251-change-user-install-location.patch
Normal file
160
F00251-change-user-install-location.patch
Normal file
@@ -0,0 +1,160 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Miro=20Hron=C4=8Dok?= <miro@hroncok.cz>
|
||||
Date: Mon, 15 Feb 2021 12:19:27 +0100
|
||||
Subject: [PATCH] 00251: Change user install location
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
|
||||
Set values of base and platbase in sysconfig from /usr
|
||||
to /usr/local when RPM build is not detected
|
||||
to make pip and similar tools install into separate location.
|
||||
|
||||
Fedora Change: https://fedoraproject.org/wiki/Changes/Making_sudo_pip_safe
|
||||
Downstream only.
|
||||
|
||||
We've tried to rework in Fedora 36/Python 3.10 to follow https://bugs.python.org/issue43976
|
||||
but we have identified serious problems with that approach,
|
||||
see https://bugzilla.redhat.com/2026979 or https://bugzilla.redhat.com/2097183
|
||||
|
||||
pypa/distutils integration: https://github.com/pypa/distutils/pull/70
|
||||
|
||||
Co-authored-by: Petr Viktorin <encukou@gmail.com>
|
||||
Co-authored-by: Miro Hrončok <miro@hroncok.cz>
|
||||
Co-authored-by: Michal Cyprian <m.cyprian@gmail.com>
|
||||
Co-authored-by: Lumír Balhar <frenzy.madness@gmail.com>
|
||||
---
|
||||
Lib/sysconfig.py | 51 ++++++++++++++++++++++++++++++++++++++++++++-
|
||||
Lib/test/test_sysconfig.py | 17 +++++++++++++--
|
||||
2 files changed, 65 insertions(+), 3 deletions(-)
|
||||
|
||||
Index: Python-3.12.10/Lib/sysconfig.py
|
||||
===================================================================
|
||||
--- Python-3.12.10.orig/Lib/sysconfig.py 2025-04-11 21:04:43.494305425 +0200
|
||||
+++ Python-3.12.10/Lib/sysconfig.py 2025-04-11 21:04:51.517931810 +0200
|
||||
@@ -104,6 +104,11 @@
|
||||
else:
|
||||
_INSTALL_SCHEMES['venv'] = _INSTALL_SCHEMES['posix_venv']
|
||||
|
||||
+# For a brief period of time in the Fedora 36 life cycle,
|
||||
+# this installation scheme existed and was documented in the release notes.
|
||||
+# For backwards compatibility, we keep it here (at least on 3.10 and 3.11).
|
||||
+_INSTALL_SCHEMES['rpm_prefix'] = _INSTALL_SCHEMES['posix_prefix']
|
||||
+
|
||||
|
||||
# NOTE: site.py has copy of this function.
|
||||
# Sync it when modify this function.
|
||||
@@ -163,13 +168,28 @@
|
||||
},
|
||||
}
|
||||
|
||||
+# This is used by distutils.command.install in the stdlib
|
||||
+# as well as pypa/distutils (e.g. bundled in setuptools).
|
||||
+# The self.prefix value is set to sys.prefix + /local/
|
||||
+# if neither RPM build nor virtual environment is
|
||||
+# detected to make distutils install packages
|
||||
+# into the separate location.
|
||||
+# https://fedoraproject.org/wiki/Changes/Making_sudo_pip_safe
|
||||
+if (not (hasattr(sys, 'real_prefix') or
|
||||
+ sys.prefix != sys.base_prefix) and
|
||||
+ 'RPM_BUILD_ROOT' not in os.environ):
|
||||
+ _prefix_addition = '/local'
|
||||
+
|
||||
+
|
||||
_SCHEME_KEYS = ('stdlib', 'platstdlib', 'purelib', 'platlib', 'include',
|
||||
'scripts', 'data')
|
||||
|
||||
_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()
|
||||
@@ -268,11 +288,40 @@
|
||||
target_dict[key] = value
|
||||
|
||||
|
||||
+_CONFIG_VARS_LOCAL = None
|
||||
+
|
||||
+
|
||||
+def _config_vars_local():
|
||||
+ # This function returns the config vars with prefixes amended to /usr/local
|
||||
+ # https://fedoraproject.org/wiki/Changes/Making_sudo_pip_safe
|
||||
+ global _CONFIG_VARS_LOCAL
|
||||
+ if _CONFIG_VARS_LOCAL is None:
|
||||
+ _CONFIG_VARS_LOCAL = dict(get_config_vars())
|
||||
+ _CONFIG_VARS_LOCAL['base'] = '/usr/local'
|
||||
+ _CONFIG_VARS_LOCAL['platbase'] = '/usr/local'
|
||||
+ return _CONFIG_VARS_LOCAL
|
||||
+
|
||||
+
|
||||
def _expand_vars(scheme, vars):
|
||||
res = {}
|
||||
if vars is None:
|
||||
vars = {}
|
||||
- _extend_dict(vars, get_config_vars())
|
||||
+
|
||||
+ # when we are not in a virtual environment or an RPM build
|
||||
+ # we change '/usr' to '/usr/local'
|
||||
+ # to avoid surprises, we explicitly check for the /usr/ prefix
|
||||
+ # Python virtual environments have different prefixes
|
||||
+ # we only do this for posix_prefix, not to mangle the venv scheme
|
||||
+ # posix_prefix is used by sudo pip install
|
||||
+ # we only change the defaults here, so explicit --prefix will take precedence
|
||||
+ # https://fedoraproject.org/wiki/Changes/Making_sudo_pip_safe
|
||||
+ if (scheme == 'posix_prefix' and
|
||||
+ _PREFIX == '/usr' and
|
||||
+ 'RPM_BUILD_ROOT' not in os.environ):
|
||||
+ _extend_dict(vars, _config_vars_local())
|
||||
+ else:
|
||||
+ _extend_dict(vars, get_config_vars())
|
||||
+
|
||||
if os.name == 'nt':
|
||||
# On Windows we want to substitute 'lib' for schemes rather
|
||||
# than the native value (without modifying vars, in case it
|
||||
Index: Python-3.12.10/Lib/test/test_sysconfig.py
|
||||
===================================================================
|
||||
--- Python-3.12.10.orig/Lib/test/test_sysconfig.py 2025-04-11 21:04:45.175417431 +0200
|
||||
+++ Python-3.12.10/Lib/test/test_sysconfig.py 2025-04-11 21:04:51.518393464 +0200
|
||||
@@ -119,8 +119,19 @@
|
||||
for scheme in _INSTALL_SCHEMES:
|
||||
for name in _INSTALL_SCHEMES[scheme]:
|
||||
expected = _INSTALL_SCHEMES[scheme][name].format(**config_vars)
|
||||
+ tested = get_path(name, scheme)
|
||||
+ # https://fedoraproject.org/wiki/Changes/Making_sudo_pip_safe
|
||||
+ if tested.startswith('/usr/local'):
|
||||
+ # /usr/local should only be used in posix_prefix
|
||||
+ self.assertEqual(scheme, 'posix_prefix')
|
||||
+ # Fedora CI runs tests for venv and virtualenv that check for other prefixes
|
||||
+ self.assertEqual(sys.prefix, '/usr')
|
||||
+ # When building the RPM of Python, %check runs this with RPM_BUILD_ROOT set
|
||||
+ # Fedora CI runs this with RPM_BUILD_ROOT unset
|
||||
+ self.assertNotIn('RPM_BUILD_ROOT', os.environ)
|
||||
+ tested = tested.replace('/usr/local', '/usr')
|
||||
self.assertEqual(
|
||||
- os.path.normpath(get_path(name, scheme)),
|
||||
+ os.path.normpath(tested),
|
||||
os.path.normpath(expected),
|
||||
)
|
||||
|
||||
@@ -353,7 +364,7 @@
|
||||
self.assertTrue(os.path.isfile(config_h), config_h)
|
||||
|
||||
def test_get_scheme_names(self):
|
||||
- wanted = ['nt', 'posix_home', 'posix_prefix', 'posix_venv', 'nt_venv', 'venv']
|
||||
+ wanted = ['nt', 'posix_home', 'posix_prefix', 'posix_venv', 'nt_venv', 'venv', 'rpm_prefix']
|
||||
if HAS_USER_BASE:
|
||||
wanted.extend(['nt_user', 'osx_framework_user', 'posix_user'])
|
||||
self.assertEqual(get_scheme_names(), tuple(sorted(wanted)))
|
||||
@@ -365,6 +376,8 @@
|
||||
cmd = "-c", "import sysconfig; print(sysconfig.get_platform())"
|
||||
self.assertEqual(py.call_real(*cmd), py.call_link(*cmd))
|
||||
|
||||
+ @unittest.skipIf('RPM_BUILD_ROOT' not in os.environ,
|
||||
+ "Test doesn't expect Fedora's paths")
|
||||
def test_user_similar(self):
|
||||
# Issue #8759: make sure the posix scheme for the users
|
||||
# is similar to the global posix_prefix one
|
||||
3
Python-3.12.12.tar.xz
Normal file
3
Python-3.12.12.tar.xz
Normal file
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:fb85a13414b028c49ba18bbd523c2d055a30b56b18b92ce454ea2c51edc656c4
|
||||
size 20798712
|
||||
18
Python-3.12.12.tar.xz.asc
Normal file
18
Python-3.12.12.tar.xz.asc
Normal file
@@ -0,0 +1,18 @@
|
||||
-----BEGIN PGP SIGNATURE-----
|
||||
|
||||
iQKTBAABCgB9FiEEcWlgX2LHUTVtBUomqCHmgOX6YwUFAmjnnr1fFIAAAAAALgAo
|
||||
aXNzdWVyLWZwckBub3RhdGlvbnMub3BlbnBncC5maWZ0aGhvcnNlbWFuLm5ldDcx
|
||||
Njk2MDVGNjJDNzUxMzU2RDA1NEEyNkE4MjFFNjgwRTVGQTYzMDUACgkQqCHmgOX6
|
||||
YwXF3Q//VrreGa+P8lvp9UMjoj/YquKPwLqjzzAWf5vzHipkebdiESsB1HfGu04k
|
||||
Jw+ctTnXHf/12u0W7ijv+56JtcJFqEzh8yGokWqOzc99rpCeCY9qtuwaVYtZrTNx
|
||||
wepRaDAHdhP4Z2kLPDiE6pCXu2NIR5wHqHjQ8JGmprhASc07uxEhNN/gucVR2Sbr
|
||||
cCfC9rHfHkdhoPpZRRbcraAaxPGL3VyBXf7HuYbHhf4GuF9EVDlFg5I0BzHCKJDd
|
||||
ebPXYHvsoDgrMMqPXiX/YkGNByf3Ze6KZTNSGICy8SDzIzZgpmtOe5rzvlOXJBZZ
|
||||
SVfX8SqP4Ufml+MfJrGEx30S9reYYvnyTSmttpbDznonROKPEZOuDt08+CG3yR+T
|
||||
o5RdIneWmGXRf1mBrFKH9Br5tfOd+YeldfxdoQgla2fFHFVRnab1lsZFOC/HZ5z2
|
||||
Q3rPfVMDYKO8yoIKqv0BUzlkn9wYphCWoPHq0Y+SGjcP+Zh5qRTMqZYIaGekhWmx
|
||||
86egHHVqedMI0Q9hvgIEirupVJ1q34FZn2+3sEka9hdOie9aNHXWTmgWCGDm46qj
|
||||
qC9tT/jkMzWIY2Y4RdVDMdSCb7HkBEl1eAANq511gJ+eSWAXbP1sVrQoiAQY+EkC
|
||||
Yu2ceZYsl9i6zm7i/QaU/mOGB7xMZhMQLZBnZTHSzAZo/pBN7y8=
|
||||
=RuLK
|
||||
-----END PGP SIGNATURE-----
|
||||
1
Python-3.12.12.tar.xz.sigstore
Normal file
1
Python-3.12.12.tar.xz.sigstore
Normal file
File diff suppressed because one or more lines are too long
@@ -1,3 +0,0 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:56bfef1fdfc1221ce6720e43a661e3eb41785dd914ce99698d8c7896af4bdaa1
|
||||
size 20625068
|
||||
@@ -1,18 +0,0 @@
|
||||
-----BEGIN PGP SIGNATURE-----
|
||||
|
||||
iQKTBAABCgB9FiEEcWlgX2LHUTVtBUomqCHmgOX6YwUFAmYVDdNfFIAAAAAALgAo
|
||||
aXNzdWVyLWZwckBub3RhdGlvbnMub3BlbnBncC5maWZ0aGhvcnNlbWFuLm5ldDcx
|
||||
Njk2MDVGNjJDNzUxMzU2RDA1NEEyNkE4MjFFNjgwRTVGQTYzMDUACgkQqCHmgOX6
|
||||
YwU8Vg//aP8bxzPTDIM9Af1LLJj5LNLIuZOl5QysWQVbakoCpS8Z8ZiK3LyzGi7H
|
||||
pQ5uJEnRjhULnOi+va2TPBDqiYvY1CkVizYzmUe1dMtzHdJUBE1TzybfON02JzPD
|
||||
62oDHxUC1hvITyLE8tjnsgBuP9bbYYHnS+qqmDgBWS1M60i4bqcBiSdlWZp7ZTI4
|
||||
KIxIy9eyNujHnNQrQQ1oqIoj7ty1Hrtkfqia/3cVq7rkQT8HecBIW0K82WuIXizm
|
||||
/Ua/TQslTJsypslFYpoJBoIkWG2nk7RhJvfU5iLxQHen6cr7JOUo/u3jv0DIJyJs
|
||||
LdBWG6noTIiqKJb65UswLUxexM5f3Y7gLEZ4FCqlbAOAPG16xwwC8Xd7LIF33cHK
|
||||
133BvYCkwdl0MCpmsQuxi8i6Kql0MaEqJ9MEj6UN66ZJVpRx8hOm2FtZGhn5ZNxx
|
||||
r5C2zXGw/IjXeS01wgD8cSRVA0XJdN4bu88vmvhqMuezg3CDF5bX85isoFUaLUjS
|
||||
c5Lv1HNrqPiaWHOctnvzasy0djpwze+WCzsXFMI6VfejPpYwNlhmnxS7i3R9A4RK
|
||||
gBwViMd5q5rwx365tCfRfGcBW6OOvrHZalhSGYmUw13sBarFliW9CvN4ghN9kWbN
|
||||
YQwSggf5KD6v5mAAyReMrOJTyBG6B5hMlxKai5CzbRLlG25T2wI=
|
||||
=ZQxz
|
||||
-----END PGP SIGNATURE-----
|
||||
BIN
bluez-devel-vendor.tar.xz
(Stored with Git LFS)
BIN
bluez-devel-vendor.tar.xz
(Stored with Git LFS)
Binary file not shown.
@@ -5,30 +5,32 @@ Subject: [PATCH] bpo-31046: ensurepip does not honour the value of $(prefix)
|
||||
|
||||
Co-Authored-By: Xavier de Gaye <xdegaye@gmail.com>
|
||||
---
|
||||
Doc/library/ensurepip.rst | 9 +++--
|
||||
Doc/library/ensurepip.rst | 12 +++++-
|
||||
Lib/ensurepip/__init__.py | 18 +++++++---
|
||||
Lib/test/test_ensurepip.py | 11 ++++++
|
||||
Makefile.pre.in | 4 +-
|
||||
Misc/NEWS.d/next/Build/2019-12-16-17-50-42.bpo-31046.XA-Qfr.rst | 1
|
||||
5 files changed, 34 insertions(+), 9 deletions(-)
|
||||
5 files changed, 37 insertions(+), 9 deletions(-)
|
||||
create mode 100644 Misc/NEWS.d/next/Build/2019-12-16-17-50-42.bpo-31046.XA-Qfr.rst
|
||||
|
||||
Index: Python-3.12.2/Doc/library/ensurepip.rst
|
||||
Index: Python-3.12.10/Doc/library/ensurepip.rst
|
||||
===================================================================
|
||||
--- Python-3.12.2.orig/Doc/library/ensurepip.rst
|
||||
+++ Python-3.12.2/Doc/library/ensurepip.rst
|
||||
@@ -59,8 +59,9 @@ is at least as recent as the one availab
|
||||
--- Python-3.12.10.orig/Doc/library/ensurepip.rst 2025-04-08 13:35:47.000000000 +0200
|
||||
+++ Python-3.12.10/Doc/library/ensurepip.rst 2025-04-11 21:16:06.140273604 +0200
|
||||
@@ -61,7 +61,11 @@
|
||||
By default, ``pip`` is installed into the current virtual environment
|
||||
(if one is active) or into the system site packages (if there is no
|
||||
active virtual environment). The installation location can be controlled
|
||||
-through two additional command line options:
|
||||
+through some additional command line options:
|
||||
+
|
||||
+.. option:: --prefix <dir>
|
||||
+
|
||||
+ Installs ``pip`` using the given directory prefix.
|
||||
|
||||
+* ``--prefix <dir>``: Installs ``pip`` using the given directory prefix.
|
||||
* :samp:`--root {dir}`: Installs ``pip`` relative to the given root directory
|
||||
rather than the root of the currently active virtual environment (if any)
|
||||
or the default root for the current Python installation.
|
||||
@@ -92,7 +93,7 @@ Module API
|
||||
.. option:: --root <dir>
|
||||
|
||||
@@ -102,7 +106,7 @@
|
||||
Returns a string specifying the available version of pip that will be
|
||||
installed when bootstrapping an environment.
|
||||
|
||||
@@ -37,7 +39,7 @@ Index: Python-3.12.2/Doc/library/ensurepip.rst
|
||||
altinstall=False, default_pip=False, \
|
||||
verbosity=0)
|
||||
|
||||
@@ -102,6 +103,8 @@ Module API
|
||||
@@ -112,6 +116,8 @@
|
||||
If *root* is ``None``, then installation uses the default install location
|
||||
for the current environment.
|
||||
|
||||
@@ -46,7 +48,7 @@ Index: Python-3.12.2/Doc/library/ensurepip.rst
|
||||
*upgrade* indicates whether or not to upgrade an existing installation
|
||||
of an earlier version of ``pip`` to the available version.
|
||||
|
||||
@@ -122,6 +125,8 @@ Module API
|
||||
@@ -132,6 +138,8 @@
|
||||
*verbosity* controls the level of output to :data:`sys.stdout` from the
|
||||
bootstrapping operation.
|
||||
|
||||
@@ -55,11 +57,11 @@ Index: Python-3.12.2/Doc/library/ensurepip.rst
|
||||
.. audit-event:: ensurepip.bootstrap root ensurepip.bootstrap
|
||||
|
||||
.. note::
|
||||
Index: Python-3.12.2/Lib/ensurepip/__init__.py
|
||||
Index: Python-3.12.10/Lib/ensurepip/__init__.py
|
||||
===================================================================
|
||||
--- Python-3.12.2.orig/Lib/ensurepip/__init__.py
|
||||
+++ Python-3.12.2/Lib/ensurepip/__init__.py
|
||||
@@ -120,27 +120,27 @@ def _disable_pip_configuration_settings(
|
||||
--- Python-3.12.10.orig/Lib/ensurepip/__init__.py 2025-04-11 21:04:42.789443156 +0200
|
||||
+++ Python-3.12.10/Lib/ensurepip/__init__.py 2025-04-11 21:13:01.303399067 +0200
|
||||
@@ -120,27 +120,27 @@
|
||||
os.environ['PIP_CONFIG_FILE'] = os.devnull
|
||||
|
||||
|
||||
@@ -92,7 +94,7 @@ Index: Python-3.12.2/Lib/ensurepip/__init__.py
|
||||
|
||||
Note that calling this function will alter both sys.path and os.environ.
|
||||
"""
|
||||
@@ -190,6 +190,8 @@ def _bootstrap(*, root=None, upgrade=Fal
|
||||
@@ -190,6 +190,8 @@
|
||||
args = ["install", "--no-cache-dir", "--no-index", "--find-links", tmpdir]
|
||||
if root:
|
||||
args += ["--root", root]
|
||||
@@ -101,7 +103,7 @@ Index: Python-3.12.2/Lib/ensurepip/__init__.py
|
||||
if upgrade:
|
||||
args += ["--upgrade"]
|
||||
if user:
|
||||
@@ -265,6 +267,11 @@ def _main(argv=None):
|
||||
@@ -265,6 +267,11 @@
|
||||
help="Install everything relative to this alternate root directory.",
|
||||
)
|
||||
parser.add_argument(
|
||||
@@ -113,7 +115,7 @@ Index: Python-3.12.2/Lib/ensurepip/__init__.py
|
||||
"--altinstall",
|
||||
action="store_true",
|
||||
default=False,
|
||||
@@ -283,6 +290,7 @@ def _main(argv=None):
|
||||
@@ -283,6 +290,7 @@
|
||||
|
||||
return _bootstrap(
|
||||
root=args.root,
|
||||
@@ -121,11 +123,11 @@ Index: Python-3.12.2/Lib/ensurepip/__init__.py
|
||||
upgrade=args.upgrade,
|
||||
user=args.user,
|
||||
verbosity=args.verbosity,
|
||||
Index: Python-3.12.2/Lib/test/test_ensurepip.py
|
||||
Index: Python-3.12.10/Lib/test/test_ensurepip.py
|
||||
===================================================================
|
||||
--- Python-3.12.2.orig/Lib/test/test_ensurepip.py
|
||||
+++ Python-3.12.2/Lib/test/test_ensurepip.py
|
||||
@@ -105,6 +105,17 @@ class TestBootstrap(EnsurepipMixin, unit
|
||||
--- Python-3.12.10.orig/Lib/test/test_ensurepip.py 2025-04-11 21:04:44.274413027 +0200
|
||||
+++ Python-3.12.10/Lib/test/test_ensurepip.py 2025-04-11 21:13:01.303691075 +0200
|
||||
@@ -105,6 +105,17 @@
|
||||
unittest.mock.ANY,
|
||||
)
|
||||
|
||||
@@ -143,11 +145,11 @@ Index: Python-3.12.2/Lib/test/test_ensurepip.py
|
||||
def test_bootstrapping_with_user(self):
|
||||
ensurepip.bootstrap(user=True)
|
||||
|
||||
Index: Python-3.12.2/Makefile.pre.in
|
||||
Index: Python-3.12.10/Makefile.pre.in
|
||||
===================================================================
|
||||
--- Python-3.12.2.orig/Makefile.pre.in
|
||||
+++ Python-3.12.2/Makefile.pre.in
|
||||
@@ -1912,7 +1912,7 @@ install: @FRAMEWORKINSTALLFIRST@ commoni
|
||||
--- Python-3.12.10.orig/Makefile.pre.in 2025-04-11 21:04:58.388346212 +0200
|
||||
+++ Python-3.12.10/Makefile.pre.in 2025-04-11 21:13:01.304095180 +0200
|
||||
@@ -1914,7 +1914,7 @@
|
||||
install|*) ensurepip="" ;; \
|
||||
esac; \
|
||||
$(RUNSHARED) $(PYTHON_FOR_BUILD) -m ensurepip \
|
||||
@@ -156,7 +158,7 @@ Index: Python-3.12.2/Makefile.pre.in
|
||||
fi
|
||||
|
||||
.PHONY: altinstall
|
||||
@@ -1923,7 +1923,7 @@ altinstall: commoninstall
|
||||
@@ -1925,7 +1925,7 @@
|
||||
install|*) ensurepip="--altinstall" ;; \
|
||||
esac; \
|
||||
$(RUNSHARED) $(PYTHON_FOR_BUILD) -m ensurepip \
|
||||
@@ -165,9 +167,9 @@ Index: Python-3.12.2/Makefile.pre.in
|
||||
fi
|
||||
|
||||
.PHONY: commoninstall
|
||||
Index: Python-3.12.2/Misc/NEWS.d/next/Build/2019-12-16-17-50-42.bpo-31046.XA-Qfr.rst
|
||||
Index: Python-3.12.10/Misc/NEWS.d/next/Build/2019-12-16-17-50-42.bpo-31046.XA-Qfr.rst
|
||||
===================================================================
|
||||
--- /dev/null
|
||||
+++ Python-3.12.2/Misc/NEWS.d/next/Build/2019-12-16-17-50-42.bpo-31046.XA-Qfr.rst
|
||||
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
|
||||
+++ Python-3.12.10/Misc/NEWS.d/next/Build/2019-12-16-17-50-42.bpo-31046.XA-Qfr.rst 2025-04-11 21:13:01.304672632 +0200
|
||||
@@ -0,0 +1 @@
|
||||
+A directory prefix can now be specified when using :mod:`ensurepip`.
|
||||
|
||||
45
bsc1243155-sphinx-non-determinism.patch
Normal file
45
bsc1243155-sphinx-non-determinism.patch
Normal file
@@ -0,0 +1,45 @@
|
||||
From 906a590df191f66f4f0c4a70e3edb6fd82c156ef Mon Sep 17 00:00:00 2001
|
||||
From: Daniel Garcia Moreno <daniel.garcia@suse.com>
|
||||
Date: Tue, 1 Jul 2025 12:13:28 +0200
|
||||
Subject: [PATCH] Doc: Generate ids for audit_events using docname
|
||||
|
||||
This patch generates ids for audit_events using the docname so the id is
|
||||
not global but depend on the source file. This make the doc build
|
||||
reproducible with multiple cores because it doesn't which file is parsed
|
||||
first, the id for audit_events will always be consistent independently
|
||||
of what file is parsed first.
|
||||
|
||||
https://github.com/python/cpython/issues/130979
|
||||
---
|
||||
Doc/tools/extensions/audit_events.py | 11 ++++++++---
|
||||
1 file changed, 8 insertions(+), 3 deletions(-)
|
||||
|
||||
Index: Python-3.12.11/Doc/tools/extensions/audit_events.py
|
||||
===================================================================
|
||||
--- Python-3.12.11.orig/Doc/tools/extensions/audit_events.py 2025-07-02 16:10:55.447792590 +0200
|
||||
+++ Python-3.12.11/Doc/tools/extensions/audit_events.py 2025-07-02 16:12:00.124596479 +0200
|
||||
@@ -64,8 +64,13 @@
|
||||
logger.warning(msg)
|
||||
return
|
||||
|
||||
- def id_for(self, name) -> str:
|
||||
- source_count = len(self.sources.get(name, ()))
|
||||
+ def _source_count(self, name, docname) -> int:
|
||||
+ """Count the event name in the same source"""
|
||||
+ sources = self.sources.get(name, set())
|
||||
+ return len([s for s, t in sources if s == docname])
|
||||
+
|
||||
+ def id_for(self, name, docname) -> str:
|
||||
+ source_count = self._source_count(name, docname)
|
||||
name_clean = re.sub(r"\W", "_", name)
|
||||
return f"audit_event_{name_clean}_{source_count}"
|
||||
|
||||
@@ -140,7 +145,7 @@
|
||||
except (IndexError, TypeError):
|
||||
target = None
|
||||
if not target:
|
||||
- target = self.env.audit_events.id_for(name)
|
||||
+ target = self.env.audit_events.id_for(name, self.env.docname)
|
||||
ids.append(target)
|
||||
self.env.audit_events.add_event(name, args, (self.env.docname, target))
|
||||
|
||||
780
doc-py38-to-py36.patch
Normal file
780
doc-py38-to-py36.patch
Normal file
@@ -0,0 +1,780 @@
|
||||
---
|
||||
Doc/Makefile | 8 +--
|
||||
Doc/conf.py | 16 ++++++-
|
||||
Doc/tools/check-warnings.py | 5 +-
|
||||
Doc/tools/extensions/audit_events.py | 54 +++++++++++++-------------
|
||||
Doc/tools/extensions/availability.py | 15 +++----
|
||||
Doc/tools/extensions/c_annotations.py | 45 +++++++++++++--------
|
||||
Doc/tools/extensions/changes.py | 8 +--
|
||||
Doc/tools/extensions/glossary_search.py | 10 +---
|
||||
Doc/tools/extensions/implementation_detail.py | 22 +++-------
|
||||
Doc/tools/extensions/issue_role.py | 16 ++-----
|
||||
Doc/tools/extensions/misc_news.py | 14 ++----
|
||||
Doc/tools/extensions/patchlevel.py | 9 ++--
|
||||
Doc/tools/extensions/pydoc_topics.py | 22 +++++-----
|
||||
13 files changed, 126 insertions(+), 118 deletions(-)
|
||||
|
||||
Index: Python-3.12.10/Doc/Makefile
|
||||
===================================================================
|
||||
--- Python-3.12.10.orig/Doc/Makefile 2025-04-29 22:11:50.013198738 +0200
|
||||
+++ Python-3.12.10/Doc/Makefile 2025-04-29 22:11:52.047098026 +0200
|
||||
@@ -14,15 +14,15 @@
|
||||
SOURCES =
|
||||
DISTVERSION = $(shell $(PYTHON) tools/extensions/patchlevel.py)
|
||||
REQUIREMENTS = requirements.txt
|
||||
-SPHINXERRORHANDLING = --fail-on-warning
|
||||
+SPHINXERRORHANDLING = -W
|
||||
|
||||
# Internal variables.
|
||||
PAPEROPT_a4 = --define latex_elements.papersize=a4paper
|
||||
PAPEROPT_letter = --define latex_elements.papersize=letterpaper
|
||||
|
||||
-ALLSPHINXOPTS = --builder $(BUILDER) \
|
||||
- --doctree-dir build/doctrees \
|
||||
- --jobs $(JOBS) \
|
||||
+ALLSPHINXOPTS = -b $(BUILDER) \
|
||||
+ -d build/doctrees \
|
||||
+ -j $(JOBS) \
|
||||
$(PAPEROPT_$(PAPER)) \
|
||||
$(SPHINXOPTS) $(SPHINXERRORHANDLING) \
|
||||
. build/$(BUILDER) $(SOURCES)
|
||||
Index: Python-3.12.10/Doc/conf.py
|
||||
===================================================================
|
||||
--- Python-3.12.10.orig/Doc/conf.py 2025-04-29 22:11:46.161835452 +0200
|
||||
+++ Python-3.12.10/Doc/conf.py 2025-04-29 22:11:52.047459667 +0200
|
||||
@@ -11,6 +11,8 @@
|
||||
from importlib import import_module
|
||||
from importlib.util import find_spec
|
||||
|
||||
+from sphinx import version_info
|
||||
+
|
||||
# Make our custom extensions available to Sphinx
|
||||
sys.path.append(os.path.abspath('tools/extensions'))
|
||||
sys.path.append(os.path.abspath('includes'))
|
||||
@@ -87,7 +89,7 @@
|
||||
|
||||
# Minimum version of sphinx required
|
||||
# Keep this version in sync with ``Doc/requirements.txt``.
|
||||
-needs_sphinx = '8.2.0'
|
||||
+needs_sphinx = '4.2.0'
|
||||
|
||||
# Create table of contents entries for domain objects (e.g. functions, classes,
|
||||
# attributes, etc.). Default is True.
|
||||
@@ -342,7 +344,7 @@
|
||||
# (See .readthedocs.yml and https://docs.readthedocs.io/en/stable/reference/environment-variables.html)
|
||||
is_deployment_preview = os.getenv("READTHEDOCS_VERSION_TYPE") == "external"
|
||||
repository_url = os.getenv("READTHEDOCS_GIT_CLONE_URL", "")
|
||||
-repository_url = repository_url.removesuffix(".git")
|
||||
+repository_url = repository_url[:-len(".git")]
|
||||
html_context = {
|
||||
"is_deployment_preview": is_deployment_preview,
|
||||
"repository_url": repository_url or None,
|
||||
@@ -588,6 +590,16 @@
|
||||
}
|
||||
extlinks_detect_hardcoded_links = True
|
||||
|
||||
+if version_info[:2] < (8, 1):
|
||||
+ # Sphinx 8.1 has in-built CVE and CWE roles.
|
||||
+ extlinks.update({
|
||||
+ "cve": (
|
||||
+ "https://www.cve.org/CVERecord?id=CVE-%s",
|
||||
+ "CVE-%s",
|
||||
+ ),
|
||||
+ "cwe": ("https://cwe.mitre.org/data/definitions/%s.html", "CWE-%s"),
|
||||
+ })
|
||||
+
|
||||
# Options for c_annotations extension
|
||||
# -----------------------------------
|
||||
|
||||
Index: Python-3.12.10/Doc/tools/check-warnings.py
|
||||
===================================================================
|
||||
--- Python-3.12.10.orig/Doc/tools/check-warnings.py 2025-04-08 13:35:47.000000000 +0200
|
||||
+++ Python-3.12.10/Doc/tools/check-warnings.py 2025-04-29 22:11:52.047704324 +0200
|
||||
@@ -228,7 +228,8 @@
|
||||
print(filename)
|
||||
for warning in warnings:
|
||||
if filename in warning:
|
||||
- if match := WARNING_PATTERN.fullmatch(warning):
|
||||
+ match = WARNING_PATTERN.fullmatch(warning)
|
||||
+ if match:
|
||||
print(" {line}: {msg}".format_map(match))
|
||||
return -1
|
||||
return 0
|
||||
@@ -316,7 +317,7 @@
|
||||
|
||||
cwd = str(Path.cwd()) + os.path.sep
|
||||
files_with_nits = {
|
||||
- warning.removeprefix(cwd).split(":")[0]
|
||||
+ (warning[len(cwd):].split(":")[0] if warning.startswith(cwd) else warning.split(":")[0])
|
||||
for warning in warnings
|
||||
if "Doc/" in warning
|
||||
}
|
||||
Index: Python-3.12.10/Doc/tools/extensions/audit_events.py
|
||||
===================================================================
|
||||
--- Python-3.12.10.orig/Doc/tools/extensions/audit_events.py 2025-04-08 13:35:47.000000000 +0200
|
||||
+++ Python-3.12.10/Doc/tools/extensions/audit_events.py 2025-04-29 22:11:52.047967558 +0200
|
||||
@@ -1,9 +1,6 @@
|
||||
"""Support for documenting audit events."""
|
||||
|
||||
-from __future__ import annotations
|
||||
-
|
||||
import re
|
||||
-from typing import TYPE_CHECKING
|
||||
|
||||
from docutils import nodes
|
||||
from sphinx.errors import NoUri
|
||||
@@ -12,12 +9,11 @@
|
||||
from sphinx.util import logging
|
||||
from sphinx.util.docutils import SphinxDirective
|
||||
|
||||
-if TYPE_CHECKING:
|
||||
- from collections.abc import Iterator
|
||||
+from typing import Any, List, Tuple
|
||||
|
||||
- from sphinx.application import Sphinx
|
||||
- from sphinx.builders import Builder
|
||||
- from sphinx.environment import BuildEnvironment
|
||||
+from sphinx.application import Sphinx
|
||||
+from sphinx.builders import Builder
|
||||
+from sphinx.environment import BuildEnvironment
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -32,16 +28,16 @@
|
||||
|
||||
class AuditEvents:
|
||||
def __init__(self) -> None:
|
||||
- self.events: dict[str, list[str]] = {}
|
||||
- self.sources: dict[str, list[tuple[str, str]]] = {}
|
||||
+ self.events: dict[str, List[str]] = {}
|
||||
+ self.sources: dict[str, List[Tuple[str, str]]] = {}
|
||||
|
||||
- def __iter__(self) -> Iterator[tuple[str, list[str], tuple[str, str]]]:
|
||||
+ def __iter__(self) -> Any:
|
||||
for name, args in self.events.items():
|
||||
for source in self.sources[name]:
|
||||
yield name, args, source
|
||||
|
||||
def add_event(
|
||||
- self, name, args: list[str], source: tuple[str, str]
|
||||
+ self, name, args: List[str], source: Tuple[str, str]
|
||||
) -> None:
|
||||
if name in self.events:
|
||||
self._check_args_match(name, args)
|
||||
@@ -49,7 +45,7 @@
|
||||
self.events[name] = args
|
||||
self.sources.setdefault(name, []).append(source)
|
||||
|
||||
- def _check_args_match(self, name: str, args: list[str]) -> None:
|
||||
+ def _check_args_match(self, name: str, args: List[str]) -> None:
|
||||
current_args = self.events[name]
|
||||
msg = (
|
||||
f"Mismatched arguments for audit-event {name}: "
|
||||
@@ -60,7 +56,7 @@
|
||||
if len(current_args) != len(args):
|
||||
logger.warning(msg)
|
||||
return
|
||||
- for a1, a2 in zip(current_args, args, strict=False):
|
||||
+ for a1, a2 in zip(current_args, args):
|
||||
if a1 == a2:
|
||||
continue
|
||||
if any(a1 in s and a2 in s for s in _SYNONYMS):
|
||||
@@ -73,7 +69,7 @@
|
||||
name_clean = re.sub(r"\W", "_", name)
|
||||
return f"audit_event_{name_clean}_{source_count}"
|
||||
|
||||
- def rows(self) -> Iterator[tuple[str, list[str], list[tuple[str, str]]]]:
|
||||
+ def rows(self) -> Any:
|
||||
for name in sorted(self.events.keys()):
|
||||
yield name, self.events[name], self.sources[name]
|
||||
|
||||
@@ -97,7 +93,7 @@
|
||||
def audit_events_merge(
|
||||
app: Sphinx,
|
||||
env: BuildEnvironment,
|
||||
- docnames: list[str],
|
||||
+ docnames: List[str],
|
||||
other: BuildEnvironment,
|
||||
) -> None:
|
||||
"""In Sphinx parallel builds, this merges audit_events from subprocesses."""
|
||||
@@ -126,14 +122,16 @@
|
||||
),
|
||||
]
|
||||
|
||||
- def run(self) -> list[nodes.paragraph]:
|
||||
+ def run(self) -> List[nodes.paragraph]:
|
||||
+ def _no_walrus_op(args):
|
||||
+ for arg in args.strip("'\"").split(","):
|
||||
+ aarg = arg.strip()
|
||||
+ if aarg:
|
||||
+ yield aarg
|
||||
+
|
||||
name = self.arguments[0]
|
||||
if len(self.arguments) >= 2 and self.arguments[1]:
|
||||
- args = [
|
||||
- arg
|
||||
- for argument in self.arguments[1].strip("'\"").split(",")
|
||||
- if (arg := argument.strip())
|
||||
- ]
|
||||
+ args = list(_no_walrus_op(self.arguments[1]))
|
||||
else:
|
||||
args = []
|
||||
ids = []
|
||||
@@ -169,7 +167,7 @@
|
||||
|
||||
|
||||
class AuditEventListDirective(SphinxDirective):
|
||||
- def run(self) -> list[audit_event_list]:
|
||||
+ def run(self) -> List[audit_event_list]:
|
||||
return [audit_event_list()]
|
||||
|
||||
|
||||
@@ -181,7 +179,11 @@
|
||||
return
|
||||
|
||||
table = self._make_table(self.app.builder, self.env.docname)
|
||||
- for node in self.document.findall(audit_event_list):
|
||||
+ try:
|
||||
+ findall = self.document.findall
|
||||
+ except AttributeError:
|
||||
+ findall = self.document.traverse
|
||||
+ for node in findall(audit_event_list):
|
||||
node.replace_self(table)
|
||||
|
||||
def _make_table(self, builder: Builder, docname: str) -> nodes.table:
|
||||
@@ -217,8 +219,8 @@
|
||||
builder: Builder,
|
||||
docname: str,
|
||||
name: str,
|
||||
- args: list[str],
|
||||
- sources: list[tuple[str, str]],
|
||||
+ args: List[str],
|
||||
+ sources: List[Tuple[str, str]],
|
||||
) -> nodes.row:
|
||||
row = nodes.row()
|
||||
name_node = nodes.paragraph("", nodes.Text(name))
|
||||
Index: Python-3.12.10/Doc/tools/extensions/availability.py
|
||||
===================================================================
|
||||
--- Python-3.12.10.orig/Doc/tools/extensions/availability.py 2025-04-08 13:35:47.000000000 +0200
|
||||
+++ Python-3.12.10/Doc/tools/extensions/availability.py 2025-04-29 22:11:52.048206976 +0200
|
||||
@@ -1,8 +1,6 @@
|
||||
"""Support for documenting platform availability"""
|
||||
|
||||
-from __future__ import annotations
|
||||
-
|
||||
-from typing import TYPE_CHECKING
|
||||
+from typing import Dict, List, TYPE_CHECKING, Union
|
||||
|
||||
from docutils import nodes
|
||||
from sphinx import addnodes
|
||||
@@ -53,7 +51,7 @@
|
||||
optional_arguments = 0
|
||||
final_argument_whitespace = True
|
||||
|
||||
- def run(self) -> list[nodes.container]:
|
||||
+ def run(self) -> List[nodes.container]:
|
||||
title = sphinx_gettext("Availability")
|
||||
refnode = addnodes.pending_xref(
|
||||
title,
|
||||
@@ -77,7 +75,7 @@
|
||||
|
||||
return [cnode]
|
||||
|
||||
- def parse_platforms(self) -> dict[str, str | bool]:
|
||||
+ def parse_platforms(self) -> Dict[str, Union[str, bool]]:
|
||||
"""Parse platform information from arguments
|
||||
|
||||
Arguments is a comma-separated string of platforms. A platform may
|
||||
@@ -96,12 +94,13 @@
|
||||
platform, _, version = arg.partition(" >= ")
|
||||
if platform.startswith("not "):
|
||||
version = False
|
||||
- platform = platform.removeprefix("not ")
|
||||
+ platform = platform[len("not "):]
|
||||
elif not version:
|
||||
version = True
|
||||
platforms[platform] = version
|
||||
|
||||
- if unknown := set(platforms).difference(KNOWN_PLATFORMS):
|
||||
+ unknown = set(platforms).difference(KNOWN_PLATFORMS)
|
||||
+ if unknown:
|
||||
logger.warning(
|
||||
"Unknown platform%s or syntax '%s' in '.. availability:: %s', "
|
||||
"see %s:KNOWN_PLATFORMS for a set of known platforms.",
|
||||
@@ -114,7 +113,7 @@
|
||||
return platforms
|
||||
|
||||
|
||||
-def setup(app: Sphinx) -> ExtensionMetadata:
|
||||
+def setup(app):
|
||||
app.add_directive("availability", Availability)
|
||||
|
||||
return {
|
||||
Index: Python-3.12.10/Doc/tools/extensions/c_annotations.py
|
||||
===================================================================
|
||||
--- Python-3.12.10.orig/Doc/tools/extensions/c_annotations.py 2025-04-29 22:11:52.033400629 +0200
|
||||
+++ Python-3.12.10/Doc/tools/extensions/c_annotations.py 2025-04-29 22:11:52.048411194 +0200
|
||||
@@ -9,22 +9,18 @@
|
||||
* Set ``stable_abi_file`` to the path to stable ABI list.
|
||||
"""
|
||||
|
||||
-from __future__ import annotations
|
||||
-
|
||||
import csv
|
||||
import dataclasses
|
||||
from pathlib import Path
|
||||
-from typing import TYPE_CHECKING
|
||||
+from typing import Any, Dict, List, TYPE_CHECKING, Union
|
||||
|
||||
from docutils import nodes
|
||||
from docutils.statemachine import StringList
|
||||
-from sphinx import addnodes
|
||||
+from sphinx import addnodes, version_info
|
||||
from sphinx.locale import _ as sphinx_gettext
|
||||
from sphinx.util.docutils import SphinxDirective
|
||||
|
||||
-if TYPE_CHECKING:
|
||||
- from sphinx.application import Sphinx
|
||||
- from sphinx.util.typing import ExtensionMetadata
|
||||
+from sphinx.application import Sphinx
|
||||
|
||||
ROLE_TO_OBJECT_TYPE = {
|
||||
"func": "function",
|
||||
@@ -35,20 +31,20 @@
|
||||
}
|
||||
|
||||
|
||||
-@dataclasses.dataclass(slots=True)
|
||||
+@dataclasses.dataclass()
|
||||
class RefCountEntry:
|
||||
# Name of the function.
|
||||
name: str
|
||||
# List of (argument name, type, refcount effect) tuples.
|
||||
# (Currently not used. If it was, a dataclass might work better.)
|
||||
- args: list = dataclasses.field(default_factory=list)
|
||||
+ args: List = dataclasses.field(default_factory=list)
|
||||
# Return type of the function.
|
||||
result_type: str = ""
|
||||
# Reference count effect for the return value.
|
||||
- result_refs: int | None = None
|
||||
+ result_refs: Union[int, None] = None
|
||||
|
||||
|
||||
-@dataclasses.dataclass(frozen=True, slots=True)
|
||||
+@dataclasses.dataclass(frozen=True)
|
||||
class StableABIEntry:
|
||||
# Role of the object.
|
||||
# Source: Each [item_kind] in stable_abi.toml is mapped to a C Domain role.
|
||||
@@ -67,7 +63,7 @@
|
||||
struct_abi_kind: str
|
||||
|
||||
|
||||
-def read_refcount_data(refcount_filename: Path) -> dict[str, RefCountEntry]:
|
||||
+def read_refcount_data(refcount_filename: Path) -> Dict[str, RefCountEntry]:
|
||||
refcount_data = {}
|
||||
refcounts = refcount_filename.read_text(encoding="utf8")
|
||||
for line in refcounts.splitlines():
|
||||
@@ -103,7 +99,7 @@
|
||||
return refcount_data
|
||||
|
||||
|
||||
-def read_stable_abi_data(stable_abi_file: Path) -> dict[str, StableABIEntry]:
|
||||
+def read_stable_abi_data(stable_abi_file: Path) -> Dict[str, StableABIEntry]:
|
||||
stable_abi_data = {}
|
||||
with open(stable_abi_file, encoding="utf8") as fp:
|
||||
for record in csv.DictReader(fp):
|
||||
@@ -127,11 +123,14 @@
|
||||
continue
|
||||
if not par[0].get("ids", None):
|
||||
continue
|
||||
- name = par[0]["ids"][0].removeprefix("c.")
|
||||
+ name = par[0]["ids"][0]
|
||||
+ if name.startswith("c."):
|
||||
+ name = name[len("c."):]
|
||||
objtype = par["objtype"]
|
||||
|
||||
# Stable ABI annotation.
|
||||
- if record := stable_abi_data.get(name):
|
||||
+ record = stable_abi_data.get(name)
|
||||
+ if record:
|
||||
if ROLE_TO_OBJECT_TYPE[record.role] != objtype:
|
||||
msg = (
|
||||
f"Object type mismatch in limited API annotation for {name}: "
|
||||
@@ -238,7 +237,7 @@
|
||||
)
|
||||
|
||||
|
||||
-def _return_value_annotation(result_refs: int | None) -> nodes.emphasis:
|
||||
+def _return_value_annotation(result_refs: Union[int, None]) -> nodes.emphasis:
|
||||
classes = ["refcount"]
|
||||
if result_refs is None:
|
||||
rc = sphinx_gettext("Return value: Always NULL.")
|
||||
@@ -258,7 +257,7 @@
|
||||
optional_arguments = 0
|
||||
final_argument_whitespace = True
|
||||
|
||||
- def run(self) -> list[nodes.Node]:
|
||||
+ def run(self) -> List[nodes.Node]:
|
||||
state = self.env.domaindata["c_annotations"]
|
||||
content = [
|
||||
f"* :c:{record.role}:`{record.name}`"
|
||||
@@ -281,13 +280,23 @@
|
||||
)
|
||||
|
||||
|
||||
-def setup(app: Sphinx) -> ExtensionMetadata:
|
||||
+def setup(app: Sphinx) -> Any:
|
||||
app.add_config_value("refcount_file", "", "env", types={str})
|
||||
app.add_config_value("stable_abi_file", "", "env", types={str})
|
||||
app.add_directive("limited-api-list", LimitedAPIList)
|
||||
app.connect("builder-inited", init_annotations)
|
||||
app.connect("doctree-read", add_annotations)
|
||||
|
||||
+ if version_info[:2] < (7, 2):
|
||||
+ from docutils.parsers.rst import directives
|
||||
+ from sphinx.domains.c import CObject
|
||||
+
|
||||
+ # monkey-patch C object...
|
||||
+ CObject.option_spec.update({
|
||||
+ "no-index-entry": directives.flag,
|
||||
+ "no-contents-entry": directives.flag,
|
||||
+ })
|
||||
+
|
||||
return {
|
||||
"version": "1.0",
|
||||
"parallel_read_safe": True,
|
||||
Index: Python-3.12.10/Doc/tools/extensions/changes.py
|
||||
===================================================================
|
||||
--- Python-3.12.10.orig/Doc/tools/extensions/changes.py 2025-04-08 13:35:47.000000000 +0200
|
||||
+++ Python-3.12.10/Doc/tools/extensions/changes.py 2025-04-29 22:11:52.048619113 +0200
|
||||
@@ -1,7 +1,5 @@
|
||||
"""Support for documenting version of changes, additions, deprecations."""
|
||||
|
||||
-from __future__ import annotations
|
||||
-
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from sphinx.domains.changeset import (
|
||||
@@ -25,7 +23,7 @@
|
||||
|
||||
|
||||
class PyVersionChange(VersionChange):
|
||||
- def run(self) -> list[Node]:
|
||||
+ def run(self) -> "list[Node]":
|
||||
# Replace the 'next' special token with the current development version
|
||||
self.arguments[0] = expand_version_arg(
|
||||
self.arguments[0], self.config.release
|
||||
@@ -43,7 +41,7 @@
|
||||
"Deprecated since version %s, removed in version %s"
|
||||
)
|
||||
|
||||
- def run(self) -> list[Node]:
|
||||
+ def run(self) -> "list[Node]":
|
||||
# Replace the first two arguments (deprecated version and removed version)
|
||||
# with a single tuple of both versions.
|
||||
version_deprecated = expand_version_arg(
|
||||
@@ -73,7 +71,7 @@
|
||||
versionlabel_classes[self.name] = ""
|
||||
|
||||
|
||||
-def setup(app: Sphinx) -> ExtensionMetadata:
|
||||
+def setup(app: "Sphinx") -> "ExtensionMetadata":
|
||||
# Override Sphinx's directives with support for 'next'
|
||||
app.add_directive("versionadded", PyVersionChange, override=True)
|
||||
app.add_directive("versionchanged", PyVersionChange, override=True)
|
||||
Index: Python-3.12.10/Doc/tools/extensions/glossary_search.py
|
||||
===================================================================
|
||||
--- Python-3.12.10.orig/Doc/tools/extensions/glossary_search.py 2025-04-29 22:11:52.033722879 +0200
|
||||
+++ Python-3.12.10/Doc/tools/extensions/glossary_search.py 2025-04-29 22:11:52.048797629 +0200
|
||||
@@ -1,18 +1,14 @@
|
||||
"""Feature search results for glossary items prominently."""
|
||||
|
||||
-from __future__ import annotations
|
||||
-
|
||||
import json
|
||||
from pathlib import Path
|
||||
-from typing import TYPE_CHECKING
|
||||
+from typing import Any, TYPE_CHECKING
|
||||
|
||||
from docutils import nodes
|
||||
from sphinx.addnodes import glossary
|
||||
from sphinx.util import logging
|
||||
|
||||
-if TYPE_CHECKING:
|
||||
- from sphinx.application import Sphinx
|
||||
- from sphinx.util.typing import ExtensionMetadata
|
||||
+from sphinx.application import Sphinx
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -60,7 +56,7 @@
|
||||
dest.write_text(json.dumps(app.env.glossary_terms), encoding='utf-8')
|
||||
|
||||
|
||||
-def setup(app: Sphinx) -> ExtensionMetadata:
|
||||
+def setup(app: Sphinx) -> Any:
|
||||
app.connect('doctree-resolved', process_glossary_nodes)
|
||||
app.connect('build-finished', write_glossary_json)
|
||||
|
||||
Index: Python-3.12.10/Doc/tools/extensions/implementation_detail.py
|
||||
===================================================================
|
||||
--- Python-3.12.10.orig/Doc/tools/extensions/implementation_detail.py 2025-04-08 13:35:47.000000000 +0200
|
||||
+++ Python-3.12.10/Doc/tools/extensions/implementation_detail.py 2025-04-29 22:48:23.397548211 +0200
|
||||
@@ -1,17 +1,10 @@
|
||||
"""Support for marking up implementation details."""
|
||||
|
||||
-from __future__ import annotations
|
||||
-
|
||||
-from typing import TYPE_CHECKING
|
||||
-
|
||||
from docutils import nodes
|
||||
from sphinx.locale import _ as sphinx_gettext
|
||||
from sphinx.util.docutils import SphinxDirective
|
||||
|
||||
-if TYPE_CHECKING:
|
||||
- from sphinx.application import Sphinx
|
||||
- from sphinx.util.typing import ExtensionMetadata
|
||||
-
|
||||
+from sphinx.application import Sphinx
|
||||
|
||||
class ImplementationDetail(SphinxDirective):
|
||||
has_content = True
|
||||
@@ -21,23 +14,24 @@
|
||||
label_text = sphinx_gettext("CPython implementation detail:")
|
||||
|
||||
def run(self):
|
||||
- self.assert_has_content()
|
||||
- content_nodes = self.parse_content_to_nodes()
|
||||
+ container_node = nodes.container()
|
||||
+ container_node.document = self.state.document # Ensure node has document context
|
||||
+ self.state.nested_parse(self.content, self.content_offset, container_node)
|
||||
+ parsed_nodes = container_node.children
|
||||
|
||||
# insert our prefix at the start of the first paragraph
|
||||
- first_node = content_nodes[0]
|
||||
+ first_node = parsed_nodes[0]
|
||||
first_node[:0] = [
|
||||
nodes.strong(self.label_text, self.label_text),
|
||||
nodes.Text(" "),
|
||||
]
|
||||
|
||||
- # create a new compound container node
|
||||
- cnode = nodes.compound("", *content_nodes, classes=["impl-detail"])
|
||||
+ cnode = nodes.compound("", *parsed_nodes, classes=["impl-detail"])
|
||||
self.set_source_info(cnode)
|
||||
return [cnode]
|
||||
|
||||
|
||||
-def setup(app: Sphinx) -> ExtensionMetadata:
|
||||
+def setup(app: Sphinx):
|
||||
app.add_directive("impl-detail", ImplementationDetail)
|
||||
|
||||
return {
|
||||
Index: Python-3.12.10/Doc/tools/extensions/issue_role.py
|
||||
===================================================================
|
||||
--- Python-3.12.10.orig/Doc/tools/extensions/issue_role.py 2025-04-08 13:35:47.000000000 +0200
|
||||
+++ Python-3.12.10/Doc/tools/extensions/issue_role.py 2025-04-29 22:21:55.278961032 +0200
|
||||
@@ -1,22 +1,18 @@
|
||||
"""Support for referencing issues in the tracker."""
|
||||
|
||||
-from __future__ import annotations
|
||||
-
|
||||
-from typing import TYPE_CHECKING
|
||||
+from typing import TYPE_CHECKING, List, Tuple
|
||||
|
||||
from docutils import nodes
|
||||
from sphinx.util.docutils import SphinxRole
|
||||
|
||||
-if TYPE_CHECKING:
|
||||
- from docutils.nodes import Element
|
||||
- from sphinx.application import Sphinx
|
||||
- from sphinx.util.typing import ExtensionMetadata
|
||||
+from docutils.nodes import Element
|
||||
+from sphinx.application import Sphinx
|
||||
|
||||
|
||||
class BPOIssue(SphinxRole):
|
||||
ISSUE_URI = "https://bugs.python.org/issue?@action=redirect&bpo={0}"
|
||||
|
||||
- def run(self) -> tuple[list[Element], list[nodes.system_message]]:
|
||||
+ def run(self) -> Tuple[List[Element], List[nodes.system_message]]:
|
||||
issue = self.text
|
||||
|
||||
# sanity check: there are no bpo issues within these two values
|
||||
@@ -38,7 +34,7 @@
|
||||
class GitHubIssue(SphinxRole):
|
||||
ISSUE_URI = "https://github.com/python/cpython/issues/{0}"
|
||||
|
||||
- def run(self) -> tuple[list[Element], list[nodes.system_message]]:
|
||||
+ def run(self) -> Tuple[List[Element], List[nodes.system_message]]:
|
||||
issue = self.text
|
||||
|
||||
# sanity check: all GitHub issues have ID >= 32426
|
||||
@@ -58,7 +54,7 @@
|
||||
return [refnode], []
|
||||
|
||||
|
||||
-def setup(app: Sphinx) -> ExtensionMetadata:
|
||||
+def setup(app: Sphinx) -> "ExtensionMetadata":
|
||||
app.add_role("issue", BPOIssue())
|
||||
app.add_role("gh", GitHubIssue())
|
||||
|
||||
Index: Python-3.12.10/Doc/tools/extensions/misc_news.py
|
||||
===================================================================
|
||||
--- Python-3.12.10.orig/Doc/tools/extensions/misc_news.py 2025-04-08 13:35:47.000000000 +0200
|
||||
+++ Python-3.12.10/Doc/tools/extensions/misc_news.py 2025-04-29 22:11:52.049046825 +0200
|
||||
@@ -1,7 +1,5 @@
|
||||
"""Support for including Misc/NEWS."""
|
||||
|
||||
-from __future__ import annotations
|
||||
-
|
||||
import re
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING
|
||||
@@ -24,13 +22,13 @@
|
||||
+++++++++++
|
||||
"""
|
||||
|
||||
-bpo_issue_re: Final[re.Pattern[str]] = re.compile(
|
||||
+bpo_issue_re: "Final[re.Pattern[str]]" = re.compile(
|
||||
"(?:issue #|bpo-)([0-9]+)", re.ASCII
|
||||
)
|
||||
-gh_issue_re: Final[re.Pattern[str]] = re.compile(
|
||||
+gh_issue_re: "Final[re.Pattern[str]]" = re.compile(
|
||||
"gh-(?:issue-)?([0-9]+)", re.ASCII | re.IGNORECASE
|
||||
)
|
||||
-whatsnew_re: Final[re.Pattern[str]] = re.compile(
|
||||
+whatsnew_re: "Final[re.Pattern[str]]" = re.compile(
|
||||
r"^what's new in (.*?)\??$", re.ASCII | re.IGNORECASE | re.MULTILINE
|
||||
)
|
||||
|
||||
@@ -42,7 +40,7 @@
|
||||
final_argument_whitespace = False
|
||||
option_spec = {}
|
||||
|
||||
- def run(self) -> list[Node]:
|
||||
+ def run(self) -> "list[Node]":
|
||||
# Get content of NEWS file
|
||||
source, _ = self.get_source_info()
|
||||
news_file = Path(source).resolve().parent / self.arguments[0]
|
||||
@@ -54,7 +52,7 @@
|
||||
return [nodes.strong(text, text)]
|
||||
|
||||
# remove first 3 lines as they are the main heading
|
||||
- news_text = news_text.removeprefix(BLURB_HEADER)
|
||||
+ news_text = news_text[len(BLURB_HEADER):] if news_text.startswith(BLURB_HEADER) else news_text
|
||||
|
||||
news_text = bpo_issue_re.sub(r":issue:`\1`", news_text)
|
||||
# Fallback handling for GitHub issues
|
||||
@@ -65,7 +63,7 @@
|
||||
return []
|
||||
|
||||
|
||||
-def setup(app: Sphinx) -> ExtensionMetadata:
|
||||
+def setup(app: "Sphinx") -> "ExtensionMetadata":
|
||||
app.add_directive("miscnews", MiscNews)
|
||||
|
||||
return {
|
||||
Index: Python-3.12.10/Doc/tools/extensions/patchlevel.py
|
||||
===================================================================
|
||||
--- Python-3.12.10.orig/Doc/tools/extensions/patchlevel.py 2025-04-08 13:35:47.000000000 +0200
|
||||
+++ Python-3.12.10/Doc/tools/extensions/patchlevel.py 2025-04-29 22:11:52.049253068 +0200
|
||||
@@ -3,7 +3,7 @@
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
-from typing import Literal, NamedTuple
|
||||
+from typing import NamedTuple, Tuple
|
||||
|
||||
CPYTHON_ROOT = Path(
|
||||
__file__, # cpython/Doc/tools/extensions/patchlevel.py
|
||||
@@ -26,7 +26,7 @@
|
||||
major: int #: Major release number
|
||||
minor: int #: Minor release number
|
||||
micro: int #: Patch release number
|
||||
- releaselevel: Literal["alpha", "beta", "candidate", "final"]
|
||||
+ releaselevel: str
|
||||
serial: int #: Serial release number
|
||||
|
||||
|
||||
@@ -37,7 +37,8 @@
|
||||
defines = {}
|
||||
patchlevel_h = PATCHLEVEL_H.read_text(encoding="utf-8")
|
||||
for line in patchlevel_h.splitlines():
|
||||
- if (m := pat.match(line)) is not None:
|
||||
+ m = pat.match(line)
|
||||
+ if m is not None:
|
||||
name, value = m.groups()
|
||||
defines[name] = value
|
||||
|
||||
@@ -50,7 +51,7 @@
|
||||
)
|
||||
|
||||
|
||||
-def format_version_info(info: version_info) -> tuple[str, str]:
|
||||
+def format_version_info(info: version_info) -> Tuple[str, str]:
|
||||
version = f"{info.major}.{info.minor}"
|
||||
release = f"{info.major}.{info.minor}.{info.micro}"
|
||||
if info.releaselevel != "final":
|
||||
Index: Python-3.12.10/Doc/tools/extensions/pydoc_topics.py
|
||||
===================================================================
|
||||
--- Python-3.12.10.orig/Doc/tools/extensions/pydoc_topics.py 2025-04-08 13:35:47.000000000 +0200
|
||||
+++ Python-3.12.10/Doc/tools/extensions/pydoc_topics.py 2025-04-29 22:33:59.916893510 +0200
|
||||
@@ -1,21 +1,23 @@
|
||||
"""Support for building "topic help" for pydoc."""
|
||||
|
||||
-from __future__ import annotations
|
||||
-
|
||||
from time import asctime
|
||||
-from typing import TYPE_CHECKING
|
||||
+from typing import TYPE_CHECKING, Tuple
|
||||
|
||||
from sphinx.builders.text import TextBuilder
|
||||
from sphinx.util import logging
|
||||
-from sphinx.util.display import status_iterator
|
||||
+try:
|
||||
+ from sphinx.util.display import status_iterator
|
||||
+except ModuleNotFoundError:
|
||||
+ from sphinx.util import status_iterator
|
||||
from sphinx.util.docutils import new_document
|
||||
from sphinx.writers.text import TextTranslator
|
||||
|
||||
-if TYPE_CHECKING:
|
||||
+try:
|
||||
+ from typing import Sequence, Set
|
||||
+except ModuleNotFoundError:
|
||||
from collections.abc import Sequence, Set
|
||||
|
||||
- from sphinx.application import Sphinx
|
||||
- from sphinx.util.typing import ExtensionMetadata
|
||||
+from sphinx.application import Sphinx
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -161,7 +163,7 @@
|
||||
self.outdir.joinpath("topics.py").write_text(topics, encoding="utf-8")
|
||||
|
||||
|
||||
-def _display_labels(item: tuple[str, Sequence[tuple[str, str]]]) -> str:
|
||||
+def _display_labels(item: Tuple[str, Sequence[Tuple[str, str]]]) -> str:
|
||||
_docname, label_ids = item
|
||||
labels = [name for name, _id in label_ids]
|
||||
if len(labels) > 4:
|
||||
@@ -169,7 +171,7 @@
|
||||
return ", ".join(labels)
|
||||
|
||||
|
||||
-def _repr(text: str, /) -> str:
|
||||
+def _repr(text: str) -> str:
|
||||
"""Return a triple-single-quoted representation of text."""
|
||||
if "'''" not in text:
|
||||
return f"r'''{text}'''"
|
||||
@@ -177,7 +179,7 @@
|
||||
return f"'''{text}'''"
|
||||
|
||||
|
||||
-def setup(app: Sphinx) -> ExtensionMetadata:
|
||||
+def setup(app: Sphinx) -> "ExtensionMetadata":
|
||||
app.add_builder(PydocTopicsBuilder)
|
||||
|
||||
return {
|
||||
45
docs-docutils_014-Sphinx_420.patch
Normal file
45
docs-docutils_014-Sphinx_420.patch
Normal file
@@ -0,0 +1,45 @@
|
||||
---
|
||||
Doc/tools/extensions/c_annotations.py | 6 +++++-
|
||||
Doc/tools/extensions/glossary_search.py | 12 ++++++++++--
|
||||
2 files changed, 15 insertions(+), 3 deletions(-)
|
||||
|
||||
Index: Python-3.12.10/Doc/tools/extensions/c_annotations.py
|
||||
===================================================================
|
||||
--- Python-3.12.10.orig/Doc/tools/extensions/c_annotations.py 2025-04-08 13:35:47.000000000 +0200
|
||||
+++ Python-3.12.10/Doc/tools/extensions/c_annotations.py 2025-04-11 21:16:39.007011463 +0200
|
||||
@@ -117,7 +117,11 @@
|
||||
state = app.env.domaindata["c_annotations"]
|
||||
refcount_data = state["refcount_data"]
|
||||
stable_abi_data = state["stable_abi_data"]
|
||||
- for node in doctree.findall(addnodes.desc_content):
|
||||
+ try:
|
||||
+ findall = doctree.findall
|
||||
+ except AttributeError:
|
||||
+ findall = doctree.traverse
|
||||
+ for node in findall(addnodes.desc_content):
|
||||
par = node.parent
|
||||
if par["domain"] != "c":
|
||||
continue
|
||||
Index: Python-3.12.10/Doc/tools/extensions/glossary_search.py
|
||||
===================================================================
|
||||
--- Python-3.12.10.orig/Doc/tools/extensions/glossary_search.py 2025-04-08 13:35:47.000000000 +0200
|
||||
+++ Python-3.12.10/Doc/tools/extensions/glossary_search.py 2025-04-11 21:16:39.007340209 +0200
|
||||
@@ -30,8 +30,16 @@
|
||||
else:
|
||||
terms = app.env.glossary_terms = {}
|
||||
|
||||
- for node in doctree.findall(glossary):
|
||||
- for glossary_item in node.findall(nodes.definition_list_item):
|
||||
+ try:
|
||||
+ findall = doctree.findall
|
||||
+ except AttributeError:
|
||||
+ findall = doctree.traverse
|
||||
+ for node in findall(glossary):
|
||||
+ try:
|
||||
+ node_findall = node.findall
|
||||
+ except AttributeError:
|
||||
+ node_findall = node.traverse
|
||||
+ for glossary_item in node_findall(nodes.definition_list_item):
|
||||
term = glossary_item[0].astext()
|
||||
definition = glossary_item[-1]
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
Index: Python-3.12.3/Lib/test/test_compile.py
|
||||
===================================================================
|
||||
--- Python-3.12.3.orig/Lib/test/test_compile.py
|
||||
+++ Python-3.12.3/Lib/test/test_compile.py
|
||||
---
|
||||
Lib/test/test_compile.py | 5 +++++
|
||||
1 file changed, 5 insertions(+)
|
||||
|
||||
--- a/Lib/test/test_compile.py
|
||||
+++ b/Lib/test/test_compile.py
|
||||
@@ -14,6 +14,9 @@ from test.support import (script_helper,
|
||||
requires_specialization, C_RECURSION_LIMIT)
|
||||
from test.support.os_helper import FakePath
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
--- a/Doc/using/configure.rst
|
||||
+++ b/Doc/using/configure.rst
|
||||
@@ -631,13 +631,11 @@ macOS Options
|
||||
@@ -640,13 +640,11 @@ macOS Options
|
||||
|
||||
See ``Mac/README.rst``.
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
Create a Python.framework rather than a traditional Unix install. Optional
|
||||
--- a/Misc/NEWS
|
||||
+++ b/Misc/NEWS
|
||||
@@ -13428,7 +13428,7 @@ C API
|
||||
@@ -15146,7 +15146,7 @@ C API
|
||||
- bpo-40939: Removed documentation for the removed ``PyParser_*`` C API.
|
||||
|
||||
- bpo-43795: The list in :ref:`limited-api-list` now shows the public name
|
||||
|
||||
36
gh139257-Support-docutils-0.22.patch
Normal file
36
gh139257-Support-docutils-0.22.patch
Normal file
@@ -0,0 +1,36 @@
|
||||
From 19b61747df3d62c822285c488753d6fbdf91e3ac Mon Sep 17 00:00:00 2001
|
||||
From: Daniel Garcia Moreno <daniel.garcia@suse.com>
|
||||
Date: Tue, 23 Sep 2025 10:20:16 +0200
|
||||
Subject: [PATCH 1/2] gh-139257: Support docutils >= 0.22
|
||||
|
||||
---
|
||||
Doc/tools/extensions/pyspecific.py | 12 +++++++++++-
|
||||
1 file changed, 11 insertions(+), 1 deletion(-)
|
||||
|
||||
Index: Python-3.12.11/Doc/tools/extensions/pyspecific.py
|
||||
===================================================================
|
||||
--- Python-3.12.11.orig/Doc/tools/extensions/pyspecific.py 2025-06-03 17:41:47.000000000 +0200
|
||||
+++ Python-3.12.11/Doc/tools/extensions/pyspecific.py 2025-09-30 18:16:55.089412381 +0200
|
||||
@@ -25,11 +25,21 @@
|
||||
SOURCE_URI = 'https://github.com/python/cpython/tree/3.12/%s'
|
||||
|
||||
# monkey-patch reST parser to disable alphabetic and roman enumerated lists
|
||||
+def _disable_alphabetic_and_roman(text):
|
||||
+ try:
|
||||
+ # docutils >= 0.22
|
||||
+ from docutils.parsers.rst.states import InvalidRomanNumeralError
|
||||
+ raise InvalidRomanNumeralError(text)
|
||||
+ except ImportError:
|
||||
+ # docutils < 0.22
|
||||
+ return None
|
||||
+
|
||||
+
|
||||
from docutils.parsers.rst.states import Body
|
||||
Body.enum.converters['loweralpha'] = \
|
||||
Body.enum.converters['upperalpha'] = \
|
||||
Body.enum.converters['lowerroman'] = \
|
||||
- Body.enum.converters['upperroman'] = lambda x: None
|
||||
+ Body.enum.converters['upperroman'] = _disable_alphabetic_and_roman
|
||||
|
||||
|
||||
class PyAwaitableMixin(object):
|
||||
@@ -1,16 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!-- Copyright 2017 Zbigniew Jędrzejewski-Szmek -->
|
||||
<application>
|
||||
<id type="desktop">idle3.desktop</id>
|
||||
<component type="desktop-application">
|
||||
<id>org.python.IDLE3</id>
|
||||
<launchable type="desktop-id">idle3.desktop</launchable>
|
||||
|
||||
<name>IDLE3</name>
|
||||
<metadata_licence>CC0</metadata_licence>
|
||||
<project_license>Python-2.0</project_license>
|
||||
<summary>Python 3 Integrated Development and Learning Environment</summary>
|
||||
|
||||
<description>
|
||||
<p>
|
||||
IDLE is Python’s Integrated Development and Learning Environment.
|
||||
The GUI is uniform between Windows, Unix, and Mac OS X.
|
||||
The GUI is uniform between Windows, Unix, and macOS.
|
||||
IDLE provides an easy way to start writing, running, and debugging
|
||||
Python code.
|
||||
</p>
|
||||
@@ -19,17 +19,33 @@
|
||||
It provides:
|
||||
</p>
|
||||
<ul>
|
||||
<li>a Python shell window (interactive interpreter) with colorizing of code input, output, and error messages,</li>
|
||||
<li>a multi-window text editor with multiple undo, Python colorizing, smart indent, call tips, auto completion, and other features,</li>
|
||||
<li>search within any window, replace within editor windows, and search through multiple files (grep),</li>
|
||||
<li>a debugger with persistent breakpoints, stepping, and viewing of global and local namespaces.</li>
|
||||
<li>a Python shell window (interactive interpreter) with colorizing of code input, output, and error messages,</li>
|
||||
<li>a multi-window text editor with multiple undo, Python colorizing, smart indent, call tips, auto completion, and other features,</li>
|
||||
<li>search within any window, replace within editor windows, and search through multiple files (grep),</li>
|
||||
<li>a debugger with persistent breakpoints, stepping, and viewing of global and local namespaces.</li>
|
||||
</ul>
|
||||
</description>
|
||||
|
||||
<developer id="org.python">
|
||||
<name>Python Software Foundation</name>
|
||||
</developer>
|
||||
|
||||
<url type="homepage">https://docs.python.org/3/library/idle.html</url>
|
||||
|
||||
<screenshots>
|
||||
<screenshot type="default">http://in.waw.pl/~zbyszek/fedora/idle3-appdata/idle3-main-window.png</screenshot>
|
||||
<screenshot>http://in.waw.pl/~zbyszek/fedora/idle3-appdata/idle3-class-browser.png</screenshot>
|
||||
<screenshot>http://in.waw.pl/~zbyszek/fedora/idle3-appdata/idle3-code-viewer.png</screenshot>
|
||||
<screenshot type="default">
|
||||
<image>https://in.waw.pl/~zbyszek/fedora/idle3-appdata/idle3-main-window.png</image>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image>https://in.waw.pl/~zbyszek/fedora/idle3-appdata/idle3-class-browser.png</image>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image>https://in.waw.pl/~zbyszek/fedora/idle3-appdata/idle3-code-viewer.png</image>
|
||||
</screenshot>
|
||||
</screenshots>
|
||||
|
||||
<project_license>Python-2.0</project_license>
|
||||
<metadata_license>CC0-1.0</metadata_license>
|
||||
<update_contact>zbyszek@in.waw.pl</update_contact>
|
||||
</application>
|
||||
</component>
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
python311-curses: curses _curses _curses_panel
|
||||
python311-dbm: dbm _dbm _gdbm
|
||||
python311-idle: idlelib
|
||||
python311-testsuite: test _ctypes_test _testbuffer _testcapi _testinternalcapi _testimportmultiple _testmultiphase xxlimited
|
||||
python311-tk: tkinter _tkinter
|
||||
python311-tools: turtledemo
|
||||
python311: sqlite3 readline _sqlite3 nis
|
||||
python312-curses: curses _curses
|
||||
python312-dbm: dbm _dbm _gdbm
|
||||
python312-idle: idlelib
|
||||
python312-testsuite: test _ctypes_test _testbuffer _testcapi _testclinic _testinternalcapi _testimportmultiple _testmultiphase _testsinglephase _xxinterpchannels _xxtestfuzz
|
||||
python312-tk: tkinter _tkinter
|
||||
python312-tools: turtledemo
|
||||
python312: sqlite3 readline _sqlite3 nis
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
Makefile.pre.in | 7 +++++++
|
||||
1 file changed, 7 insertions(+)
|
||||
|
||||
Index: Python-3.12.2/Makefile.pre.in
|
||||
Index: Python-3.12.4/Makefile.pre.in
|
||||
===================================================================
|
||||
--- Python-3.12.2.orig/Makefile.pre.in
|
||||
+++ Python-3.12.2/Makefile.pre.in
|
||||
@@ -1335,11 +1335,18 @@ Modules/getbuildinfo.o: $(PARSER_OBJS) \
|
||||
--- Python-3.12.4.orig/Makefile.pre.in
|
||||
+++ Python-3.12.4/Makefile.pre.in
|
||||
@@ -1337,11 +1337,18 @@ Modules/getbuildinfo.o: $(PARSER_OBJS) \
|
||||
$(DTRACE_OBJS) \
|
||||
$(srcdir)/Modules/getbuildinfo.c
|
||||
$(CC) -c $(PY_CORE_CFLAGS) \
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
---
|
||||
Lib/test/test_posix.py | 2 +-
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
Index: Python-3.12.2/Lib/test/test_posix.py
|
||||
===================================================================
|
||||
--- Python-3.12.2.orig/Lib/test/test_posix.py
|
||||
+++ Python-3.12.2/Lib/test/test_posix.py
|
||||
@@ -433,7 +433,7 @@ class PosixTester(unittest.TestCase):
|
||||
def test_posix_fadvise(self):
|
||||
fd = os.open(os_helper.TESTFN, os.O_RDONLY)
|
||||
try:
|
||||
- posix.posix_fadvise(fd, 0, 0, posix.POSIX_FADV_WILLNEED)
|
||||
+ posix.posix_fadvise(fd, 0, 0, posix.POSIX_FADV_RANDOM)
|
||||
finally:
|
||||
os.close(fd)
|
||||
|
||||
1719
python312.changes
1719
python312.changes
File diff suppressed because it is too large
Load Diff
112
python312.spec
112
python312.spec
@@ -1,7 +1,7 @@
|
||||
#
|
||||
# spec file for package python312
|
||||
#
|
||||
# Copyright (c) 2024 SUSE LLC
|
||||
# Copyright (c) 2025 SUSE LLC and contributors
|
||||
#
|
||||
# All modifications and additions to the file contributed by third parties
|
||||
# remain the property of their copyright owners, unless otherwise agreed
|
||||
@@ -36,6 +36,20 @@
|
||||
%bcond_without general
|
||||
%endif
|
||||
|
||||
%if 0%{?do_profiling} && !0%{?want_reproducible_builds}
|
||||
%bcond_without profileopt
|
||||
%else
|
||||
%bcond_with profileopt
|
||||
%endif
|
||||
|
||||
# Only for Tumbleweed
|
||||
# https://en.opensuse.org/openSUSE:Python:Externally_managed
|
||||
%if 0%{?suse_version} > 1600
|
||||
%bcond_without externally_managed
|
||||
%else
|
||||
%bcond_with externally_managed
|
||||
%endif
|
||||
|
||||
%define python_pkg_name python312
|
||||
%if "%{python_pkg_name}" == "%{primary_python}"
|
||||
%define primary_interpreter 1
|
||||
@@ -103,18 +117,18 @@
|
||||
# pyexpat.cpython-35m-armv7-linux-gnueabihf
|
||||
# _md5.cpython-38m-x86_64-linux-gnu.so
|
||||
%define dynlib() %{sitedir}/lib-dynload/%{1}.cpython-%{abi_tag}-%{archname}-%{_os}%{?_gnu}%{?armsuffix}.so
|
||||
%bcond_without profileopt
|
||||
Name: %{python_pkg_name}%{psuffix}
|
||||
Version: 3.12.3
|
||||
Version: 3.12.12
|
||||
Release: 0
|
||||
Summary: Python 3 Interpreter
|
||||
License: Python-2.0
|
||||
URL: https://www.python.org/
|
||||
Source0: https://www.python.org/ftp/python/%{folderversion}/%{tarname}.tar.xz
|
||||
Source1: https://www.python.org/ftp/python/%{folderversion}/%{tarname}.tar.xz.asc
|
||||
Source2: baselibs.conf
|
||||
Source3: README.SUSE
|
||||
Source4: externally_managed.in
|
||||
Source2: https://www.python.org/ftp/python/%{folderversion}/%{tarname}.tar.xz.sigstore
|
||||
Source3: baselibs.conf
|
||||
Source4: README.SUSE
|
||||
Source5: externally_managed.in
|
||||
Source7: macros.python3
|
||||
Source8: import_failed.py
|
||||
Source9: import_failed.map
|
||||
@@ -136,12 +150,17 @@ Source99: python.keyring
|
||||
# They are listed here to work around missing functionality in rpmbuild,
|
||||
# which would otherwise exclude them from distributed src.rpm files.
|
||||
Source100: PACKAGING-NOTES
|
||||
# PATCH-FEATURE-UPSTREAM F00251-change-user-install-location.patch bsc#[0-9]+ mcepl@suse.com
|
||||
# Fix installation in /usr/local (boo#1071941), originally from Fedora
|
||||
# https://src.fedoraproject.org/rpms/python3.12/blob/rawhide/f/00251-change-user-install-location.patch
|
||||
# Set values of prefix and exec_prefix in distutils install command
|
||||
# to /usr/local if executable is /usr/bin/python* and RPM build
|
||||
# is not detected to make pip and distutils install into separate location
|
||||
Patch02: F00251-change-user-install-location.patch
|
||||
# support finding packages in /usr/local, install to /usr/local by default
|
||||
Patch07: python-3.3.0b1-localpath.patch
|
||||
# replace DATE, TIME and COMPILER by fixed definitions to aid reproducible builds
|
||||
Patch08: python-3.3.0b1-fix_date_time_compiler.patch
|
||||
# POSIX_FADV_WILLNEED throws EINVAL. Use a different constant in test
|
||||
Patch09: python-3.3.0b1-test-posix_fadvise.patch
|
||||
# Raise timeout value for test_subprocess
|
||||
Patch15: subprocess-raise-timeout.patch
|
||||
# PATCH-FEATURE-UPSTREAM bpo-31046_ensurepip_honours_prefix.patch bpo#31046 mcepl@suse.com
|
||||
@@ -156,21 +175,20 @@ Patch34: skip-test_pyobject_freed_is_freed.patch
|
||||
# PATCH-FIX-SLE fix_configure_rst.patch bpo#43774 mcepl@suse.com
|
||||
# remove duplicate link targets and make documentation with old Sphinx in SLE
|
||||
Patch35: fix_configure_rst.patch
|
||||
# PATCH-FIX-UPSTREAM CVE-2023-27043-email-parsing-errors.patch bsc#1210638 mcepl@suse.com
|
||||
# Detect email address parsing errors and return empty tuple to
|
||||
# indicate the parsing error (old API)
|
||||
Patch36: CVE-2023-27043-email-parsing-errors.patch
|
||||
# PATCH-FIX-UPSTREAM CVE-2023-6597-TempDir-cleaning-symlink.patch bsc#1219666 mcepl@suse.com
|
||||
# tempfile.TemporaryDirectory: fix symlink bug in cleanup (from gh#python/cpython!99930)
|
||||
Patch38: CVE-2023-6597-TempDir-cleaning-symlink.patch
|
||||
# PATCH-FIX-OPENSUSE CVE-2023-52425-libexpat-2.6.0-backport-15.6.patch
|
||||
# This problem on libexpat is patched on 15.6 without version
|
||||
# update, this patch changes the tests to match the libexpat provided
|
||||
# by SUSE
|
||||
Patch39: CVE-2023-52425-libexpat-2.6.0-backport-15.6.patch
|
||||
# PATCH-FIX-OPENSUSE fix-test-recursion-limit-15.6.patch gh#python/cpython#115083
|
||||
# Skip some failing tests in test_compile for i586 arch in 15.6.
|
||||
Patch40: fix-test-recursion-limit-15.6.patch
|
||||
# PATCH-FIX-SLE docs-docutils_014-Sphinx_420.patch bsc#[0-9]+ mcepl@suse.com
|
||||
# related to gh#python/cpython#119317
|
||||
Patch41: docs-docutils_014-Sphinx_420.patch
|
||||
# PATCH-FIX-SLE doc-py38-to-py36.patch mcepl@suse.com
|
||||
# Make documentation extensions working with Python 3.6
|
||||
Patch44: doc-py38-to-py36.patch
|
||||
# PATCH-FIX-UPSTREAM bsc1243155-sphinx-non-determinism.patch bsc#1243155 mcepl@suse.com
|
||||
# Doc: Generate ids for audit_events using docname
|
||||
Patch45: bsc1243155-sphinx-non-determinism.patch
|
||||
# PATCH-FIX-OPENSUSE gh139257-Support-docutils-0.22.patch gh#python/cpython#139257 daniel.garcia@suse.com
|
||||
Patch46: gh139257-Support-docutils-0.22.patch
|
||||
BuildRequires: autoconf-archive
|
||||
BuildRequires: automake
|
||||
BuildRequires: fdupes
|
||||
@@ -201,18 +219,18 @@ BuildRequires: mpdecimal-devel
|
||||
BuildRequires: python3-Sphinx >= 4.0.0
|
||||
%if 0%{?suse_version} >= 1500
|
||||
BuildRequires: python3-python-docs-theme >= 2022.1
|
||||
%if 0%{?suse_version} < 1599
|
||||
BuildRequires: python3-dataclasses
|
||||
%endif
|
||||
%endif
|
||||
%endif
|
||||
%if %{with general}
|
||||
# required for idle3 (.desktop and .appdata.xml files)
|
||||
BuildRequires: appstream-glib
|
||||
BuildRequires: gcc-c++
|
||||
BuildRequires: gdbm-devel
|
||||
BuildRequires: gettext
|
||||
BuildRequires: readline-devel
|
||||
BuildRequires: sqlite-devel
|
||||
BuildRequires: timezone
|
||||
BuildRequires: update-desktop-files
|
||||
BuildRequires: pkgconfig(ncurses)
|
||||
BuildRequires: pkgconfig(tk)
|
||||
BuildRequires: pkgconfig(x11)
|
||||
@@ -425,8 +443,10 @@ This package contains libpython3.2 shared library for embedding in
|
||||
other applications.
|
||||
|
||||
%prep
|
||||
%setup -q -n %{tarname}
|
||||
%autopatch -p1
|
||||
%autosetup -p1 -n %{tarname}
|
||||
|
||||
# Fix devhelp doc build gh#python/cpython#120150
|
||||
echo "master_doc = 'contents'" >> Doc/conf.py
|
||||
|
||||
# drop Autoconf version requirement
|
||||
sed -i 's/^AC_PREREQ/dnl AC_PREREQ/' configure.ac
|
||||
@@ -460,7 +480,8 @@ rm Lib/site-packages/README.txt
|
||||
tar xvf %{SOURCE21}
|
||||
|
||||
# Don't fail on warnings when building documentation
|
||||
# sed -i -e '/^SPHINXERRORHANDLING/s/-W//' Doc/Makefile
|
||||
sed -i -e '/^SPHINXERRORHANDLING/s/--fail-on-warning//' Doc/Makefile
|
||||
sed -i -e '/^SPHINXERRORHANDLING/s/-W//' Doc/Makefile
|
||||
|
||||
%build
|
||||
%if %{with doc}
|
||||
@@ -469,7 +490,7 @@ TODAY_DATE=`date -r %{SOURCE0} "+%%B %%d, %%Y"`
|
||||
|
||||
cd Doc
|
||||
sed -i "s/^today = .*/today = '$TODAY_DATE'/" conf.py
|
||||
%make_build -j1 html
|
||||
%make_build -j1 JOBS=1 html
|
||||
|
||||
# Build also devhelp files
|
||||
sphinx-build -a -b devhelp . build/devhelp
|
||||
@@ -551,7 +572,12 @@ EXCLUDE="$EXCLUDE test_faulthandler"
|
||||
%endif
|
||||
# some tests break in QEMU
|
||||
%if 0%{?qemu_user_space_build}
|
||||
EXCLUDE="$EXCLUDE test_faulthandler test_multiprocessing_forkserver test_multiprocessing_spawn test_os test_posix test_signal test_socket test_subprocess"
|
||||
# test_faulthandler: test_register_chain is racy
|
||||
# test_os: test_fork_warns_when_non_python_thread_exists fails
|
||||
# test_posix: qemu does not support fexecve in test_fexecve
|
||||
# test_signal: qemu crashes in test_stress_modifying_handlers
|
||||
# test_socket: many CmsgTrunc tests fail
|
||||
EXCLUDE="$EXCLUDE test_faulthandler test_os test_posix test_signal test_socket"
|
||||
%endif
|
||||
|
||||
# This test (part of test_uuid) requires real network interfaces
|
||||
@@ -663,12 +689,10 @@ done
|
||||
cp %{SOURCE19} idle%{python_version}.desktop
|
||||
sed -i -e 's:idle3:idle%{python_version}:g' idle%{python_version}.desktop
|
||||
install -m 644 -D -t %{buildroot}%{_datadir}/applications idle%{python_version}.desktop
|
||||
%suse_update_desktop_file idle%{python_version}
|
||||
|
||||
cp %{SOURCE20} idle%{python_version}.appdata.xml
|
||||
sed -i -e 's:idle3.desktop:idle%{python_version}.desktop:g' idle%{python_version}.appdata.xml
|
||||
install -m 644 -D -t %{buildroot}%{_datadir}/metainfo idle%{python_version}.appdata.xml
|
||||
appstream-util validate-relax --nonet %{buildroot}%{_datadir}/metainfo/idle%{python_version}.appdata.xml
|
||||
|
||||
%fdupes %{buildroot}/%{_libdir}/python%{python_version}
|
||||
%endif
|
||||
@@ -705,7 +729,7 @@ rm %{buildroot}%{_libdir}/libpython3.so
|
||||
rm %{buildroot}%{_libdir}/pkgconfig/{python3,python3-embed}.pc
|
||||
%endif
|
||||
|
||||
%if %{suse_version} > 1550
|
||||
%if %{with externally_managed}
|
||||
# PEP-0668 mark this as a distro maintained python
|
||||
sed -e 's,__PYTHONPREFIX__,%{python_pkg_name},' -e 's,__PYTHON__,python%{python_version},' < %{SOURCE4} > %{buildroot}%{sitedir}/EXTERNALLY-MANAGED
|
||||
%endif
|
||||
@@ -727,7 +751,7 @@ rm %{buildroot}%{_bindir}/2to3
|
||||
# documentation
|
||||
export PDOCS=%{buildroot}%{_docdir}/%{name}
|
||||
install -d -m 755 $PDOCS
|
||||
install -c -m 644 %{SOURCE3} $PDOCS/
|
||||
install -c -m 644 %{SOURCE4} $PDOCS/
|
||||
install -c -m 644 README.rst $PDOCS/
|
||||
|
||||
# tools
|
||||
@@ -746,6 +770,9 @@ install -m 755 -D Tools/gdb/libpython.py %{buildroot}%{_datadir}/gdb/auto-load/%
|
||||
# install devel files to /config
|
||||
#cp Makefile Makefile.pre.in Makefile.pre $RPM_BUILD_ROOT%{sitedir}/config-%{python_abi}/
|
||||
|
||||
# Remove -IVendor/ from python-config boo#1231795
|
||||
sed -i 's/-IVendor\///' %{buildroot}%{_bindir}/python%{python_abi}-config
|
||||
|
||||
# RPM macros
|
||||
%if %{primary_interpreter}
|
||||
mkdir -p %{buildroot}%{_rpmconfigdir}/macros.d/
|
||||
@@ -762,6 +789,9 @@ LD_LIBRARY_PATH=. ./python -O -c "from py_compile import compile; compile('$FAIL
|
||||
cd $FAILDIR
|
||||
while read package modules; do
|
||||
for module in $modules; do
|
||||
%if 0%{?suse_version} >= 1599
|
||||
test $module = nis && continue
|
||||
%endif
|
||||
ln import_failed.py $module.py
|
||||
pushd __pycache__
|
||||
for i in import_failed*; do
|
||||
@@ -774,25 +804,26 @@ LD_LIBRARY_PATH=. ./python -O -c "from py_compile import compile; compile('$FAIL
|
||||
echo %{sitedir}/_import_failed > %{buildroot}/%{sitedir}/site-packages/zzzz-import-failed-hooks.pth
|
||||
%endif
|
||||
|
||||
# For the purposes of reproducibility, it is necessary to eliminate any *.pyc files inside documentation dirs
|
||||
if [ -d %{buildroot}%{_defaultdocdir} ] ; then
|
||||
find %{buildroot}%{_defaultdocdir} -type f -name \*.pyc -ls -exec rm -vf '{}' \;
|
||||
fi
|
||||
|
||||
%if %{with general}
|
||||
%files -n %{python_pkg_name}-tk
|
||||
%defattr(644, root, root, 755)
|
||||
%{sitedir}/tkinter
|
||||
%{dynlib _tkinter}
|
||||
|
||||
%files -n %{python_pkg_name}-curses
|
||||
%defattr(644, root, root, 755)
|
||||
%{sitedir}/curses
|
||||
%{dynlib _curses}
|
||||
|
||||
%files -n %{python_pkg_name}-dbm
|
||||
%defattr(644, root, root, 755)
|
||||
%{sitedir}/dbm
|
||||
%{dynlib _dbm}
|
||||
%{dynlib _gdbm}
|
||||
|
||||
%files -n %{python_pkg_name}
|
||||
%defattr(644, root, root, 755)
|
||||
%dir %{sitedir}
|
||||
%dir %{sitedir}/lib-dynload
|
||||
%{sitedir}/sqlite3
|
||||
@@ -803,7 +834,6 @@ echo %{sitedir}/_import_failed > %{buildroot}/%{sitedir}/site-packages/zzzz-impo
|
||||
%endif
|
||||
|
||||
%files -n %{python_pkg_name}-idle
|
||||
%defattr(644, root, root, 755)
|
||||
%{sitedir}/idlelib
|
||||
%dir %{_sysconfdir}/idle%{python_version}
|
||||
%config %{_sysconfdir}/idle%{python_version}/*
|
||||
@@ -840,11 +870,9 @@ echo %{sitedir}/_import_failed > %{buildroot}/%{sitedir}/site-packages/zzzz-impo
|
||||
%postun -n libpython%{so_version} -p /sbin/ldconfig
|
||||
|
||||
%files -n libpython%{so_version}
|
||||
%defattr(644, root,root)
|
||||
%{_libdir}/libpython%{python_abi}.so.%{so_major}.%{so_minor}
|
||||
|
||||
%files -n %{python_pkg_name}-tools
|
||||
%defattr(644, root, root, 755)
|
||||
%{sitedir}/turtledemo
|
||||
%if %{primary_interpreter}
|
||||
%{_bindir}/2to3
|
||||
@@ -853,7 +881,6 @@ echo %{sitedir}/_import_failed > %{buildroot}/%{sitedir}/site-packages/zzzz-impo
|
||||
%doc %{_docdir}/%{name}/Tools
|
||||
|
||||
%files -n %{python_pkg_name}-devel
|
||||
%defattr(644, root, root, 755)
|
||||
%{_libdir}/libpython%{python_abi}.so
|
||||
%if %{primary_interpreter}
|
||||
%{_libdir}/libpython3.so
|
||||
@@ -861,7 +888,6 @@ echo %{sitedir}/_import_failed > %{buildroot}/%{sitedir}/site-packages/zzzz-impo
|
||||
%{_libdir}/pkgconfig/*
|
||||
%{_includedir}/python%{python_abi}
|
||||
%{sitedir}/config-%{python_abi}-*
|
||||
%defattr(755, root, root)
|
||||
%{_bindir}/python%{python_abi}-config
|
||||
%if %{primary_interpreter}
|
||||
%{_bindir}/python3-config
|
||||
@@ -874,7 +900,6 @@ echo %{sitedir}/_import_failed > %{buildroot}/%{sitedir}/site-packages/zzzz-impo
|
||||
%{_datadir}/gdb/auto-load/%{_libdir}/libpython%{python_abi}.so.%{so_major}.%{so_minor}-gdb.py
|
||||
|
||||
%files -n %{python_pkg_name}-testsuite
|
||||
%defattr(644, root, root, 755)
|
||||
%{sitedir}/test
|
||||
# %%{sitedir}/*/test
|
||||
# %%{sitedir}/*/tests
|
||||
@@ -893,7 +918,6 @@ echo %{sitedir}/_import_failed > %{buildroot}/%{sitedir}/site-packages/zzzz-impo
|
||||
%dir %{sitedir}/tkinter
|
||||
|
||||
%files -n %{python_pkg_name}-base
|
||||
%defattr(644, root, root, 755)
|
||||
# docs
|
||||
%dir %{_docdir}/%{name}
|
||||
%doc %{_docdir}/%{name}/README.rst
|
||||
@@ -903,7 +927,7 @@ echo %{sitedir}/_import_failed > %{buildroot}/%{sitedir}/site-packages/zzzz-impo
|
||||
%{_mandir}/man1/python3.1%{?ext_man}
|
||||
%endif
|
||||
%{_mandir}/man1/python%{python_version}.1%{?ext_man}
|
||||
%if %{suse_version} > 1550
|
||||
%if %{with externally_managed}
|
||||
# PEP-0668
|
||||
%{sitedir}/EXTERNALLY-MANAGED
|
||||
%endif
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
---
|
||||
Lib/test/test_subprocess.py | 3 ++-
|
||||
1 file changed, 2 insertions(+), 1 deletion(-)
|
||||
Lib/test/test_subprocess.py | 6 +++++-
|
||||
1 file changed, 5 insertions(+), 1 deletion(-)
|
||||
|
||||
Index: Python-3.12.2/Lib/test/test_subprocess.py
|
||||
Index: Python-3.12.10/Lib/test/test_subprocess.py
|
||||
===================================================================
|
||||
--- Python-3.12.2.orig/Lib/test/test_subprocess.py
|
||||
+++ Python-3.12.2/Lib/test/test_subprocess.py
|
||||
@@ -281,7 +281,8 @@ class ProcessTestCase(BaseTestCase):
|
||||
"time.sleep(3600)"],
|
||||
# Some heavily loaded buildbots (sparc Debian 3.x) require
|
||||
# this much time to start and print.
|
||||
- timeout=3)
|
||||
+ # OBS might require even more
|
||||
+ timeout=10)
|
||||
self.fail("Expected TimeoutExpired.")
|
||||
self.assertEqual(c.exception.output, b'BDFL')
|
||||
--- Python-3.12.10.orig/Lib/test/test_subprocess.py 2025-04-11 21:04:45.154639562 +0200
|
||||
+++ Python-3.12.10/Lib/test/test_subprocess.py 2025-04-11 21:12:03.374471647 +0200
|
||||
@@ -274,7 +274,11 @@
|
||||
output = subprocess.check_output(
|
||||
[sys.executable, "-c",
|
||||
"import time; time.sleep(3600)"],
|
||||
- timeout=0.1)
|
||||
+ # Some heavily loaded buildbots (sparc Debian 3.x) require
|
||||
+ # this much time to start and print.
|
||||
+ # timeout=0.1)
|
||||
+ # OBS might require even more
|
||||
+ timeout=10)
|
||||
|
||||
def test_call_kwargs(self):
|
||||
# call() function with keyword args
|
||||
|
||||
Reference in New Issue
Block a user