- Update to 3.12.12:

- Tools/Demos
    - gh-139330: SBOM generation tool didn’t cross-check the version
      and checksum values against the Modules/expat/refresh.sh script,
      leading to the values becoming out-of-date during routine
      updates.
  - Security
    - gh-139700: Check consistency of the zip64 end of central
      directory record. Support records with “zip64 extensible data”
      if there are no bytes prepended to the ZIP file.
    - gh-139400: xml.parsers.expat: Make sure that parent Expat
      parsers are only garbage-collected once they are no longer
      referenced by subparsers created by
      ExternalEntityParserCreate(). Patch by Sebastian Pipping.
    - gh-135661: Fix parsing start and end tags in
      html.parser.HTMLParser according to the HTML5 standard.
      * Whitespaces no longer accepted between </ and the tag name.
        E.g. </ script> does not end the script section.
      * Vertical tabulation (\v) and non-ASCII whitespaces no longer
        recognized as whitespaces. The only whitespaces are \t\n\r\f
        and space.
      * Null character (U+0000) no longer ends the tag name.
      * Attributes and slashes after the tag name in end tags are now
        ignored, instead of terminating after the first > in quoted
        attribute value. E.g. </script/foo=">"/>.
      * Multiple slashes and whitespaces between the last attribute
        and closing > are now ignored in both start and end tags. E.g.
        <a foo=bar/ //>.
      * Multiple = between attribute name and value are no longer
        collapsed. E.g. <a foo==bar> produces attribute “foo” with

OBS-URL: https://build.opensuse.org/package/show/devel:languages:python:Factory/python312?expand=0&rev=162
This commit is contained in:
2025-10-16 16:28:40 +00:00
committed by Git OBS Bridge
commit 58bbae4bf2
40 changed files with 11356 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,237 @@
From 1d53c3e7343bddb064182e02c21b13be9b63390f Mon Sep 17 00:00:00 2001
From: Serhiy Storchaka <storchaka@gmail.com>
Date: Fri, 13 Jun 2025 19:57:48 +0300
Subject: [PATCH] [3.12] gh-135462: Fix quadratic complexity in processing
special input in HTMLParser (GH-135464)
End-of-file errors are now handled according to the HTML5 specs --
comments and declarations are automatically closed, tags are ignored.
(cherry picked from commit 6eb6c5dbfb528bd07d77b60fd71fd05d81d45c41)
Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
---
Lib/html/parser.py | 41 +++-
Lib/test/test_htmlparser.py | 94 ++++++++--
Misc/NEWS.d/next/Security/2025-06-13-15-55-22.gh-issue-135462.KBeJpc.rst | 4
3 files changed, 116 insertions(+), 23 deletions(-)
create mode 100644 Misc/NEWS.d/next/Security/2025-06-13-15-55-22.gh-issue-135462.KBeJpc.rst
Index: Python-3.12.11/Lib/html/parser.py
===================================================================
--- Python-3.12.11.orig/Lib/html/parser.py 2025-07-02 17:09:00.904899297 +0200
+++ Python-3.12.11/Lib/html/parser.py 2025-07-02 17:09:12.496469955 +0200
@@ -25,6 +25,7 @@
charref = re.compile('&#(?:[0-9]+|[xX][0-9a-fA-F]+)[^0-9a-fA-F]')
starttagopen = re.compile('<[a-zA-Z]')
+endtagopen = re.compile('</[a-zA-Z]')
piclose = re.compile('>')
commentclose = re.compile(r'--\s*>')
# Note:
@@ -177,7 +178,7 @@
k = self.parse_pi(i)
elif startswith("<!", i):
k = self.parse_html_declaration(i)
- elif (i + 1) < n:
+ elif (i + 1) < n or end:
self.handle_data("<")
k = i + 1
else:
@@ -185,17 +186,35 @@
if k < 0:
if not end:
break
- k = rawdata.find('>', i + 1)
- if k < 0:
- k = rawdata.find('<', i + 1)
- if k < 0:
- k = i + 1
+ if starttagopen.match(rawdata, i): # < + letter
+ pass
+ elif startswith("</", i):
+ if i + 2 == n:
+ self.handle_data("</")
+ elif endtagopen.match(rawdata, i): # </ + letter
+ pass
+ else:
+ # bogus comment
+ self.handle_comment(rawdata[i+2:])
+ elif startswith("<!--", i):
+ j = n
+ for suffix in ("--!", "--", "-"):
+ if rawdata.endswith(suffix, i+4):
+ j -= len(suffix)
+ break
+ self.handle_comment(rawdata[i+4:j])
+ elif startswith("<![CDATA[", i):
+ self.unknown_decl(rawdata[i+3:])
+ elif rawdata[i:i+9].lower() == '<!doctype':
+ self.handle_decl(rawdata[i+2:])
+ elif startswith("<!", i):
+ # bogus comment
+ self.handle_comment(rawdata[i+2:])
+ elif startswith("<?", i):
+ self.handle_pi(rawdata[i+2:])
else:
- k += 1
- if self.convert_charrefs and not self.cdata_elem:
- self.handle_data(unescape(rawdata[i:k]))
- else:
- self.handle_data(rawdata[i:k])
+ raise AssertionError("we should not get here!")
+ k = n
i = self.updatepos(i, k)
elif startswith("&#", i):
match = charref.match(rawdata, i)
Index: Python-3.12.11/Lib/test/test_htmlparser.py
===================================================================
--- Python-3.12.11.orig/Lib/test/test_htmlparser.py 2025-07-02 17:09:02.385825386 +0200
+++ Python-3.12.11/Lib/test/test_htmlparser.py 2025-07-02 17:09:12.496693449 +0200
@@ -5,6 +5,7 @@
import unittest
from unittest.mock import patch
+from test import support
class EventCollector(html.parser.HTMLParser):
@@ -393,28 +394,34 @@
('data', '<'),
('starttag', 'bc<', [('a', None)]),
('endtag', 'html'),
- ('data', '\n<img src="URL>'),
- ('comment', '/img'),
- ('endtag', 'html<')])
+ ('data', '\n')])
def test_starttag_junk_chars(self):
+ self._run_check("<", [('data', '<')])
+ self._run_check("<>", [('data', '<>')])
+ self._run_check("< >", [('data', '< >')])
+ self._run_check("< ", [('data', '< ')])
self._run_check("</>", [])
+ self._run_check("<$>", [('data', '<$>')])
self._run_check("</$>", [('comment', '$')])
self._run_check("</", [('data', '</')])
- self._run_check("</a", [('data', '</a')])
+ self._run_check("</a", [])
+ self._run_check("</ a>", [('endtag', 'a')])
+ self._run_check("</ a", [('comment', ' a')])
self._run_check("<a<a>", [('starttag', 'a<a', [])])
self._run_check("</a<a>", [('endtag', 'a<a')])
- self._run_check("<!", [('data', '<!')])
- self._run_check("<a", [('data', '<a')])
- self._run_check("<a foo='bar'", [('data', "<a foo='bar'")])
- self._run_check("<a foo='bar", [('data', "<a foo='bar")])
- self._run_check("<a foo='>'", [('data', "<a foo='>'")])
- self._run_check("<a foo='>", [('data', "<a foo='>")])
+ self._run_check("<!", [('comment', '')])
+ self._run_check("<a", [])
+ self._run_check("<a foo='bar'", [])
+ self._run_check("<a foo='bar", [])
+ self._run_check("<a foo='>'", [])
+ self._run_check("<a foo='>", [])
self._run_check("<a$>", [('starttag', 'a$', [])])
self._run_check("<a$b>", [('starttag', 'a$b', [])])
self._run_check("<a$b/>", [('startendtag', 'a$b', [])])
self._run_check("<a$b >", [('starttag', 'a$b', [])])
self._run_check("<a$b />", [('startendtag', 'a$b', [])])
+ self._run_check("</a$b>", [('endtag', 'a$b')])
def test_slashes_in_starttag(self):
self._run_check('<a foo="var"/>', [('startendtag', 'a', [('foo', 'var')])])
@@ -539,13 +546,56 @@
for html, expected in data:
self._run_check(html, expected)
- def test_broken_comments(self):
- html = ('<! not really a comment >'
+ def test_eof_in_comments(self):
+ data = [
+ ('<!--', [('comment', '')]),
+ ('<!---', [('comment', '')]),
+ ('<!----', [('comment', '')]),
+ ('<!-----', [('comment', '-')]),
+ ('<!------', [('comment', '--')]),
+ ('<!----!', [('comment', '')]),
+ ('<!---!', [('comment', '-!')]),
+ ('<!---!>', [('comment', '-!>')]),
+ ('<!--foo', [('comment', 'foo')]),
+ ('<!--foo-', [('comment', 'foo')]),
+ ('<!--foo--', [('comment', 'foo')]),
+ ('<!--foo--!', [('comment', 'foo')]),
+ ('<!--<!--', [('comment', '<!')]),
+ ('<!--<!--!', [('comment', '<!')]),
+ ]
+ for html, expected in data:
+ self._run_check(html, expected)
+
+ def test_eof_in_declarations(self):
+ data = [
+ ('<!', [('comment', '')]),
+ ('<!-', [('comment', '-')]),
+ ('<![', [('comment', '[')]),
+ ('<![CDATA[', [('unknown decl', 'CDATA[')]),
+ ('<![CDATA[x', [('unknown decl', 'CDATA[x')]),
+ ('<![CDATA[x]', [('unknown decl', 'CDATA[x]')]),
+ ('<![CDATA[x]]', [('unknown decl', 'CDATA[x]]')]),
+ ('<!DOCTYPE', [('decl', 'DOCTYPE')]),
+ ('<!DOCTYPE ', [('decl', 'DOCTYPE ')]),
+ ('<!DOCTYPE html', [('decl', 'DOCTYPE html')]),
+ ('<!DOCTYPE html ', [('decl', 'DOCTYPE html ')]),
+ ('<!DOCTYPE html PUBLIC', [('decl', 'DOCTYPE html PUBLIC')]),
+ ('<!DOCTYPE html PUBLIC "foo', [('decl', 'DOCTYPE html PUBLIC "foo')]),
+ ('<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "foo',
+ [('decl', 'DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "foo')]),
+ ]
+ for html, expected in data:
+ self._run_check(html, expected)
+
+ def test_bogus_comments(self):
+ html = ('<!ELEMENT br EMPTY>'
+ '<! not really a comment >'
'<! not a comment either -->'
'<! -- close enough -->'
'<!><!<-- this was an empty comment>'
'<!!! another bogus comment !!!>')
expected = [
+ ('comment', 'ELEMENT br EMPTY'),
('comment', ' not really a comment '),
('comment', ' not a comment either --'),
('comment', ' -- close enough --'),
@@ -600,6 +650,26 @@
('endtag', 'a'), ('data', ' bar & baz')]
)
+ @support.requires_resource('cpu')
+ def test_eof_no_quadratic_complexity(self):
+ # Each of these examples used to take about an hour.
+ # Now they take a fraction of a second.
+ def check(source):
+ parser = html.parser.HTMLParser()
+ parser.feed(source)
+ parser.close()
+ n = 120_000
+ check("<a " * n)
+ check("<a a=" * n)
+ check("</a " * 14 * n)
+ check("</a a=" * 11 * n)
+ check("<!--" * 4 * n)
+ check("<!" * 60 * n)
+ check("<?" * 19 * n)
+ check("</$" * 15 * n)
+ check("<![CDATA[" * 9 * n)
+ check("<!doctype" * 35 * n)
+
class AttributesTestCase(TestCaseBase):
Index: Python-3.12.11/Misc/NEWS.d/next/Security/2025-06-13-15-55-22.gh-issue-135462.KBeJpc.rst
===================================================================
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ Python-3.12.11/Misc/NEWS.d/next/Security/2025-06-13-15-55-22.gh-issue-135462.KBeJpc.rst 2025-07-02 17:09:12.496947674 +0200
@@ -0,0 +1,4 @@
+Fix quadratic complexity in processing specially crafted input in
+:class:`html.parser.HTMLParser`. End-of-file errors are now handled according
+to the HTML5 specs -- comments and declarations are automatically closed,
+tags are ignored.

View File

@@ -0,0 +1,212 @@
From 8f381056d7364b6771b3dce3ebe54dd9f675811b Mon Sep 17 00:00:00 2001
From: Alexander Urieles <aeurielesn@users.noreply.github.com>
Date: Mon, 28 Jul 2025 17:37:26 +0200
Subject: [PATCH] gh-130577: tarfile now validates archives to ensure member
offsets are non-negative (GH-137027) (cherry picked from commit
7040aa54f14676938970e10c5f74ea93cd56aa38)
Co-authored-by: Alexander Urieles <aeurielesn@users.noreply.github.com>
Co-authored-by: Gregory P. Smith <greg@krypto.org>
---
Lib/tarfile.py | 3
Lib/test/test_tarfile.py | 156 ++++++++++
Misc/NEWS.d/next/Library/2025-07-23-00-35-29.gh-issue-130577.c7EITy.rst | 3
3 files changed, 162 insertions(+)
create mode 100644 Misc/NEWS.d/next/Library/2025-07-23-00-35-29.gh-issue-130577.c7EITy.rst
Index: Python-3.12.11/Lib/tarfile.py
===================================================================
--- Python-3.12.11.orig/Lib/tarfile.py 2025-08-01 22:20:38.061933888 +0200
+++ Python-3.12.11/Lib/tarfile.py 2025-08-01 22:20:42.185990406 +0200
@@ -1614,6 +1614,9 @@
"""Round up a byte count by BLOCKSIZE and return it,
e.g. _block(834) => 1024.
"""
+ # Only non-negative offsets are allowed
+ if count < 0:
+ raise InvalidHeaderError("invalid offset")
blocks, remainder = divmod(count, BLOCKSIZE)
if remainder:
blocks += 1
Index: Python-3.12.11/Lib/test/test_tarfile.py
===================================================================
--- Python-3.12.11.orig/Lib/test/test_tarfile.py 2025-08-01 22:20:39.792514772 +0200
+++ Python-3.12.11/Lib/test/test_tarfile.py 2025-08-01 22:20:42.187347433 +0200
@@ -50,6 +50,7 @@
xzname = os.path.join(TEMPDIR, "testtar.tar.xz")
tmpname = os.path.join(TEMPDIR, "tmp.tar")
dotlessname = os.path.join(TEMPDIR, "testtar")
+SPACE = b" "
sha256_regtype = (
"e09e4bc8b3c9d9177e77256353b36c159f5f040531bbd4b024a8f9b9196c71ce"
@@ -4488,6 +4489,161 @@
ar.extractall(self.testdir, filter='fully_trusted')
+class OffsetValidationTests(unittest.TestCase):
+ tarname = tmpname
+ invalid_posix_header = (
+ # name: 100 bytes
+ tarfile.NUL * tarfile.LENGTH_NAME
+ # mode, space, null terminator: 8 bytes
+ + b"000755" + SPACE + tarfile.NUL
+ # uid, space, null terminator: 8 bytes
+ + b"000001" + SPACE + tarfile.NUL
+ # gid, space, null terminator: 8 bytes
+ + b"000001" + SPACE + tarfile.NUL
+ # size, space: 12 bytes
+ + b"\xff" * 11 + SPACE
+ # mtime, space: 12 bytes
+ + tarfile.NUL * 11 + SPACE
+ # chksum: 8 bytes
+ + b"0011407" + tarfile.NUL
+ # type: 1 byte
+ + tarfile.REGTYPE
+ # linkname: 100 bytes
+ + tarfile.NUL * tarfile.LENGTH_LINK
+ # magic: 6 bytes, version: 2 bytes
+ + tarfile.POSIX_MAGIC
+ # uname: 32 bytes
+ + tarfile.NUL * 32
+ # gname: 32 bytes
+ + tarfile.NUL * 32
+ # devmajor, space, null terminator: 8 bytes
+ + tarfile.NUL * 6 + SPACE + tarfile.NUL
+ # devminor, space, null terminator: 8 bytes
+ + tarfile.NUL * 6 + SPACE + tarfile.NUL
+ # prefix: 155 bytes
+ + tarfile.NUL * tarfile.LENGTH_PREFIX
+ # padding: 12 bytes
+ + tarfile.NUL * 12
+ )
+ invalid_gnu_header = (
+ # name: 100 bytes
+ tarfile.NUL * tarfile.LENGTH_NAME
+ # mode, null terminator: 8 bytes
+ + b"0000755" + tarfile.NUL
+ # uid, null terminator: 8 bytes
+ + b"0000001" + tarfile.NUL
+ # gid, space, null terminator: 8 bytes
+ + b"0000001" + tarfile.NUL
+ # size, space: 12 bytes
+ + b"\xff" * 11 + SPACE
+ # mtime, space: 12 bytes
+ + tarfile.NUL * 11 + SPACE
+ # chksum: 8 bytes
+ + b"0011327" + tarfile.NUL
+ # type: 1 byte
+ + tarfile.REGTYPE
+ # linkname: 100 bytes
+ + tarfile.NUL * tarfile.LENGTH_LINK
+ # magic: 8 bytes
+ + tarfile.GNU_MAGIC
+ # uname: 32 bytes
+ + tarfile.NUL * 32
+ # gname: 32 bytes
+ + tarfile.NUL * 32
+ # devmajor, null terminator: 8 bytes
+ + tarfile.NUL * 8
+ # devminor, null terminator: 8 bytes
+ + tarfile.NUL * 8
+ # padding: 167 bytes
+ + tarfile.NUL * 167
+ )
+ invalid_v7_header = (
+ # name: 100 bytes
+ tarfile.NUL * tarfile.LENGTH_NAME
+ # mode, space, null terminator: 8 bytes
+ + b"000755" + SPACE + tarfile.NUL
+ # uid, space, null terminator: 8 bytes
+ + b"000001" + SPACE + tarfile.NUL
+ # gid, space, null terminator: 8 bytes
+ + b"000001" + SPACE + tarfile.NUL
+ # size, space: 12 bytes
+ + b"\xff" * 11 + SPACE
+ # mtime, space: 12 bytes
+ + tarfile.NUL * 11 + SPACE
+ # chksum: 8 bytes
+ + b"0010070" + tarfile.NUL
+ # type: 1 byte
+ + tarfile.REGTYPE
+ # linkname: 100 bytes
+ + tarfile.NUL * tarfile.LENGTH_LINK
+ # padding: 255 bytes
+ + tarfile.NUL * 255
+ )
+ valid_gnu_header = tarfile.TarInfo("filename").tobuf(tarfile.GNU_FORMAT)
+ data_block = b"\xff" * tarfile.BLOCKSIZE
+
+ def _write_buffer(self, buffer):
+ with open(self.tarname, "wb") as f:
+ f.write(buffer)
+
+ def _get_members(self, ignore_zeros=None):
+ with open(self.tarname, "rb") as f:
+ with tarfile.open(
+ mode="r", fileobj=f, ignore_zeros=ignore_zeros
+ ) as tar:
+ return tar.getmembers()
+
+ def _assert_raises_read_error_exception(self):
+ with self.assertRaisesRegex(
+ tarfile.ReadError, "file could not be opened successfully"
+ ):
+ self._get_members()
+
+ def test_invalid_offset_header_validations(self):
+ for tar_format, invalid_header in (
+ ("posix", self.invalid_posix_header),
+ ("gnu", self.invalid_gnu_header),
+ ("v7", self.invalid_v7_header),
+ ):
+ with self.subTest(format=tar_format):
+ self._write_buffer(invalid_header)
+ self._assert_raises_read_error_exception()
+
+ def test_early_stop_at_invalid_offset_header(self):
+ buffer = self.valid_gnu_header + self.invalid_gnu_header + self.valid_gnu_header
+ self._write_buffer(buffer)
+ members = self._get_members()
+ self.assertEqual(len(members), 1)
+ self.assertEqual(members[0].name, "filename")
+ self.assertEqual(members[0].offset, 0)
+
+ def test_ignore_invalid_archive(self):
+ # 3 invalid headers with their respective data
+ buffer = (self.invalid_gnu_header + self.data_block) * 3
+ self._write_buffer(buffer)
+ members = self._get_members(ignore_zeros=True)
+ self.assertEqual(len(members), 0)
+
+ def test_ignore_invalid_offset_headers(self):
+ for first_block, second_block, expected_offset in (
+ (
+ (self.valid_gnu_header),
+ (self.invalid_gnu_header + self.data_block),
+ 0,
+ ),
+ (
+ (self.invalid_gnu_header + self.data_block),
+ (self.valid_gnu_header),
+ 1024,
+ ),
+ ):
+ self._write_buffer(first_block + second_block)
+ members = self._get_members(ignore_zeros=True)
+ self.assertEqual(len(members), 1)
+ self.assertEqual(members[0].name, "filename")
+ self.assertEqual(members[0].offset, expected_offset)
+
+
def setUpModule():
os_helper.unlink(TEMPDIR)
os.makedirs(TEMPDIR)
Index: Python-3.12.11/Misc/NEWS.d/next/Library/2025-07-23-00-35-29.gh-issue-130577.c7EITy.rst
===================================================================
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ Python-3.12.11/Misc/NEWS.d/next/Library/2025-07-23-00-35-29.gh-issue-130577.c7EITy.rst 2025-08-01 22:20:42.187819145 +0200
@@ -0,0 +1,3 @@
+:mod:`tarfile` now validates archives to ensure member offsets are
+non-negative. (Contributed by Alexander Enrique Urieles Nieto in
+:gh:`130577`.)

View 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

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.

BIN
Python-3.12.11.tar.xz (Stored with Git LFS) Normal file

Binary file not shown.

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

@@ -0,0 +1,18 @@
-----BEGIN PGP SIGNATURE-----
iQKTBAABCgB9FiEEcWlgX2LHUTVtBUomqCHmgOX6YwUFAmg/MbpfFIAAAAAALgAo
aXNzdWVyLWZwckBub3RhdGlvbnMub3BlbnBncC5maWZ0aGhvcnNlbWFuLm5ldDcx
Njk2MDVGNjJDNzUxMzU2RDA1NEEyNkE4MjFFNjgwRTVGQTYzMDUACgkQqCHmgOX6
YwXySQ/7Ba9qlnTLmxqTCO8C7Gf545WNMBL2Ep6JZPgjOcgNk9e1QdAnNV5OOtGm
gW5nNPSTNNcIcPn058GuI24D4RpTQCJfMbMLsfYgvio0E7ij1gC19PsJHb6ejtCS
H2kK237Y1kuqRUdbTZssFDoAR4R9+UCaDuo4XdW+UKQk2GgdNQDMWLKmWF/Xk6Ob
/LihMXj27mDU9nXVdWR55sJzTFzfGB015vmORvcpuctkf1lZ4AfVFMgGw1CgjRjF
kjrOkrDErjDUQ8BIhMh90deiTpigfg7cg1HBDI6GRzklFg6cMfIdfvmfM0MfamX3
Tow08TGBzmYXWgrqjYXW6JknKhBGOrjXMB7/yNDk9bJVLcOJaLbOmbcG0WRQF/Py
DMOCvr09l0yt5KFYpdKrDvyCuKYfpX33B4C60kU9JzmfXGyQ6LDTPXapZooJ+8Fg
GRTUsc0YWXoaDVCcxMIdiG+jEMQkjWVwW7E/nC/d7WT5L9KPoYFA1sZ834kKq3jr
NmZynbBnKH7m7L+u6HP6B+pa84FKEME69osAXZk0HJOIHB+SOX3E6BXRo6IV8Q/K
J6f5Ja26gJ7KXcUxTgkTkYh7tz0bhb+WeL3j6N/BC0eK7ZVsKRZ/3WnntGsG5B2m
FjVOYKolfkF4tf63SjdFuudgaKGCaDK1PvfwIr7k0oozxrB2ZEA=
=SYH/
-----END PGP SIGNATURE-----

File diff suppressed because one or more lines are too long

BIN
Python-3.12.12.tar.xz (Stored with Git LFS) Normal file

Binary file not shown.

18
Python-3.12.12.tar.xz.asc Normal file
View 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-----

File diff suppressed because one or more lines are too long

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,175 @@
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 | 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, 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.10/Doc/library/ensurepip.rst
===================================================================
--- 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.
.. option:: --root <dir>
@@ -102,7 +106,7 @@
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)
@@ -112,6 +116,8 @@
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.
@@ -132,6 +138,8 @@
*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.10/Lib/ensurepip/__init__.py
===================================================================
--- 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
-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 @@
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 @@
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 @@
return _bootstrap(
root=args.root,
+ prefix=args.prefix,
upgrade=args.upgrade,
user=args.user,
verbosity=args.verbosity,
Index: Python-3.12.10/Lib/test/test_ensurepip.py
===================================================================
--- 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,
)
+ 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.10/Makefile.pre.in
===================================================================
--- 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 \
- $$ensurepip --root=$(DESTDIR)/ ; \
+ $$ensurepip --root=$(DESTDIR)/ --prefix=$(prefix) ; \
fi
.PHONY: altinstall
@@ -1925,7 +1925,7 @@
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.10/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`.

View 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
View 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 {

View 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]

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,32 @@
---
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
+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
@@ -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
- :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,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):

51
idle3.appdata.xml Normal file
View File

@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8"?>
<component type="desktop-application">
<id>org.python.IDLE3</id>
<launchable type="desktop-id">idle3.desktop</launchable>
<name>IDLE3</name>
<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 macOS.
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>
<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">
<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>
</component>

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

78
python.keyring Normal file
View File

@@ -0,0 +1,78 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
mQINBFUAInYBEACrmKcXagNRlo1VjznrJZMMUh0rxUn2iK2wy9H5qrCo4EgMYahZ
ibBunSWB4RNeVQevzUm3eSyOixnt+BmGZbSYqKp8tJIXRRcnKhEtC62X+7NVMc7B
9uPu/aJ3HNqXrsQwBJUzZxzLMLg6obCyarhhHAYbWmfaafU4yNk3J4dGNKoZtHvz
bjnUtlsUAkCmuyt3MsUuSYz34BviRLSEZEKW6xNoyQmD9dUhQ5exBuTPjtmdTf+x
gOKpBluRkJ4TADGlWf42lIkaI+8DYRj1R8eQdLFwS7sDTu/MMPceKU7nTWOoj8HF
3xXRJ+bJbpOJXZFEzVKjXHKuMFkhKr562i0LD8pdl1+s+9LRovmAvGwggt04Drzb
AK437QoyjPKiTnFlg4tOeIuN0Y+GGk2hXOdH7fNw79B9Tq5ENxth8NsnKVlz1zpF
X+aV0zCvAjNWutAUpikqZT/ibpwmM+NJcz3pgzQOq+LfPFskyrv7zkVODEjH3SG3
s4ROvyoWfLPWmX92kJMOkvzyQObZmU2zWJgJbjYRApZiTfbfnH1tE+wxH4ZR5dji
FpEdUJn1yjpYp21Q10khIdsj6q9IvS3RDq0ygc5wfl5111byEsdP12y36lvPTclT
33VHBR1vxr+js9d8FI4wwt/o+7TmAO39DYhLrtn+ZgyRgIBYY65lhEaUtwARAQAB
tCJUaG9tYXMgV291dGVycyA8dGhvbWFzQHB5dGhvbi5vcmc+iQJUBBMBCgA+AhsD
BQsJCAcDBRUKCQgLBRYCAwEAAh4BAheAFiEEcWlgX2LHUTVtBUomqCHmgOX6YwUF
AmDgZQIFCRVGRAsACgkQqCHmgOX6YwUF8RAAiCyNSQc63wae9rvhK6iR90ybBnql
4Wec0Lbxb1Fu16q6CuZQbC+Uyw/K7cl2SosJ6U0sIR6lIaEgPn5R6CSXk0a3m2bm
zbPHEUqqLkz4l89GZfKZ0pNgZfCN3mt+8Z5O70LmzUQnWRSe/a7r+XrgPzSfNUXR
DL+aRxCChctHXwvYOk1b8Hy1CaNeFijgs4iaoYyt21mhjJDAAjRTFjLpjkIQcEcx
4+ZL4NKdGb4I+u6J9xYam/JDKkG1NtpxlPACY+VyIcUWcofRs8v90YL8aZDikr/R
l68ydTsfr1Jy95TH0EL0XgWo7yVppTKADc9jeCtemymBvhvvWlCRGjvhgvlCZ+IT
yy1mDmZkVzxAWT8JxyuUJ8Hwj4E67X24AyLirMxEpAObE1FAE6F/S6s3w18HGOCY
24PmvDpS+lbR6TOe2AOAJGpVZqvqy+7EpM4OM1KrVDTKfzP5HR+QOyI7aKSYt0sO
URqhrlq1fs9Q6yssFQYHRLYQO/OrhMx5yR6R2o1ndyJ9Wx9WWcL/HalmlocTL9AC
4U0LjBVKYWaPpsLuSgID4vPWG3gul5OqZ2LNTF/VzOm5do6ZeBe/9LOa1GGf9MMx
NMW7CB8igLcoF0XD979q1zADpYZufZgTvTFQzmWXhXIUBpWyXHg05baCFkdtKCqE
/bwf9KDY9ecGDJK0IVRob21hcyBXb3V0ZXJzIDx0aG9tYXNAeHM0YWxsLm5sPokC
VwQTAQoAQQIbAwULCQgHAwUVCgkICwUWAgMBAAIeAQIXgAIZARYhBHFpYF9ix1E1
bQVKJqgh5oDl+mMFBQJg4GUBBQkVRkQLAAoJEKgh5oDl+mMFvwoP/R84ApRtRxMp
idzwUAJ/CDs5hVxoqsu4dpGUog6fEFzHdHmLeL/jW3D1Zm8KqytbBRhoMjtJABYw
qf6GMiEYw7t4JkthDeFKOAlQUDyCe3xU+QGzoBGJtUldZmlFT5RGhV5dCzvqqsLb
tRLv8igzmPM4N4qXGOBi0+SLFb4SJIlZujYK63UX1pcbFjyp0V9C8SkloxeVLIAt
Bd3bCLJrEyf3foVXktfcjjHpS6vJDHmfkayV5wjKVqXCLFn9A8WRMMeDLlWV0fgw
R5LFnZu7UrbCHYeius+7lSwUeoFo7vIwmZkxKtvDz+Z0S2TgpfYpMhXwnFaSlYgR
cDmO5w7z19KRdMHIoI4rTxk6Q9c6oCFNw900kKRw5WB3JvyPOhHcyBVQBueJlhML
9jEduzy15n1glNPUW0/321o0VwOWXoGYSOohdDF2ccURkmHcT1+BXCbxPBHrG8tL
rinBoMlAm7ZXehb2HKam4YQqaJNVnk22gOFu4y0s1xXbPeg9ihINtP3sojvzkdSN
OMSQ5U15v2CUZP9eGo6LfbN8mLQgpPEJMuz/KBvso/0NUMPieLWWIWvCdC0tVbx1
XudU5yJDbhR/s9G7GG9KVTcYT3SFvJPj7lkITr4QASQHlcfrdUMfCAv3GOgCR1bv
3yc4n6b9TgGNCzb31Kh/o30sf4Ip76FvtCRUaG9tYXMgV291dGVycyA8dHdvdXRl
cnNAZ29vZ2xlLmNvbT6JAlQEEwEKAD4CGwMFCwkIBwMFFQoJCAsFFgIDAQACHgEC
F4AWIQRxaWBfYsdRNW0FSiaoIeaA5fpjBQUCYOBlAQUJFUZECwAKCRCoIeaA5fpj
BTvxEACfyEt5rN5QGmVgahD/83l7lQpZUzLSq5MnIfRjCz50seh+oWsOuecayHZ7
9IDVSkF2L2kE1rumcB7UKPez0kHVrTdh3mQIsfCzQZEMsWTDYotlZbrPPvT3lKGL
+O7fU321q9GVotJAssYcQFIK9F2p3jhN2coOzguikVlSc4nswnq2KRIJ4BpSJ3fk
1rWLr8oJxN2pSpskYtHdUyUxfZ+fOrMHLbW94JWsLYDad4wpr8etBneVAaUPfphh
bIwfhRXlHuTreDtwr3LJYKp1VjUjzGVVT2CXkS9LbJ7aM2BYa/1MJyHxkglu8O9L
IDGH2arlbtmBKMbCXPSX/42HsGpUgQYRwG4f+2CfPj4fNx5GK8LO/EJjaw2Qh542
U0356RRVZquN6E6SS6Sndlf9sO4cKU/ptT8IsfWKKaLwvr0l71hgLRqqe3rSpTV5
4cKpJfYIG+Qf4Do69etJLxjYUsyCqzuFocxZa0DGkqDQ+f1cD1bdg7Twso041NZG
6y9+E7kCf3jtKkiYAHBY902qZi8FvtI2tDAqwlfJjdiH5rUtYZALO3KGT+l9p3FT
YIdDD1iVC41CeF6loJk0gQZiNmJtyY1TTyNS5Chtr8fSV9yYuoB5XoYYpLu1NCks
4Cwva1tE45VhFrl8lPaM3EABOV+JeHYHX/DgooJRIwgpXCBmwbkCDQRVACJ2ARAA
4lpbW8WeDqyRFffqQzVUK6456CkM7Fd77n1FdY0KwNeAmULYeiQ1Kp2PDzxFOyoJ
Ne8aQazB7jPqGth0+JgFCOxGlnAtBP7DQl2MrYAL+AcKJ0c5dXc96ObZ6xtd01n9
gAoouppJINaA2aEX8P6nhQGu9qNz8yMBC22w0MYJZ+38ZVeXGcBCS3AGggeROwNP
yNSZnW5TPVHi+Sea5bCE4eo5UYIAMqcToxieI3V4A2ciQV9nBERLF0bAadD1HEeC
b6wMg6h8z6VIRPitk45Dw73dy1yC6OvhkyGQ1yGuOPxwVnG3w0CLSUmMQeqyNAuf
mtN2yeoSMV74K9kOpkxCzzSulXGhEgCXWE7EXKC2g8i6M4BwYm3AaBGqeo+z7Din
ffWs8W2UvQUN6JTAdGVgNUfacYbP8YR7fOO1EczJ/FYGxq+JnDUFRpKNsDouw6Ze
RI1EiQT3FEKWI3meNmTPBmIcWLoYGNYdmaeb4pqHBb6SfV45H4QjTyIjNHiW/Lkp
uI7oNo/vIlNF8OQwyUFtknXIx57A0VSdI+vfz1crneg/bg0qzBz5SoYZ0XZUfvmY
LAoDZ0/KLaqZ1x1Z9wiLbe3iK6nE1mjmWf7rOfmWHuxH/gbChXMDDfOMwgOYFXNX
ImsNPWPX3XA2DrhFrlNWzA8kxi9hXJrgAfkRcx/84oUAEQEAAYkCPAQYAQoAJgIb
DBYhBHFpYF9ix1E1bQVKJqgh5oDl+mMFBQJg4GUPBQkVRkQZAAoJEKgh5oDl+mMF
hIcP/j3tJamzKpJGJAwcsoneFtYfmZnLA4UosffaPlsLGRVL1buyRuj2dFBr2WU4
NAldYrQPK4T+ciSpfogJ9Dk8s1eUMhZi7gxKmeOHUDyefPXIp7v3PSG4xcnfXjyE
K9zC714qFsI9ERjTg7uaw6qmFv8Xht8O8TLGMgqDijQIgrH2oGd6tEdYyOOCOPQ7
d6PBSm5Sw53LlCWlW5I9bc0NCjbnwWjh7Z9UXtLffzZyxgxggSw0vfg5PuhcprZ2
Rd3MwzJmALI2BB7eWW1x+M0hXmtdqj7Opmajh+UMrFjLtAlEZfslJwzV9NkAFxDY
zRi2jvsmJx78vOPB1XhXgTvlEOvA7qEYDXFaZJHlBDmFU9JqytGZ6PtiQENuLHIe
4hO6aHbhJA4I9EqoG1U1COQAwrsHreV6+fpcFn4lXbu+gWPyUzKiQMQd9kI3EEia
yObUro21OFHS7z131kKbMec/oc2RfADCvEwY8oay7o0S9aTqvPSQODs8nYkbZchN
FoC+oF9n8pBMNzhYBsTk1OXleD1yMucsuywr5i0meyvu6oQ4+pdPYD6wh7JatJh0
hayKy33GGsXd278J1Ek1p6MEFnGLc/zH+NZZLIU7Qn1oFU+gK4cVeaLX2g0/BLKc
Q/AEmYIwnecLr8A+Y4mZVwwsnSHtfELtoGSsawN26bzKbnRs
=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")

7314
python312.changes Normal file

File diff suppressed because it is too large Load Diff

1056
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,21 @@
---
Lib/test/test_subprocess.py | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
Index: Python-3.12.10/Lib/test/test_subprocess.py
===================================================================
--- 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