- Add gh120226-fix-sendfile-test-kernel-610.patch to avoid

failing test_sendfile_close_peer_in_the_middle_of_receiving
  tests on Linux >= 6.10 (GH-120227).

OBS-URL: https://build.opensuse.org/package/show/devel:languages:python:Factory/python312?expand=0&rev=65
This commit is contained in:
Matej Cepl 2024-09-02 10:08:57 +00:00 committed by Git OBS Bridge
commit 456c5f3ff6
39 changed files with 9788 additions and 0 deletions

23
.gitattributes vendored Normal file
View File

@ -0,0 +1,23 @@
## Default LFS
*.7z filter=lfs diff=lfs merge=lfs -text
*.bsp filter=lfs diff=lfs merge=lfs -text
*.bz2 filter=lfs diff=lfs merge=lfs -text
*.gem filter=lfs diff=lfs merge=lfs -text
*.gz filter=lfs diff=lfs merge=lfs -text
*.jar filter=lfs diff=lfs merge=lfs -text
*.lz filter=lfs diff=lfs merge=lfs -text
*.lzma filter=lfs diff=lfs merge=lfs -text
*.obscpio filter=lfs diff=lfs merge=lfs -text
*.oxt filter=lfs diff=lfs merge=lfs -text
*.pdf filter=lfs diff=lfs merge=lfs -text
*.png filter=lfs diff=lfs merge=lfs -text
*.rpm filter=lfs diff=lfs merge=lfs -text
*.tbz filter=lfs diff=lfs merge=lfs -text
*.tbz2 filter=lfs diff=lfs merge=lfs -text
*.tgz filter=lfs diff=lfs merge=lfs -text
*.ttf filter=lfs diff=lfs merge=lfs -text
*.txz filter=lfs diff=lfs merge=lfs -text
*.whl filter=lfs diff=lfs merge=lfs -text
*.xz filter=lfs diff=lfs merge=lfs -text
*.zip filter=lfs diff=lfs merge=lfs -text
*.zst filter=lfs diff=lfs merge=lfs -text

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
.osc

View File

@ -0,0 +1,474 @@
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.

View File

@ -0,0 +1,67 @@
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, _):

View File

@ -0,0 +1,171 @@
---
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(+)
Index: Python-3.12.4/Lib/tempfile.py
===================================================================
--- Python-3.12.4.orig/Lib/tempfile.py
+++ Python-3.12.4/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.
Index: Python-3.12.4/Lib/test/test_tempfile.py
===================================================================
--- Python-3.12.4.orig/Lib/test/test_tempfile.py
+++ Python-3.12.4/Lib/test/test_tempfile.py
@@ -1803,6 +1803,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
@@ -1977,6 +2074,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()
Index: Python-3.12.4/Misc/NEWS.d/next/Library/2022-12-01-16-57-44.gh-issue-91133.LKMVCV.rst
===================================================================
--- /dev/null
+++ Python-3.12.4/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.

View File

@ -0,0 +1,148 @@
---
Lib/test/test_zipfile/_path/test_path.py | 78 ++++++++++
Lib/zipfile/_path/__init__.py | 18 ++
Misc/NEWS.d/next/Library/2024-08-11-14-08-04.gh-issue-122905.7tDsxA.rst | 1
Misc/NEWS.d/next/Library/2024-08-26-13-45-20.gh-issue-123270.gXHvNJ.rst | 3
4 files changed, 98 insertions(+), 2 deletions(-)
--- a/Lib/test/test_zipfile/_path/test_path.py
+++ b/Lib/test/test_zipfile/_path/test_path.py
@@ -4,6 +4,7 @@ import contextlib
import pathlib
import pickle
import sys
+import time
import unittest
import zipfile
@@ -577,3 +578,80 @@ class TestPath(unittest.TestCase):
zipfile.Path(alpharep)
with self.assertRaises(KeyError):
alpharep.getinfo('does-not-exist')
+
+ def test_malformed_paths(self):
+ """
+ Path should handle malformed paths gracefully.
+
+ Paths with leading slashes are not visible.
+
+ Paths with dots are treated like regular files.
+ """
+ data = io.BytesIO()
+ zf = zipfile.ZipFile(data, "w")
+ zf.writestr("/one-slash.txt", b"content")
+ zf.writestr("//two-slash.txt", b"content")
+ zf.writestr("../parent.txt", b"content")
+ zf.filename = ''
+ root = zipfile.Path(zf)
+ assert list(map(str, root.iterdir())) == ['../']
+ assert root.joinpath('..').joinpath('parent.txt').read_bytes() == b'content'
+
+ def test_unsupported_names(self):
+ """
+ Path segments with special characters are readable.
+
+ On some platforms or file systems, characters like
+ ``:`` and ``?`` are not allowed, but they are valid
+ in the zip file.
+ """
+ data = io.BytesIO()
+ zf = zipfile.ZipFile(data, "w")
+ zf.writestr("path?", b"content")
+ zf.writestr("V: NMS.flac", b"fLaC...")
+ zf.filename = ''
+ root = zipfile.Path(zf)
+ contents = root.iterdir()
+ assert next(contents).name == 'path?'
+ assert next(contents).name == 'V: NMS.flac'
+ assert root.joinpath('V: NMS.flac').read_bytes() == b"fLaC..."
+
+ def test_backslash_not_separator(self):
+ """
+ In a zip file, backslashes are not separators.
+ """
+ data = io.BytesIO()
+ zf = zipfile.ZipFile(data, "w")
+ zf.writestr(DirtyZipInfo.for_name("foo\\bar", zf), b"content")
+ zf.filename = ''
+ root = zipfile.Path(zf)
+ (first,) = root.iterdir()
+ assert not first.is_dir()
+ assert first.name == 'foo\\bar'
+
+
+class DirtyZipInfo(zipfile.ZipInfo):
+ """
+ Bypass name sanitization.
+ """
+
+ def __init__(self, filename, *args, **kwargs):
+ super().__init__(filename, *args, **kwargs)
+ self.filename = filename
+
+ @classmethod
+ def for_name(cls, name, archive):
+ """
+ Construct the same way that ZipFile.writestr does.
+
+ TODO: extract this functionality and re-use
+ """
+ self = cls(filename=name, date_time=time.localtime(time.time())[:6])
+ self.compress_type = archive.compression
+ self.compress_level = archive.compresslevel
+ if self.filename.endswith('/'): # pragma: no cover
+ self.external_attr = 0o40775 << 16 # drwxrwxr-x
+ self.external_attr |= 0x10 # MS-DOS directory flag
+ else:
+ self.external_attr = 0o600 << 16 # ?rw-------
+ return self
--- a/Lib/zipfile/_path/__init__.py
+++ b/Lib/zipfile/_path/__init__.py
@@ -1,3 +1,12 @@
+"""
+A Path-like interface for zipfiles.
+
+This codebase is shared between zipfile.Path in the stdlib
+and zipp in PyPI. See
+https://github.com/python/importlib_metadata/wiki/Development-Methodology
+for more detail.
+"""
+
import io
import posixpath
import zipfile
@@ -34,7 +43,7 @@ def _parents(path):
def _ancestry(path):
"""
Given a path with elements separated by
- posixpath.sep, generate all elements of that path
+ posixpath.sep, generate all elements of that path.
>>> list(_ancestry('b/d'))
['b/d', 'b']
@@ -46,9 +55,14 @@ def _ancestry(path):
['b']
>>> list(_ancestry(''))
[]
+
+ Multiple separators are treated like a single.
+
+ >>> list(_ancestry('//b//d///f//'))
+ ['//b//d///f', '//b//d', '//b']
"""
path = path.rstrip(posixpath.sep)
- while path and path != posixpath.sep:
+ while path.rstrip(posixpath.sep):
yield path
path, tail = posixpath.split(path)
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2024-08-11-14-08-04.gh-issue-122905.7tDsxA.rst
@@ -0,0 +1 @@
+:class:`zipfile.Path` objects now sanitize names from the zipfile.
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2024-08-26-13-45-20.gh-issue-123270.gXHvNJ.rst
@@ -0,0 +1,3 @@
+Applied a more surgical fix for malformed payloads in :class:`zipfile.Path`
+causing infinite loops (gh-122905) without breaking contents using
+legitimate characters.

View File

@ -0,0 +1,152 @@
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/site.py | 9 ++++++-
Lib/sysconfig.py | 49 +++++++++++++++++++++++++++++++++++++-
Lib/test/test_sysconfig.py | 17 +++++++++++--
3 files changed, 71 insertions(+), 4 deletions(-)
Index: Python-3.12.4/Lib/sysconfig.py
===================================================================
--- Python-3.12.4.orig/Lib/sysconfig.py
+++ Python-3.12.4/Lib/sysconfig.py
@@ -104,6 +104,11 @@ if os.name == 'nt':
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,6 +168,19 @@ if _HAS_USER_BASE:
},
}
+# 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')
@@ -263,11 +281,40 @@ def _extend_dict(target_dict, other_dict
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.4/Lib/test/test_sysconfig.py
===================================================================
--- Python-3.12.4.orig/Lib/test/test_sysconfig.py
+++ Python-3.12.4/Lib/test/test_sysconfig.py
@@ -110,8 +110,19 @@ class TestSysConfig(unittest.TestCase):
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),
)
@@ -344,7 +355,7 @@ class TestSysConfig(unittest.TestCase):
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)))
@@ -356,6 +367,8 @@ class TestSysConfig(unittest.TestCase):
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

26
PACKAGING-NOTES Normal file
View File

@ -0,0 +1,26 @@
Notes for packagers of Python3
==============================
0. Faster build turnaround
--------------------------
By default, python builds with profile-guided optimization. This needs
an additional run of the test suite and it is generally slow.
PGO build takes around 50 minutes.
For development, use "--without profileopt" option to disable PGO. This
shortens the build time to ~5 minutes including test suite.
1. import_failed.map
----------------------
This is a mechanism installed as part of python3-base, that places shim modules
on python's path (through a generated zzzz-import-failed-hooks.pth file, so that
it is imported as much at the end as makes sense; and an _import_failed subdir
of /usr/lib/pythonX.Y). Then when the user tries to import a module that is part
of a subpackage, the ImportError will contain a helpful message telling them
which missing subpackage to install.
This can sometimes cause problems on non-standard configurations, if the pth
gets included too early (for instance if you are using a script to include all
pths by hand in some strange order). Just something to look out for.

3
Python-3.12.3.tar.xz Normal file
View File

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:56bfef1fdfc1221ce6720e43a661e3eb41785dd914ce99698d8c7896af4bdaa1
size 20625068

18
Python-3.12.3.tar.xz.asc Normal file
View File

@ -0,0 +1,18 @@
-----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-----

3
Python-3.12.5.tar.xz Normal file
View File

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:fa8a2e12c5e620b09f53e65bcd87550d2e5a1e2e04bf8ba991dcc55113876397
size 20422396

18
Python-3.12.5.tar.xz.asc Normal file
View File

@ -0,0 +1,18 @@
-----BEGIN PGP SIGNATURE-----
iQKTBAABCgB9FiEEcWlgX2LHUTVtBUomqCHmgOX6YwUFAmayiFtfFIAAAAAALgAo
aXNzdWVyLWZwckBub3RhdGlvbnMub3BlbnBncC5maWZ0aGhvcnNlbWFuLm5ldDcx
Njk2MDVGNjJDNzUxMzU2RDA1NEEyNkE4MjFFNjgwRTVGQTYzMDUACgkQqCHmgOX6
YwUr4g//VyVs9tvbtiSp8pGe8f1gYErEw54r124sL/CBuNii8Irts1j5ymGxcm+l
hshPK5UlqRnhd5dCJWFTvLTXa5Ko2R1L3JyyxfGd1hmDuMhrWsDHijI0R7L/mGM5
6X2LTaadBVNvk8HaNKvR8SEWvo68rdnOuYElFA9ir7uqwjO26ZWz9FfH80YDGwo8
Blef2NYw8rNhiaZMFV0HYV7D+YyUAZnFNfW8M7Fd4oskUyj1tD9J89T9FFLYN09d
BcCIf+EdiEfqRpKxH89bW2g52kDrm4jYGONtpyF8eruyS3YwYSbvbuWioBYKmlxC
s51mieXz6G325GTZnmPxLek3ywPv6Gil9y0wH3fIr2BsWsmXust4LBpjDGt56Fy6
seokGBg8xzsBSk3iEqNoFmNsy/QOiuCcDejX4XqBDNodOlETQPJb07TkTI2iOmg9
NG4Atiz1HvGVxK68UuK9IIcNHyaWUmH8h4VQFGvc6KV6feP5Nm21Y12PZ5XIqJBO
Y8M/VJIJ5koaNPQfnBbbI5YBkUr4BVpIXIpY5LM/L5sUo2C3R7hMi0VGK88HGfSQ
KV4JmZgf6RMBNmrWY12sryS1QQ6q3P110GTUGQWB3sxxNbhmfcrK+4viqHc83yDz
ifmk33HuqaQGU7OzUMHeNcoCJIPo3H1FpoHOn9wLLCtA1pT+as4=
=t0Rk
-----END PGP SIGNATURE-----

43
README.SUSE Normal file
View File

@ -0,0 +1,43 @@
Python 3 in SUSE
==============
* Subpackages *
Python 3 is split into several subpackages, based on external dependencies.
The main package 'python3' has soft dependencies on all subpackages needed to
assemble the standard library; however, these might not all be installed by default.
If you attempt to import a module that is currently not installed, an ImportError is thrown,
with instructions to install the missing subpackage. Installing the subpackage might result
in installing libraries that the subpackage requires to function.
* ensurepip *
The 'ensurepip' module from Python 3 standard library (PEP 453) is supposed to deploy
a bundled copy of the pip installer. This makes no sense in a managed distribution like SUSE.
Instead, you need to install package 'python3-pip'. Usually this will be installed automatically
with 'python3'.
Using 'ensurepip' when pip is not installed will result in an ImportError with instructions
to install 'python3-pip'.
* Documentation *
You can find documentation in seprarate packages: python3-doc and
python3-doc-pdf. These contan following documents:
Tutorial, What's New in Python, Global Module Index, Library Reference,
Macintosh Module Reference, Installing Python Modules, Distributing Python
Modules, Language Reference, Extending and Embedding, Python/C API,
Documenting Python
The python3-doc package constains many text files from source tarball.
* Interactive mode *
Interactive mode is by default enhanced with of history and command completion.
If you don't like these features, you can unset the PYTHONSTARTUP variable
in your .profile or disable it system wide in /etc/profile.d/python.sh.

4
_multibuild Normal file
View File

@ -0,0 +1,4 @@
<multibuild>
<package>base</package>
<package>doc</package>
</multibuild>

3
baselibs.conf Normal file
View File

@ -0,0 +1,3 @@
python312-base
python312
libpython3_12-1_0

BIN
bluez-devel-vendor.tar.xz (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -0,0 +1,173 @@
From 5754521af1d51aa8e445cba07a093bbc0c88596d Mon Sep 17 00:00:00 2001
From: Zackery Spytz <zspytz@gmail.com>
Date: Mon, 16 Dec 2019 18:24:08 -0700
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 +++--
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(-)
create mode 100644 Misc/NEWS.d/next/Build/2019-12-16-17-50-42.bpo-31046.XA-Qfr.rst
Index: Python-3.12.4/Doc/library/ensurepip.rst
===================================================================
--- Python-3.12.4.orig/Doc/library/ensurepip.rst
+++ Python-3.12.4/Doc/library/ensurepip.rst
@@ -59,8 +59,9 @@ is at least as recent as the one availab
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:
+* ``--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
Returns a string specifying the available version of pip that will be
installed when bootstrapping an environment.
-.. function:: bootstrap(root=None, upgrade=False, user=False, \
+.. function:: bootstrap(root=None, prefix=None, upgrade=False, user=False, \
altinstall=False, default_pip=False, \
verbosity=0)
@@ -102,6 +103,8 @@ Module API
If *root* is ``None``, then installation uses the default install location
for the current environment.
+ *prefix* specifies the directory prefix to use when installing.
+
*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
*verbosity* controls the level of output to :data:`sys.stdout` from the
bootstrapping operation.
+ .. versionchanged:: 3.9 the *prefix* parameter was added.
+
.. audit-event:: ensurepip.bootstrap root ensurepip.bootstrap
.. note::
Index: Python-3.12.4/Lib/ensurepip/__init__.py
===================================================================
--- Python-3.12.4.orig/Lib/ensurepip/__init__.py
+++ Python-3.12.4/Lib/ensurepip/__init__.py
@@ -120,27 +120,27 @@ def _disable_pip_configuration_settings(
os.environ['PIP_CONFIG_FILE'] = os.devnull
-def bootstrap(*, root=None, upgrade=False, user=False,
+def bootstrap(*, root=None, prefix=None, upgrade=False, user=False,
altinstall=False, default_pip=False,
verbosity=0):
"""
Bootstrap pip into the current Python installation (or the given root
- directory).
+ and directory prefix).
Note that calling this function will alter both sys.path and os.environ.
"""
# Discard the return value
- _bootstrap(root=root, upgrade=upgrade, user=user,
+ _bootstrap(root=root, prefix=prefix, upgrade=upgrade, user=user,
altinstall=altinstall, default_pip=default_pip,
verbosity=verbosity)
-def _bootstrap(*, root=None, upgrade=False, user=False,
+def _bootstrap(*, root=None, prefix=None, upgrade=False, user=False,
altinstall=False, default_pip=False,
verbosity=0):
"""
Bootstrap pip into the current Python installation (or the given root
- directory). Returns pip command status code.
+ and directory prefix). Returns pip command status code.
Note that calling this function will alter both sys.path and os.environ.
"""
@@ -190,6 +190,8 @@ def _bootstrap(*, root=None, upgrade=Fal
args = ["install", "--no-cache-dir", "--no-index", "--find-links", tmpdir]
if root:
args += ["--root", root]
+ if prefix:
+ args += ["--prefix", prefix]
if upgrade:
args += ["--upgrade"]
if user:
@@ -265,6 +267,11 @@ def _main(argv=None):
help="Install everything relative to this alternate root directory.",
)
parser.add_argument(
+ "--prefix",
+ default=None,
+ help="Install everything using this prefix.",
+ )
+ parser.add_argument(
"--altinstall",
action="store_true",
default=False,
@@ -283,6 +290,7 @@ def _main(argv=None):
return _bootstrap(
root=args.root,
+ prefix=args.prefix,
upgrade=args.upgrade,
user=args.user,
verbosity=args.verbosity,
Index: Python-3.12.4/Lib/test/test_ensurepip.py
===================================================================
--- Python-3.12.4.orig/Lib/test/test_ensurepip.py
+++ Python-3.12.4/Lib/test/test_ensurepip.py
@@ -105,6 +105,17 @@ class TestBootstrap(EnsurepipMixin, unit
unittest.mock.ANY,
)
+ def test_bootstrapping_with_prefix(self):
+ ensurepip.bootstrap(prefix="/foo/bar/")
+ self.run_pip.assert_called_once_with(
+ [
+ "install", "--no-cache-dir", "--no-index", "--find-links",
+ unittest.mock.ANY, "--prefix", "/foo/bar/",
+ "pip",
+ ],
+ unittest.mock.ANY,
+ )
+
def test_bootstrapping_with_user(self):
ensurepip.bootstrap(user=True)
Index: Python-3.12.4/Makefile.pre.in
===================================================================
--- Python-3.12.4.orig/Makefile.pre.in
+++ Python-3.12.4/Makefile.pre.in
@@ -1914,7 +1914,7 @@ install: @FRAMEWORKINSTALLFIRST@ commoni
install|*) ensurepip="" ;; \
esac; \
$(RUNSHARED) $(PYTHON_FOR_BUILD) -m ensurepip \
- $$ensurepip --root=$(DESTDIR)/ ; \
+ $$ensurepip --root=$(DESTDIR)/ --prefix=$(prefix) ; \
fi
.PHONY: altinstall
@@ -1925,7 +1925,7 @@ altinstall: commoninstall
install|*) ensurepip="--altinstall" ;; \
esac; \
$(RUNSHARED) $(PYTHON_FOR_BUILD) -m ensurepip \
- $$ensurepip --root=$(DESTDIR)/ ; \
+ $$ensurepip --root=$(DESTDIR)/ --prefix=$(prefix) ; \
fi
.PHONY: commoninstall
Index: Python-3.12.4/Misc/NEWS.d/next/Build/2019-12-16-17-50-42.bpo-31046.XA-Qfr.rst
===================================================================
--- /dev/null
+++ Python-3.12.4/Misc/NEWS.d/next/Build/2019-12-16-17-50-42.bpo-31046.XA-Qfr.rst
@@ -0,0 +1 @@
+A directory prefix can now be specified when using :mod:`ensurepip`.

View File

@ -0,0 +1,56 @@
---
Doc/tools/extensions/c_annotations.py | 6 +++++-
Doc/tools/extensions/glossary_search.py | 12 ++++++++++--
Doc/tools/extensions/pyspecific.py | 5 ++++-
3 files changed, 19 insertions(+), 4 deletions(-)
--- a/Doc/tools/extensions/c_annotations.py
+++ b/Doc/tools/extensions/c_annotations.py
@@ -118,7 +118,11 @@ def add_annotations(app: Sphinx, doctree
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
--- a/Doc/tools/extensions/glossary_search.py
+++ b/Doc/tools/extensions/glossary_search.py
@@ -30,8 +30,16 @@ def process_glossary_nodes(
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]
--- a/Doc/tools/extensions/pyspecific.py
+++ b/Doc/tools/extensions/pyspecific.py
@@ -27,7 +27,10 @@ from sphinx.locale import _ as sphinx_ge
from sphinx.util import logging
from sphinx.util.docutils import SphinxDirective
from sphinx.writers.text import TextWriter, TextTranslator
-from sphinx.util.display import status_iterator
+try:
+ from sphinx.util.display import status_iterator
+except ModuleNotFoundError:
+ from sphinx.util import status_iterator
ISSUE_URI = 'https://bugs.python.org/issue?@action=redirect&bpo=%s'

12
externally_managed.in Normal file
View File

@ -0,0 +1,12 @@
[externally-managed]
Error=To install Python packages system-wide, try
zypper install __PYTHONPREFIX__-xyz, where xyz is the package
you are trying to install.
If you wish to install a non-rpm packaged Python package,
create a virtual environment using __PYTHON__ -m venv path/to/venv.
Then use path/to/venv/bin/python and path/to/venv/bin/pip.
If you wish to install a non-rpm packaged Python application,
it may be easiest to use `pipx install xyz`, which will manage a
virtual environment for you. Install pipx via `zypper install __PYTHONPREFIX__-pipx` .

View File

@ -0,0 +1,30 @@
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
@@ -14,6 +14,9 @@ from test.support import (script_helper,
requires_specialization, C_RECURSION_LIMIT)
from test.support.os_helper import FakePath
+IS_SLE_15_6 = os.environ.get("SLE_VERSION", "") == "0150600"
+IS_32bit = hasattr(os, "uname") and os.uname().machine in ["i386", "i486", "i586", "i686"]
+
class TestSpecifics(unittest.TestCase):
def compile_single(self, source):
@@ -110,6 +113,7 @@ class TestSpecifics(unittest.TestCase):
self.assertEqual(d['z'], 12)
@unittest.skipIf(support.is_wasi, "exhausts limited stack on WASI")
+ @unittest.skipIf(IS_SLE_15_6 and IS_32bit, "fails on 15.6 i586")
def test_extended_arg(self):
repeat = int(C_RECURSION_LIMIT * 0.9)
longexpr = 'x = x or ' + '-x' * repeat
@@ -603,6 +607,7 @@ class TestSpecifics(unittest.TestCase):
@support.cpython_only
@unittest.skipIf(support.is_wasi, "exhausts limited stack on WASI")
+ @unittest.skipIf(IS_SLE_15_6 and IS_32bit, "fails on 15.6 i586")
def test_compiler_recursion_limit(self):
# Expected limit is C_RECURSION_LIMIT * 2
# Duplicating the limit here is a little ugly.

32
fix_configure_rst.patch Normal file
View File

@ -0,0 +1,32 @@
---
Doc/using/configure.rst | 2 --
Misc/NEWS | 2 +-
2 files changed, 1 insertion(+), 3 deletions(-)
--- a/Doc/using/configure.rst
+++ b/Doc/using/configure.rst
@@ -640,13 +640,11 @@ macOS Options
See ``Mac/README.rst``.
-.. option:: --enable-universalsdk
.. option:: --enable-universalsdk=SDKDIR
Create a universal binary build. *SDKDIR* specifies which macOS SDK should
be used to perform the build (default is no).
-.. option:: --enable-framework
.. option:: --enable-framework=INSTALLDIR
Create a Python.framework rather than a traditional Unix install. Optional
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -13832,7 +13832,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
- :c:struct:`PyFrameObject` rather than ``_frame``. The non-existing entry
+ :c:type:`PyFrameObject` rather than ``_frame``. The non-existing entry
``_node`` no longer appears in the list.
- bpo-44378: :c:func:`Py_IS_TYPE` no longer uses :c:func:`Py_TYPE` to avoid

View File

@ -0,0 +1,35 @@
From 1b3f6523a5c83323cdc44031b33a1c062e5dc698 Mon Sep 17 00:00:00 2001
From: Xi Ruoyao <xry111@xry111.site>
Date: Fri, 7 Jun 2024 23:51:32 +0800
Subject: [PATCH] gh-120226: Fix
test_sendfile_close_peer_in_the_middle_of_receiving on Linux >= 6.10
(GH-120227)
The worst case is that the kernel buffers 17 pages with a page size of 64k.
(cherry picked from commit a7584245661102a5768c643fbd7db8395fd3c90e)
Co-authored-by: Xi Ruoyao <xry111@xry111.site>
---
Lib/test/test_asyncio/test_sendfile.py | 11 ++++-------
1 file changed, 4 insertions(+), 7 deletions(-)
--- a/Lib/test/test_asyncio/test_sendfile.py
+++ b/Lib/test/test_asyncio/test_sendfile.py
@@ -93,13 +93,10 @@ class MyProto(asyncio.Protocol):
class SendfileBase:
- # 256 KiB plus small unaligned to buffer chunk
- # Newer versions of Windows seems to have increased its internal
- # buffer and tries to send as much of the data as it can as it
- # has some form of buffering for this which is less than 256KiB
- # on newer server versions and Windows 11.
- # So DATA should be larger than 256 KiB to make this test reliable.
- DATA = b"x" * (1024 * 256 + 1)
+ # Linux >= 6.10 seems buffering up to 17 pages of data.
+ # So DATA should be large enough to make this test reliable even with a
+ # 64 KiB page configuration.
+ DATA = b"x" * (1024 * 17 * 64 + 1)
# Reduce socket buffer size to test on relative small data sets.
BUF_SIZE = 4 * 1024 # 4 KiB

35
idle3.appdata.xml Normal file
View File

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Copyright 2017 Zbigniew Jędrzejewski-Szmek -->
<application>
<id type="desktop">idle3.desktop</id>
<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 Pythons Integrated Development and Learning Environment.
The GUI is uniform between Windows, Unix, and Mac OS X.
IDLE provides an easy way to start writing, running, and debugging
Python code.
</p>
<p>
IDLE is written in pure Python, and uses the tkinter GUI toolkit.
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>
</ul>
</description>
<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>
</screenshots>
<update_contact>zbyszek@in.waw.pl</update_contact>
</application>

12
idle3.desktop Normal file
View File

@ -0,0 +1,12 @@
[Desktop Entry]
Version=1.0
Name=IDLE 3
GenericName=Python 3 IDE
Comment=Python 3 Integrated Development and Learning Environment
Exec=idle3 %F
TryExec=idle3
Terminal=false
Type=Application
Icon=idle3
Categories=Development;IDE;
MimeType=text/x-python;

7
import_failed.map Normal file
View File

@ -0,0 +1,7 @@
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

23
import_failed.py Normal file
View File

@ -0,0 +1,23 @@
import sys, os
from sysconfig import get_path
failed_map_path = os.path.join(get_path('stdlib'), '_import_failed', 'import_failed.map')
if __spec__:
failed_name = __spec__.name
else:
failed_name = __name__
with open(failed_map_path) as fd:
for line in fd:
package = line.split(':')[0]
imports = line.split(':')[1]
if failed_name in imports:
raise ImportError(f"""Module '{failed_name}' is not installed.
Use:
sudo zypper install {package}
to install it.""")
raise ImportError(f"""Module '{failed_name}' is not installed.
It is supposed to be part of python3 distribution, but missing from failed import map.
Please file a bug on the SUSE Bugzilla.""")

28
macros.python3 Normal file
View File

@ -0,0 +1,28 @@
%have_python3 1
# commented out legacy macro definitions
#py3_prefix /usr
#py3_incdir /usr/include/python3.5m
#py3_ver 3.5
# these should now be provided by macros.python_all
#python3_sitearch /usr/lib64/python3.5/site-packages
#python3_sitelib /usr/lib/python3.5/site-packages
#python3_version 3.5
# hard to say if anyone ever used these?
#py3_soflags cpython-35m-x86_64-linux-gnu
#py3_abiflags m
%cpython3_soabi %(python3 -c "import sysconfig; print(sysconfig.get_config_var('SOABI'))")
%py3_soflags %cpython3_soabi
# compilation macros that might be in use somewhere
%py3_compile(O) \
find %1 -name '*.pyc' -exec rm -f {} ";"\
python3 -c "import sys, os, compileall; br='%{buildroot}'; compileall.compile_dir(sys.argv[1], ddir=br and (sys.argv[1][len(os.path.abspath(br)):]+'/') or None)" %1\
%{-O:\
find %1 -name '*.pyo' -exec rm -f {} ";"\
python3 -O -c "import sys, os, compileall; br='%{buildroot}'; compileall.compile_dir(sys.argv[1], ddir=br and (sys.argv[1][len(os.path.abspath(br)):]+'/') or None)" %1\
}

647
no-skipif-doctests.patch Normal file
View File

@ -0,0 +1,647 @@
only in patch2:
unchanged:
---
Doc/library/turtle.rst | 81 -------------------------------------------------
1 file changed, 81 deletions(-)
Index: Python-3.12.2/Doc/library/turtle.rst
===================================================================
--- Python-3.12.2.orig/Doc/library/turtle.rst
+++ Python-3.12.2/Doc/library/turtle.rst
@@ -441,7 +441,6 @@ Turtle motion
turtle is headed.
.. doctest::
- :skipif: _tkinter is None
>>> turtle.position()
(0.00,0.00)
@@ -468,7 +467,6 @@ Turtle motion
>>> turtle.goto(0, 0)
.. doctest::
- :skipif: _tkinter is None
>>> turtle.position()
(0.00,0.00)
@@ -487,13 +485,11 @@ Turtle motion
orientation depends on the turtle mode, see :func:`mode`.
.. doctest::
- :skipif: _tkinter is None
:hide:
>>> turtle.setheading(22)
.. doctest::
- :skipif: _tkinter is None
>>> turtle.heading()
22.0
@@ -512,13 +508,11 @@ Turtle motion
orientation depends on the turtle mode, see :func:`mode`.
.. doctest::
- :skipif: _tkinter is None
:hide:
>>> turtle.setheading(22)
.. doctest::
- :skipif: _tkinter is None
>>> turtle.heading()
22.0
@@ -541,13 +535,11 @@ Turtle motion
not change the turtle's orientation.
.. doctest::
- :skipif: _tkinter is None
:hide:
>>> turtle.goto(0, 0)
.. doctest::
- :skipif: _tkinter is None
>>> tp = turtle.pos()
>>> tp
@@ -609,13 +601,11 @@ Turtle motion
unchanged.
.. doctest::
- :skipif: _tkinter is None
:hide:
>>> turtle.goto(0, 240)
.. doctest::
- :skipif: _tkinter is None
>>> turtle.position()
(0.00,240.00)
@@ -631,13 +621,11 @@ Turtle motion
Set the turtle's second coordinate to *y*, leave first coordinate unchanged.
.. doctest::
- :skipif: _tkinter is None
:hide:
>>> turtle.goto(0, 40)
.. doctest::
- :skipif: _tkinter is None
>>> turtle.position()
(0.00,40.00)
@@ -664,7 +652,6 @@ Turtle motion
=================== ====================
.. doctest::
- :skipif: _tkinter is None
>>> turtle.setheading(90)
>>> turtle.heading()
@@ -677,14 +664,12 @@ Turtle motion
its start-orientation (which depends on the mode, see :func:`mode`).
.. doctest::
- :skipif: _tkinter is None
:hide:
>>> turtle.setheading(90)
>>> turtle.goto(0, -10)
.. doctest::
- :skipif: _tkinter is None
>>> turtle.heading()
90.0
@@ -716,7 +701,6 @@ Turtle motion
calculated automatically. May be used to draw regular polygons.
.. doctest::
- :skipif: _tkinter is None
>>> turtle.home()
>>> turtle.position()
@@ -745,7 +729,6 @@ Turtle motion
.. doctest::
- :skipif: _tkinter is None
>>> turtle.home()
>>> turtle.dot()
@@ -763,7 +746,6 @@ Turtle motion
it by calling ``clearstamp(stamp_id)``.
.. doctest::
- :skipif: _tkinter is None
>>> turtle.color("blue")
>>> stamp_id = turtle.stamp()
@@ -778,7 +760,6 @@ Turtle motion
Delete stamp with given *stampid*.
.. doctest::
- :skipif: _tkinter is None
>>> turtle.position()
(150.00,-0.00)
@@ -816,7 +797,6 @@ Turtle motion
undo actions is determined by the size of the undobuffer.
.. doctest::
- :skipif: _tkinter is None
>>> for i in range(4):
... turtle.fd(50); turtle.lt(80)
@@ -849,7 +829,6 @@ Turtle motion
turtle turn instantly.
.. doctest::
- :skipif: _tkinter is None
>>> turtle.speed()
3
@@ -870,7 +849,6 @@ Tell Turtle's state
Return the turtle's current location (x,y) (as a :class:`Vec2D` vector).
.. doctest::
- :skipif: _tkinter is None
>>> turtle.pos()
(440.00,-0.00)
@@ -886,7 +864,6 @@ Tell Turtle's state
orientation which depends on the mode - "standard"/"world" or "logo".
.. doctest::
- :skipif: _tkinter is None
>>> turtle.goto(10, 10)
>>> turtle.towards(0,0)
@@ -898,7 +875,6 @@ Tell Turtle's state
Return the turtle's x coordinate.
.. doctest::
- :skipif: _tkinter is None
>>> turtle.home()
>>> turtle.left(50)
@@ -914,7 +890,6 @@ Tell Turtle's state
Return the turtle's y coordinate.
.. doctest::
- :skipif: _tkinter is None
>>> turtle.home()
>>> turtle.left(60)
@@ -931,7 +906,6 @@ Tell Turtle's state
:func:`mode`).
.. doctest::
- :skipif: _tkinter is None
>>> turtle.home()
>>> turtle.left(67)
@@ -948,7 +922,6 @@ Tell Turtle's state
other turtle, in turtle step units.
.. doctest::
- :skipif: _tkinter is None
>>> turtle.home()
>>> turtle.distance(30,40)
@@ -972,7 +945,6 @@ Settings for measurement
Default value is 360 degrees.
.. doctest::
- :skipif: _tkinter is None
>>> turtle.home()
>>> turtle.left(90)
@@ -995,7 +967,6 @@ Settings for measurement
``degrees(2*math.pi)``.
.. doctest::
- :skipif: _tkinter is None
>>> turtle.home()
>>> turtle.left(90)
@@ -1006,7 +977,6 @@ Settings for measurement
1.5707963267948966
.. doctest::
- :skipif: _tkinter is None
:hide:
>>> turtle.degrees(360)
@@ -1042,7 +1012,6 @@ Drawing state
thickness. If no argument is given, the current pensize is returned.
.. doctest::
- :skipif: _tkinter is None
>>> turtle.pensize()
1
@@ -1074,7 +1043,6 @@ Drawing state
attributes in one statement.
.. doctest::
- :skipif: _tkinter is None
:options: +NORMALIZE_WHITESPACE
>>> turtle.pen(fillcolor="black", pencolor="red", pensize=10)
@@ -1097,7 +1065,6 @@ Drawing state
Return ``True`` if pen is down, ``False`` if it's up.
.. doctest::
- :skipif: _tkinter is None
>>> turtle.penup()
>>> turtle.isdown()
@@ -1138,7 +1105,6 @@ Color control
newly set pencolor.
.. doctest::
- :skipif: _tkinter is None
>>> colormode()
1.0
@@ -1187,7 +1153,6 @@ Color control
with the newly set fillcolor.
.. doctest::
- :skipif: _tkinter is None
>>> turtle.fillcolor("violet")
>>> turtle.fillcolor()
@@ -1226,7 +1191,6 @@ Color control
with the newly set colors.
.. doctest::
- :skipif: _tkinter is None
>>> turtle.color("red", "green")
>>> turtle.color()
@@ -1243,7 +1207,6 @@ Filling
~~~~~~~
.. doctest::
- :skipif: _tkinter is None
:hide:
>>> turtle.home()
@@ -1253,7 +1216,6 @@ Filling
Return fillstate (``True`` if filling, ``False`` else).
.. doctest::
- :skipif: _tkinter is None
>>> turtle.begin_fill()
>>> if turtle.filling():
@@ -1278,7 +1240,6 @@ Filling
above may be either all yellow or have some white regions.
.. doctest::
- :skipif: _tkinter is None
>>> turtle.color("black", "red")
>>> turtle.begin_fill()
@@ -1295,7 +1256,6 @@ More drawing control
variables to the default values.
.. doctest::
- :skipif: _tkinter is None
>>> turtle.goto(0,-22)
>>> turtle.left(100)
@@ -1346,7 +1306,6 @@ Visibility
drawing observably.
.. doctest::
- :skipif: _tkinter is None
>>> turtle.hideturtle()
@@ -1357,7 +1316,6 @@ Visibility
Make the turtle visible.
.. doctest::
- :skipif: _tkinter is None
>>> turtle.showturtle()
@@ -1388,7 +1346,6 @@ Appearance
deal with shapes see Screen method :func:`register_shape`.
.. doctest::
- :skipif: _tkinter is None
>>> turtle.shape()
'classic'
@@ -1414,7 +1371,6 @@ Appearance
``resizemode("user")`` is called by :func:`shapesize` when used with arguments.
.. doctest::
- :skipif: _tkinter is None
>>> turtle.resizemode()
'noresize'
@@ -1438,7 +1394,6 @@ Appearance
of the shape's outline.
.. doctest::
- :skipif: _tkinter is None
>>> turtle.shapesize()
(1.0, 1.0, 1)
@@ -1463,7 +1418,6 @@ Appearance
heading of the turtle are sheared.
.. doctest::
- :skipif: _tkinter is None
>>> turtle.shape("circle")
>>> turtle.shapesize(5,2)
@@ -1480,7 +1434,6 @@ Appearance
change the turtle's heading (direction of movement).
.. doctest::
- :skipif: _tkinter is None
>>> turtle.reset()
>>> turtle.shape("circle")
@@ -1526,7 +1479,6 @@ Appearance
turtle (its direction of movement).
.. doctest::
- :skipif: _tkinter is None
>>> turtle.reset()
>>> turtle.shape("circle")
@@ -1555,7 +1507,6 @@ Appearance
given matrix.
.. doctest::
- :skipif: _tkinter is None
>>> turtle = Turtle()
>>> turtle.shape("square")
@@ -1571,7 +1522,6 @@ Appearance
can be used to define a new shape or components of a compound shape.
.. doctest::
- :skipif: _tkinter is None
>>> turtle.shape("square")
>>> turtle.shapetransform(4, -1, 0, 2)
@@ -1596,7 +1546,6 @@ Using events
procedural way:
.. doctest::
- :skipif: _tkinter is None
>>> def turn(x, y):
... left(180)
@@ -1617,7 +1566,6 @@ Using events
``None``, existing bindings are removed.
.. doctest::
- :skipif: _tkinter is None
>>> class MyTurtle(Turtle):
... def glow(self,x,y):
@@ -1645,7 +1593,6 @@ Using events
mouse-click event on that turtle.
.. doctest::
- :skipif: _tkinter is None
>>> turtle.ondrag(turtle.goto)
@@ -1673,7 +1620,6 @@ Special Turtle methods
Return the last recorded polygon.
.. doctest::
- :skipif: _tkinter is None
>>> turtle.home()
>>> turtle.begin_poly()
@@ -1693,7 +1639,6 @@ Special Turtle methods
turtle properties.
.. doctest::
- :skipif: _tkinter is None
>>> mick = Turtle()
>>> joe = mick.clone()
@@ -1706,7 +1651,6 @@ Special Turtle methods
return the "anonymous turtle":
.. doctest::
- :skipif: _tkinter is None
>>> pet = getturtle()
>>> pet.fd(50)
@@ -1720,7 +1664,6 @@ Special Turtle methods
TurtleScreen methods can then be called for that object.
.. doctest::
- :skipif: _tkinter is None
>>> ts = turtle.getscreen()
>>> ts
@@ -1738,7 +1681,6 @@ Special Turtle methods
``None``, the undobuffer is disabled.
.. doctest::
- :skipif: _tkinter is None
>>> turtle.setundobuffer(42)
@@ -1748,7 +1690,6 @@ Special Turtle methods
Return number of entries in the undobuffer.
.. doctest::
- :skipif: _tkinter is None
>>> while undobufferentries():
... undo()
@@ -1771,7 +1712,6 @@ below:
For example:
.. doctest::
- :skipif: _tkinter is None
>>> s = Shape("compound")
>>> poly1 = ((0,0),(10,-5),(0,10),(-10,-5))
@@ -1782,7 +1722,6 @@ below:
3. Now add the Shape to the Screen's shapelist and use it:
.. doctest::
- :skipif: _tkinter is None
>>> register_shape("myshape", s)
>>> shape("myshape")
@@ -1802,7 +1741,6 @@ Most of the examples in this section ref
``screen``.
.. doctest::
- :skipif: _tkinter is None
:hide:
>>> screen = Screen()
@@ -1819,7 +1757,6 @@ Window control
Set or return background color of the TurtleScreen.
.. doctest::
- :skipif: _tkinter is None
>>> screen.bgcolor("orange")
>>> screen.bgcolor()
@@ -1911,7 +1848,6 @@ Window control
distorted.
.. doctest::
- :skipif: _tkinter is None
>>> screen.reset()
>>> screen.setworldcoordinates(-50,-7.5,50,7.5)
@@ -1922,7 +1858,6 @@ Window control
... left(45); fd(2) # a regular octagon
.. doctest::
- :skipif: _tkinter is None
:hide:
>>> screen.reset()
@@ -1944,7 +1879,6 @@ Animation control
Optional argument:
.. doctest::
- :skipif: _tkinter is None
>>> screen.delay()
10
@@ -1966,7 +1900,6 @@ Animation control
:func:`delay`).
.. doctest::
- :skipif: _tkinter is None
>>> screen.tracer(8, 25)
>>> dist = 2
@@ -2003,7 +1936,6 @@ Using screen events
must have the focus. (See method :func:`listen`.)
.. doctest::
- :skipif: _tkinter is None
>>> def f():
... fd(50)
@@ -2024,7 +1956,6 @@ Using screen events
must have focus. (See method :func:`listen`.)
.. doctest::
- :skipif: _tkinter is None
>>> def f():
... fd(50)
@@ -2049,7 +1980,6 @@ Using screen events
named ``turtle``:
.. doctest::
- :skipif: _tkinter is None
>>> screen.onclick(turtle.goto) # Subsequently clicking into the TurtleScreen will
>>> # make the turtle move to the clicked point.
@@ -2069,7 +1999,6 @@ Using screen events
Install a timer that calls *fun* after *t* milliseconds.
.. doctest::
- :skipif: _tkinter is None
>>> running = True
>>> def f():
@@ -2151,7 +2080,6 @@ Settings and special methods
============ ========================= ===================
.. doctest::
- :skipif: _tkinter is None
>>> mode("logo") # resets turtle heading to north
>>> mode()
@@ -2166,7 +2094,6 @@ Settings and special methods
values of color triples have to be in the range 0..*cmode*.
.. doctest::
- :skipif: _tkinter is None
>>> screen.colormode(1)
>>> turtle.pencolor(240, 160, 80)
@@ -2187,7 +2114,6 @@ Settings and special methods
do with a Tkinter Canvas.
.. doctest::
- :skipif: _tkinter is None
>>> cv = screen.getcanvas()
>>> cv
@@ -2199,7 +2125,6 @@ Settings and special methods
Return a list of names of all currently available turtle shapes.
.. doctest::
- :skipif: _tkinter is None
>>> screen.getshapes()
['arrow', 'blank', 'circle', ..., 'turtle']
@@ -2223,7 +2148,6 @@ Settings and special methods
coordinates: Install the corresponding polygon shape.
.. doctest::
- :skipif: _tkinter is None
>>> screen.register_shape("triangle", ((5,-3), (0,5), (-5,-3)))
@@ -2239,7 +2163,6 @@ Settings and special methods
Return the list of turtles on the screen.
.. doctest::
- :skipif: _tkinter is None
>>> for turtle in screen.turtles():
... turtle.color("red")
@@ -2301,7 +2224,6 @@ Methods specific to Screen, not inherite
center window vertically
.. doctest::
- :skipif: _tkinter is None
>>> screen.setup (width=200, height=200, startx=0, starty=0)
>>> # sets window to 200x200 pixels, in upper left of screen
@@ -2317,7 +2239,6 @@ Methods specific to Screen, not inherite
Set title of turtle window to *titlestring*.
.. doctest::
- :skipif: _tkinter is None
>>> screen.title("Welcome to the turtle zoo!")
@@ -2388,7 +2309,6 @@ Public classes
Example:
.. doctest::
- :skipif: _tkinter is None
>>> poly = ((0,0),(10,-5),(0,10),(-10,-5))
>>> s = Shape("compound")
@@ -2774,7 +2694,6 @@ Changes since Python 3.0
.. doctest::
- :skipif: _tkinter is None
:hide:
>>> for turtle in turtles():

78
pre_checkin.sh Normal file
View File

@ -0,0 +1,78 @@
#!/bin/bash
export LC_ALL=C
master=python*.spec
# create import_failed.map from package definitions
pkgname=$(grep python_pkg_name $master |grep define |awk -F' ' '{print $3}')
MAPFILE=import_failed.map
function new_map_line () {
package=$1
package=$(echo $1 |sed -e "s:%{python_pkg_name}:$pkgname:")
modules=$2
if [ -z "$package" -o -z "$modules" ]; then
return
fi
if [[ "$package" =~ "-base" ]]; then
return
fi
echo "$package:$modules" >> $MAPFILE.tmp
}
for spec in *.spec; do
basename=${spec%.spec}
package=
modules=
while read line; do
case $line in
"%files -n "*)
new_map_line $package "$modules"
package=${line#"%files -n "}
modules=
;;
"%files "*)
new_map_line $package "$modules"
package=$basename-${line#"%files "}
modules=
;;
"%files")
new_map_line $package "$modules"
package=$basename
modules=
;;
"%{sitedir}/config-"*)
# ignore
;;
"%{sitedir}/"*)
word=${line#"%{sitedir}/"}
if ! echo $word | grep -q /; then
modules="$modules $word"
fi
;;
"%{dynlib "*"}")
word=${line#"%{dynlib "}
word=${word%"}"}
modules="$modules $word"
;;
esac
done < $spec
new_map_line $package "$modules"
done
cat $MAPFILE.tmp |sort -u > $MAPFILE
rm $MAPFILE.tmp
# run test inclusion check
tar xJf Python-*.xz
python3 skipped_tests.py
# generate baselibs.conf
VERSION=$(grep ^Version $master|awk -F':' '{print $2}' |sed -e 's/ //g')
python_version=${VERSION:0:3} # 3.3
python_version_abitag=${python_version//./} # 33
python_version_soname=${python_version//./_} # 3_3
echo "$pkgname-base" > baselibs.conf
echo "$pkgname" >> baselibs.conf
echo "libpython$python_version_soname-1_0" >> baselibs.conf

View File

@ -0,0 +1,27 @@
---
Makefile.pre.in | 7 +++++++
1 file changed, 7 insertions(+)
Index: Python-3.12.4/Makefile.pre.in
===================================================================
--- 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) \
+ -DDATE="\"`date -u -r Makefile.pre.in +"%b %d %Y"`\"" \
+ -DTIME="\"`date -u -r Makefile.pre.in +"%T"`\"" \
-DGITVERSION="\"`LC_ALL=C $(GITVERSION)`\"" \
-DGITTAG="\"`LC_ALL=C $(GITTAG)`\"" \
-DGITBRANCH="\"`LC_ALL=C $(GITBRANCH)`\"" \
-o $@ $(srcdir)/Modules/getbuildinfo.c
+Python/getcompiler.o: $(srcdir)/Python/getcompiler.c Makefile
+ $(CC) -c $(PY_CORE_CFLAGS) \
+ -DCOMPILER='"[GCC]"' \
+ -o $@ $(srcdir)/Python/getcompiler.c
+
Modules/getpath.o: $(srcdir)/Modules/getpath.c Python/frozen_modules/getpath.h Makefile $(PYTHON_HEADERS)
$(CC) -c $(PY_CORE_CFLAGS) -DPYTHONPATH='"$(PYTHONPATH)"' \
-DPREFIX='"$(prefix)"' \

View File

@ -0,0 +1,13 @@
Index: Python-3.12.2/Lib/site.py
===================================================================
--- Python-3.12.2.orig/Lib/site.py
+++ Python-3.12.2/Lib/site.py
@@ -77,7 +77,7 @@ import io
import stat
# Prefixes for site-packages; add additional prefixes like /usr/local here
-PREFIXES = [sys.prefix, sys.exec_prefix]
+PREFIXES = [sys.prefix, sys.exec_prefix, '/usr/local']
# Enable per user site-packages directory
# set it to False to disable the feature or True to force the feature
ENABLE_USER_SITE = None

View File

@ -0,0 +1,17 @@
---
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)

78
python.keyring Normal file
View File

@ -0,0 +1,78 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
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
=J6BY
-----END PGP PUBLIC KEY BLOCK-----

3
python312-rpmlintrc Normal file
View File

@ -0,0 +1,3 @@
addFilter("pem-certificate.*/usr/lib.*/python.*/test/*.pem")
addFilter("devel-file-in-non-devel-package.*/usr/lib.*/python.*/tests/*.c")
addFilter("devel-file-in-non-devel-package.*/usr/lib.*/python.*/test/*.cpp")

6171
python312.changes Normal file

File diff suppressed because it is too large Load Diff

1051
python312.spec Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,16 @@
---
Lib/test/test_capi/test_mem.py | 1 +
1 file changed, 1 insertion(+)
Index: Python-3.12.2/Lib/test/test_capi/test_mem.py
===================================================================
--- Python-3.12.2.orig/Lib/test/test_capi/test_mem.py
+++ Python-3.12.2/Lib/test/test_capi/test_mem.py
@@ -110,6 +110,7 @@ class PyMemDebugTests(unittest.TestCase)
def test_pyobject_forbidden_bytes_is_freed(self):
self.check_pyobject_is_freed('check_pyobject_forbidden_bytes_is_freed')
+ @unittest.skip('Failing on Leap 15.*')
def test_pyobject_freed_is_freed(self):
self.check_pyobject_is_freed('check_pyobject_freed_is_freed')

69
skipped_tests.py Normal file
View File

@ -0,0 +1,69 @@
#!/usr/bin/python3
"""
Simple regexp-based skipped test checker.
It lists tests that are mentioned (presumably for exclusion)
in BASE, and in MAIN (presumably for inclusion)
and reports discrepancies.
This will have a number of
"""
MAIN = "python39.spec"
import glob
import re
from os.path import basename
alltests = set()
qemu_exclusions = set()
for item in glob.glob("Python-*/Lib/test/test_*"):
testname = basename(item)
if testname.endswith(".py"):
testname = testname[:-3]
alltests.add(testname)
testre = re.compile(r'[\s"](test_\w+)\b')
def find_tests_in_spec(specname):
global qemu_exclusions
found_tests = set()
with open(specname) as spec:
in_qemu = False
for line in spec:
line = line.strip()
if "#" in line:
line = line[:line.index("#")]
tests = set(testre.findall(line))
found_tests |= tests
if line == "%if 0%{?qemu_user_space_build} > 0":
in_qemu = True
if in_qemu:
if line == "%endif":
in_qemu = False
qemu_exclusions |= tests
return found_tests
excluded = find_tests_in_spec(MAIN)
#print("--- excluded tests:", " ".join(sorted(excluded)))
#print("--- included tests:", " ".join(sorted(included)))
mentioned = excluded
nonexistent = mentioned - alltests
missing = excluded - qemu_exclusions
print("--- the following tests are excluded for QEMU and not tested in python")
print("--- (that probably means we don't need to worry about them)")
for test in sorted(qemu_exclusions - excluded):
print(test)
print("--- the following tests might be excluded in python:")
for test in sorted(missing):
print(test)
if nonexistent:
print("--- the following tests don't exist:")
for test in sorted(nonexistent):
print(test)

View File

@ -0,0 +1,18 @@
---
Lib/test/test_subprocess.py | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
Index: Python-3.12.4/Lib/test/test_subprocess.py
===================================================================
--- Python-3.12.4.orig/Lib/test/test_subprocess.py
+++ Python-3.12.4/Lib/test/test_subprocess.py
@@ -280,7 +280,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')