SHA256
1
0
forked from pool/python38

- Remove %suse_update_desktop_file macro as it is not useful any

more.

OBS-URL: https://build.opensuse.org/package/show/devel:languages:python:Factory/python38?expand=0&rev=160
This commit is contained in:
Matej Cepl 2024-07-22 21:23:07 +00:00 committed by Git OBS Bridge
commit 394799feb0
44 changed files with 7839 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,54 @@
From 5775f51691d7d64fb676586e008b41261ce64ac2 Mon Sep 17 00:00:00 2001
From: "Matt.Wang" <mattwang44@gmail.com>
Date: Wed, 19 Oct 2022 14:49:08 +0800
Subject: [PATCH 1/2] fix(doc-tools): use sphinx.locale._ as gettext() for
backward-compatibility in pyspecific.py
[why] spinix 5.3 changed locale.translators from a defaultdict(gettext.NullTranslations) to a dict, which leads to failure of pyspecific.py. Use sphinx.locale._ as gettext to fix the issue.
---
Doc/tools/extensions/pyspecific.py | 8 ++++----
Misc/NEWS.d/next/Documentation/2022-10-19-07-15-52.gh-issue-98366.UskMXF.rst | 1 +
2 files changed, 5 insertions(+), 4 deletions(-)
--- a/Doc/tools/extensions/pyspecific.py
+++ b/Doc/tools/extensions/pyspecific.py
@@ -26,7 +26,7 @@ try:
from sphinx.errors import NoUri
except ImportError:
from sphinx.environment import NoUri
-from sphinx.locale import translators
+from sphinx.locale import _ as sphinx_gettext
from sphinx.util import status_iterator, logging
from sphinx.util.nodes import split_explicit_title
from sphinx.writers.text import TextWriter, TextTranslator
@@ -110,7 +110,7 @@ class ImplementationDetail(Directive):
def run(self):
pnode = nodes.compound(classes=['impl-detail'])
- label = translators['sphinx'].gettext(self.label_text)
+ label = sphinx_gettext(self.label_text)
content = self.content
add_text = nodes.strong(label, label)
if self.arguments:
@@ -179,7 +179,7 @@ class AuditEvent(Directive):
else:
args = []
- label = translators['sphinx'].gettext(self._label[min(2, len(args))])
+ label = sphinx_gettext(self._label[min(2, len(args))])
text = label.format(name="``{}``".format(name),
args=", ".join("``{}``".format(a) for a in args if a))
@@ -357,7 +357,7 @@ class DeprecatedRemoved(Directive):
else:
label = self._removed_label
- label = translators['sphinx'].gettext(label)
+ label = sphinx_gettext(label)
text = label.format(deprecated=self.arguments[0], removed=self.arguments[1])
if len(self.arguments) == 3:
inodes, messages = self.state.inline_text(self.arguments[2],
--- /dev/null
+++ b/Misc/NEWS.d/next/Documentation/2022-10-19-07-15-52.gh-issue-98366.UskMXF.rst
@@ -0,0 +1 @@
+Use sphinx.locale._ as the gettext function in pyspecific.py.

View File

@ -0,0 +1,79 @@
From c0dea0309b9a0a7cbc87727c9957f0a388fb9b0f Mon Sep 17 00:00:00 2001
From: Nikita Sobolev <mail@sobolevn.me>
Date: Fri, 11 Nov 2022 11:04:30 +0300
Subject: [PATCH] gh-98086: Now ``patch.dict`` can decorate async functions
(GH-98095) (cherry picked from commit
67b4d2772c5124b908f8ed9b13166a79bbeb88d2)
Co-authored-by: Nikita Sobolev <mail@sobolevn.me>
---
Lib/unittest/mock.py | 18 ++++++++++
Lib/unittest/test/testmock/testasync.py | 17 +++++++++
Misc/NEWS.d/next/Library/2022-10-08-19-39-27.gh-issue-98086.y---WC.rst | 1
3 files changed, 36 insertions(+)
create mode 100644 Misc/NEWS.d/next/Library/2022-10-08-19-39-27.gh-issue-98086.y---WC.rst
--- a/Lib/unittest/mock.py
+++ b/Lib/unittest/mock.py
@@ -1749,6 +1749,12 @@ class _patch_dict(object):
def __call__(self, f):
if isinstance(f, type):
return self.decorate_class(f)
+ if inspect.iscoroutinefunction(f):
+ return self.decorate_async_callable(f)
+ return self.decorate_callable(f)
+
+
+ def decorate_callable(self, f):
@wraps(f)
def _inner(*args, **kw):
self._patch_dict()
@@ -1757,6 +1763,18 @@ class _patch_dict(object):
finally:
self._unpatch_dict()
+ return _inner
+
+
+ def decorate_async_callable(self, f):
+ @wraps(f)
+ async def _inner(*args, **kw):
+ self._patch_dict()
+ try:
+ return await f(*args, **kw)
+ finally:
+ self._unpatch_dict()
+
return _inner
--- a/Lib/unittest/test/testmock/testasync.py
+++ b/Lib/unittest/test/testmock/testasync.py
@@ -143,6 +143,23 @@ class AsyncPatchCMTest(unittest.TestCase
asyncio.run(test_async())
+ def test_patch_dict_async_def(self):
+ foo = {'a': 'a'}
+ @patch.dict(foo, {'a': 'b'})
+ async def test_async():
+ self.assertEqual(foo['a'], 'b')
+
+ self.assertTrue(inspect.iscoroutinefunction(test_async))
+ asyncio.run(test_async())
+
+ def test_patch_dict_async_def_context(self):
+ foo = {'a': 'a'}
+ async def test_async():
+ with patch.dict(foo, {'a': 'b'}):
+ self.assertEqual(foo['a'], 'b')
+
+ asyncio.run(test_async())
+
class AsyncMockTest(unittest.TestCase):
def test_iscoroutinefunction_default(self):
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2022-10-08-19-39-27.gh-issue-98086.y---WC.rst
@@ -0,0 +1 @@
+Make sure ``patch.dict()`` can be applied on async functions.

View File

@ -0,0 +1,57 @@
From a37f52436f9aa4b9292878b72f3ff1480e2606c3 Mon Sep 17 00:00:00 2001
From: Christian Heimes <christian@python.org>
Date: Tue, 15 Jan 2019 23:47:42 +0100
Subject: [PATCH] bpo-35746: Fix segfault in ssl's cert parser (GH-11569)
Fix a NULL pointer deref in ssl module. The cert parser did not handle CRL
distribution points with empty DP or URI correctly. A malicious or buggy
certificate can result into segfault.
Signed-off-by: Christian Heimes <christian@python.org>
https://bugs.python.org/issue35746
---
Lib/test/test_ssl.py | 21 ++++++++++
Misc/NEWS.d/next/Security/2019-01-15-18-16-05.bpo-35746.nMSd0j.rst | 3 +
2 files changed, 24 insertions(+)
create mode 100644 Lib/test/talos-2019-0758.pem
create mode 100644 Misc/NEWS.d/next/Security/2019-01-15-18-16-05.bpo-35746.nMSd0j.rst
--- a/Lib/test/test_ssl.py
+++ b/Lib/test/test_ssl.py
@@ -507,6 +507,27 @@ class BasicSocketTests(unittest.TestCase
}
)
+ def test_parse_cert_CVE_2019_5010(self):
+ p = ssl._ssl._test_decode_cert(TALOS_INVALID_CRLDP)
+ if support.verbose:
+ sys.stdout.write("\n" + pprint.pformat(p) + "\n")
+ self.assertEqual(
+ p,
+ {
+ 'issuer': (
+ (('countryName', 'UK'),), (('commonName', 'cody-ca'),)),
+ 'notAfter': 'Jun 14 18:00:58 2028 GMT',
+ 'notBefore': 'Jun 18 18:00:58 2018 GMT',
+ 'serialNumber': '02',
+ 'subject': ((('countryName', 'UK'),),
+ (('commonName',
+ 'codenomicon-vm-2.test.lal.cisco.com'),)),
+ 'subjectAltName': (
+ ('DNS', 'codenomicon-vm-2.test.lal.cisco.com'),),
+ 'version': 3
+ }
+ )
+
def test_parse_cert_CVE_2013_4238(self):
p = ssl._ssl._test_decode_cert(NULLBYTECERT)
if support.verbose:
--- /dev/null
+++ b/Misc/NEWS.d/next/Security/2019-01-15-18-16-05.bpo-35746.nMSd0j.rst
@@ -0,0 +1,3 @@
+[CVE-2019-5010] Fix a NULL pointer deref in ssl module. The cert parser did
+not handle CRL distribution points with empty DP or URI correctly. A
+malicious or buggy certificate can result into segfault.

View File

@ -0,0 +1,462 @@
---
Doc/library/email.utils.rst | 19 -
Lib/email/utils.py | 151 +++++++-
Lib/test/test_email/test_email.py | 187 +++++++++-
Misc/NEWS.d/next/Library/2023-10-20-15-28-08.gh-issue-102988.dStNO7.rst | 8
4 files changed, 344 insertions(+), 21 deletions(-)
--- a/Doc/library/email.utils.rst
+++ b/Doc/library/email.utils.rst
@@ -60,13 +60,18 @@ of the new API.
begins with angle brackets, they are stripped off.
-.. function:: parseaddr(address)
+.. function:: parseaddr(address, *, strict=True)
Parse address -- which should be the value of some address-containing field such
as :mailheader:`To` or :mailheader:`Cc` -- into its constituent *realname* and
*email address* parts. Returns a tuple of that information, unless the parse
fails, in which case a 2-tuple of ``('', '')`` is returned.
+ If *strict* is true, use a strict parser which rejects malformed inputs.
+
+ .. versionchanged:: 3.13
+ Add *strict* optional parameter and reject malformed inputs by default.
+
.. function:: formataddr(pair, charset='utf-8')
@@ -84,12 +89,15 @@ of the new API.
Added the *charset* option.
-.. function:: getaddresses(fieldvalues)
+.. function:: getaddresses(fieldvalues, *, strict=True)
This method returns a list of 2-tuples of the form returned by ``parseaddr()``.
*fieldvalues* is a sequence of header field values as might be returned by
- :meth:`Message.get_all <email.message.Message.get_all>`. Here's a simple
- example that gets all the recipients of a message::
+ :meth:`Message.get_all <email.message.Message.get_all>`.
+
+ If *strict* is true, use a strict parser which rejects malformed inputs.
+
+ Here's a simple example that gets all the recipients of a message::
from email.utils import getaddresses
@@ -99,6 +107,9 @@ of the new API.
resent_ccs = msg.get_all('resent-cc', [])
all_recipients = getaddresses(tos + ccs + resent_tos + resent_ccs)
+ .. versionchanged:: 3.13
+ Add *strict* optional parameter and reject malformed inputs by default.
+
.. function:: parsedate(date)
--- a/Lib/email/utils.py
+++ b/Lib/email/utils.py
@@ -48,6 +48,7 @@ TICK = "'"
specialsre = re.compile(r'[][\\()<>@,:;".]')
escapesre = re.compile(r'[\\"]')
+
def _has_surrogates(s):
"""Return True if s contains surrogate-escaped binary data."""
# This check is based on the fact that unless there are surrogates, utf8
@@ -105,13 +106,128 @@ def formataddr(pair, charset='utf-8'):
return '%s%s%s <%s>' % (quotes, name, quotes, address)
return address
+
+def _iter_escaped_chars(addr):
+ pos = 0
+ escape = False
+ for pos, ch in enumerate(addr):
+ if escape:
+ yield (pos, '\\' + ch)
+ escape = False
+ elif ch == '\\':
+ escape = True
+ else:
+ yield (pos, ch)
+ if escape:
+ yield (pos, '\\')
+
+
+def _strip_quoted_realnames(addr):
+ """Strip real names between quotes."""
+ if '"' not in addr:
+ # Fast path
+ return addr
+
+ start = 0
+ open_pos = None
+ result = []
+ for pos, ch in _iter_escaped_chars(addr):
+ if ch == '"':
+ if open_pos is None:
+ open_pos = pos
+ else:
+ if start != open_pos:
+ result.append(addr[start:open_pos])
+ start = pos + 1
+ open_pos = None
+
+ if start < len(addr):
+ result.append(addr[start:])
+
+ return ''.join(result)
+
+
+supports_strict_parsing = True
+def getaddresses(fieldvalues, *, strict=True):
+ """Return a list of (REALNAME, EMAIL) or ('','') for each fieldvalue.
-def getaddresses(fieldvalues):
- """Return a list of (REALNAME, EMAIL) for each fieldvalue."""
- all = COMMASPACE.join(fieldvalues)
- a = _AddressList(all)
- return a.addresslist
+ When parsing fails for a fieldvalue, a 2-tuple of ('', '') is returned in
+ its place.
+
+ If strict is true, use a strict parser which rejects malformed inputs.
+ """
+
+ # If strict is true, if the resulting list of parsed addresses is greater
+ # than the number of fieldvalues in the input list, a parsing error has
+ # occurred and consequently a list containing a single empty 2-tuple [('',
+ # '')] is returned in its place. This is done to avoid invalid output.
+ #
+ # Malformed input: getaddresses(['alice@example.com <bob@example.com>'])
+ # Invalid output: [('', 'alice@example.com'), ('', 'bob@example.com')]
+ # Safe output: [('', '')]
+
+ if not strict:
+ all = COMMASPACE.join(str(v) for v in fieldvalues)
+ a = _AddressList(all)
+ return a.addresslist
+
+ fieldvalues = [str(v) for v in fieldvalues]
+ fieldvalues = _pre_parse_validation(fieldvalues)
+ addr = COMMASPACE.join(fieldvalues)
+ a = _AddressList(addr)
+ result = _post_parse_validation(a.addresslist)
+
+ # Treat output as invalid if the number of addresses is not equal to the
+ # expected number of addresses.
+ n = 0
+ for v in fieldvalues:
+ # When a comma is used in the Real Name part it is not a deliminator.
+ # So strip those out before counting the commas.
+ v = _strip_quoted_realnames(v)
+ # Expected number of addresses: 1 + number of commas
+ n += 1 + v.count(',')
+ if len(result) != n:
+ return [('', '')]
+
+ return result
+
+
+def _check_parenthesis(addr):
+ # Ignore parenthesis in quoted real names.
+ addr = _strip_quoted_realnames(addr)
+
+ opens = 0
+ for pos, ch in _iter_escaped_chars(addr):
+ if ch == '(':
+ opens += 1
+ elif ch == ')':
+ opens -= 1
+ if opens < 0:
+ return False
+ return (opens == 0)
+
+
+def _pre_parse_validation(email_header_fields):
+ accepted_values = []
+ for v in email_header_fields:
+ if not _check_parenthesis(v):
+ v = "('', '')"
+ accepted_values.append(v)
+
+ return accepted_values
+
+
+def _post_parse_validation(parsed_email_header_tuples):
+ accepted_values = []
+ # The parser would have parsed a correctly formatted domain-literal
+ # The existence of an [ after parsing indicates a parsing failure
+ for v in parsed_email_header_tuples:
+ if '[' in v[1]:
+ v = ('', '')
+ accepted_values.append(v)
+
+ return accepted_values
def _format_timetuple_and_zone(timetuple, zone):
@@ -202,16 +318,33 @@ def parsedate_to_datetime(data):
tzinfo=datetime.timezone(datetime.timedelta(seconds=tz)))
-def parseaddr(addr):
+def parseaddr(addr, *, strict=True):
"""
Parse addr into its constituent realname and email address parts.
Return a tuple of realname and email address, unless the parse fails, in
which case return a 2-tuple of ('', '').
+
+ If strict is True, use a strict parser which rejects malformed inputs.
"""
- addrs = _AddressList(addr).addresslist
- if not addrs:
- return '', ''
+ if not strict:
+ addrs = _AddressList(addr).addresslist
+ if not addrs:
+ return ('', '')
+ return addrs[0]
+
+ if isinstance(addr, list):
+ addr = addr[0]
+
+ if not isinstance(addr, str):
+ return ('', '')
+
+ addr = _pre_parse_validation([addr])[0]
+ addrs = _post_parse_validation(_AddressList(addr).addresslist)
+
+ if not addrs or len(addrs) > 1:
+ return ('', '')
+
return addrs[0]
--- a/Lib/test/test_email/test_email.py
+++ b/Lib/test/test_email/test_email.py
@@ -16,6 +16,7 @@ from unittest.mock import patch
import email
import email.policy
+import email.utils
from email.charset import Charset
from email.header import Header, decode_header, make_header
@@ -3248,15 +3249,137 @@ Foo
[('Al Person', 'aperson@dom.ain'),
('Bud Person', 'bperson@dom.ain')])
+ def test_parsing_errors(self):
+ """Test for parsing errors from CVE-2023-27043 and CVE-2019-16056"""
+ alice = 'alice@example.org'
+ bob = 'bob@example.com'
+ empty = ('', '')
+
+ # Test utils.getaddresses() and utils.parseaddr() on malformed email
+ # addresses: default behavior (strict=True) rejects malformed address,
+ # and strict=False which tolerates malformed address.
+ for invalid_separator, expected_non_strict in (
+ ('(', [(f'<{bob}>', alice)]),
+ (')', [('', alice), empty, ('', bob)]),
+ ('<', [('', alice), empty, ('', bob), empty]),
+ ('>', [('', alice), empty, ('', bob)]),
+ ('[', [('', f'{alice}[<{bob}>]')]),
+ (']', [('', alice), empty, ('', bob)]),
+ ('@', [empty, empty, ('', bob)]),
+ (';', [('', alice), empty, ('', bob)]),
+ (':', [('', alice), ('', bob)]),
+ ('.', [('', alice + '.'), ('', bob)]),
+ ('"', [('', alice), ('', f'<{bob}>')]),
+ ):
+ address = f'{alice}{invalid_separator}<{bob}>'
+ with self.subTest(address=address):
+ self.assertEqual(utils.getaddresses([address]),
+ [empty])
+ self.assertEqual(utils.getaddresses([address], strict=False),
+ expected_non_strict)
+
+ self.assertEqual(utils.parseaddr([address]),
+ empty)
+ self.assertEqual(utils.parseaddr([address], strict=False),
+ ('', address))
+
+ # Comma (',') is treated differently depending on strict parameter.
+ # Comma without quotes.
+ address = f'{alice},<{bob}>'
+ self.assertEqual(utils.getaddresses([address]),
+ [('', alice), ('', bob)])
+ self.assertEqual(utils.getaddresses([address], strict=False),
+ [('', alice), ('', bob)])
+ self.assertEqual(utils.parseaddr([address]),
+ empty)
+ self.assertEqual(utils.parseaddr([address], strict=False),
+ ('', address))
+
+ # Real name between quotes containing comma.
+ address = '"Alice, alice@example.org" <bob@example.com>'
+ expected_strict = ('Alice, alice@example.org', 'bob@example.com')
+ self.assertEqual(utils.getaddresses([address]), [expected_strict])
+ self.assertEqual(utils.getaddresses([address], strict=False), [expected_strict])
+ self.assertEqual(utils.parseaddr([address]), expected_strict)
+ self.assertEqual(utils.parseaddr([address], strict=False),
+ ('', address))
+
+ # Valid parenthesis in comments.
+ address = 'alice@example.org (Alice)'
+ expected_strict = ('Alice', 'alice@example.org')
+ self.assertEqual(utils.getaddresses([address]), [expected_strict])
+ self.assertEqual(utils.getaddresses([address], strict=False), [expected_strict])
+ self.assertEqual(utils.parseaddr([address]), expected_strict)
+ self.assertEqual(utils.parseaddr([address], strict=False),
+ ('', address))
+
+ # Invalid parenthesis in comments.
+ address = 'alice@example.org )Alice('
+ self.assertEqual(utils.getaddresses([address]), [empty])
+ self.assertEqual(utils.getaddresses([address], strict=False),
+ [('', 'alice@example.org'), ('', ''), ('', 'Alice')])
+ self.assertEqual(utils.parseaddr([address]), empty)
+ self.assertEqual(utils.parseaddr([address], strict=False),
+ ('', address))
+
+ # Two addresses with quotes separated by comma.
+ address = '"Jane Doe" <jane@example.net>, "John Doe" <john@example.net>'
+ self.assertEqual(utils.getaddresses([address]),
+ [('Jane Doe', 'jane@example.net'),
+ ('John Doe', 'john@example.net')])
+ self.assertEqual(utils.getaddresses([address], strict=False),
+ [('Jane Doe', 'jane@example.net'),
+ ('John Doe', 'john@example.net')])
+ self.assertEqual(utils.parseaddr([address]), empty)
+ self.assertEqual(utils.parseaddr([address], strict=False),
+ ('', address))
+
+ # Test email.utils.supports_strict_parsing attribute
+ self.assertEqual(email.utils.supports_strict_parsing, True)
+
def test_getaddresses_nasty(self):
- eq = self.assertEqual
- eq(utils.getaddresses(['foo: ;']), [('', '')])
- eq(utils.getaddresses(
- ['[]*-- =~$']),
- [('', ''), ('', ''), ('', '*--')])
- eq(utils.getaddresses(
- ['foo: ;', '"Jason R. Mastaler" <jason@dom.ain>']),
- [('', ''), ('Jason R. Mastaler', 'jason@dom.ain')])
+ for addresses, expected in (
+ (['"Sürname, Firstname" <to@example.com>'],
+ [('Sürname, Firstname', 'to@example.com')]),
+
+ (['foo: ;'],
+ [('', '')]),
+
+ (['foo: ;', '"Jason R. Mastaler" <jason@dom.ain>'],
+ [('', ''), ('Jason R. Mastaler', 'jason@dom.ain')]),
+
+ ([r'Pete(A nice \) chap) <pete(his account)@silly.test(his host)>'],
+ [('Pete (A nice ) chap his account his host)', 'pete@silly.test')]),
+
+ (['(Empty list)(start)Undisclosed recipients :(nobody(I know))'],
+ [('', '')]),
+
+ (['Mary <@machine.tld:mary@example.net>, , jdoe@test . example'],
+ [('Mary', 'mary@example.net'), ('', ''), ('', 'jdoe@test.example')]),
+
+ (['John Doe <jdoe@machine(comment). example>'],
+ [('John Doe (comment)', 'jdoe@machine.example')]),
+
+ (['"Mary Smith: Personal Account" <smith@home.example>'],
+ [('Mary Smith: Personal Account', 'smith@home.example')]),
+
+ (['Undisclosed recipients:;'],
+ [('', '')]),
+
+ ([r'<boss@nil.test>, "Giant; \"Big\" Box" <bob@example.net>'],
+ [('', 'boss@nil.test'), ('Giant; "Big" Box', 'bob@example.net')]),
+ ):
+ with self.subTest(addresses=addresses):
+ self.assertEqual(utils.getaddresses(addresses),
+ expected)
+ self.assertEqual(utils.getaddresses(addresses, strict=False),
+ expected)
+
+ addresses = ['[]*-- =~$']
+ self.assertEqual(utils.getaddresses(addresses),
+ [('', '')])
+ self.assertEqual(utils.getaddresses(addresses, strict=False),
+ [('', ''), ('', ''), ('', '*--')])
def test_getaddresses_embedded_comment(self):
"""Test proper handling of a nested comment"""
@@ -3440,6 +3563,54 @@ multipart/report
m = cls(*constructor, policy=email.policy.default)
self.assertIs(m.policy, email.policy.default)
+ def test_iter_escaped_chars(self):
+ self.assertEqual(list(utils._iter_escaped_chars(r'a\\b\"c\\"d')),
+ [(0, 'a'),
+ (2, '\\\\'),
+ (3, 'b'),
+ (5, '\\"'),
+ (6, 'c'),
+ (8, '\\\\'),
+ (9, '"'),
+ (10, 'd')])
+ self.assertEqual(list(utils._iter_escaped_chars('a\\')),
+ [(0, 'a'), (1, '\\')])
+
+ def test_strip_quoted_realnames(self):
+ def check(addr, expected):
+ self.assertEqual(utils._strip_quoted_realnames(addr), expected)
+
+ check('"Jane Doe" <jane@example.net>, "John Doe" <john@example.net>',
+ ' <jane@example.net>, <john@example.net>')
+ check(r'"Jane \"Doe\"." <jane@example.net>',
+ ' <jane@example.net>')
+
+ # special cases
+ check(r'before"name"after', 'beforeafter')
+ check(r'before"name"', 'before')
+ check(r'b"name"', 'b') # single char
+ check(r'"name"after', 'after')
+ check(r'"name"a', 'a') # single char
+ check(r'"name"', '')
+
+ # no change
+ for addr in (
+ 'Jane Doe <jane@example.net>, John Doe <john@example.net>',
+ 'lone " quote',
+ ):
+ self.assertEqual(utils._strip_quoted_realnames(addr), addr)
+
+
+ def test_check_parenthesis(self):
+ addr = 'alice@example.net'
+ self.assertTrue(utils._check_parenthesis(f'{addr} (Alice)'))
+ self.assertFalse(utils._check_parenthesis(f'{addr} )Alice('))
+ self.assertFalse(utils._check_parenthesis(f'{addr} (Alice))'))
+ self.assertFalse(utils._check_parenthesis(f'{addr} ((Alice)'))
+
+ # Ignore real name between quotes
+ self.assertTrue(utils._check_parenthesis(f'")Alice((" {addr}'))
+
# Test the iterator/generators
class TestIterators(TestEmailBase):
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2023-10-20-15-28-08.gh-issue-102988.dStNO7.rst
@@ -0,0 +1,8 @@
+:func:`email.utils.getaddresses` and :func:`email.utils.parseaddr` now
+return ``('', '')`` 2-tuples in more situations where invalid email
+addresses are encountered instead of potentially inaccurate values. Add
+optional *strict* parameter to these two functions: use ``strict=False`` to
+get the old behavior, accept malformed inputs.
+``getattr(email.utils, 'supports_strict_parsing', False)`` can be use to check
+if the *strict* paramater is available. Patch by Thomas Dwyer and Victor
+Stinner to improve the CVE-2023-27043 fix.

View File

@ -0,0 +1,173 @@
From 732c7d512e7cdf656a3f02a38c329b14a14a8573 Mon Sep 17 00:00:00 2001
From: Seth Michael Larson <seth@python.org>
Date: Fri, 19 Apr 2024 11:21:40 -0700
Subject: [PATCH] [3.9] gh-114572: Fix locking in cert_store_stats and
get_ca_certs
---
Misc/NEWS.d/next/Security/2024-04-19-11-21-13.gh-issue-114572.t1QMQD.rst | 4
Modules/_ssl.c | 91 +++++++++-
2 files changed, 92 insertions(+), 3 deletions(-)
create mode 100644 Misc/NEWS.d/next/Security/2024-04-19-11-21-13.gh-issue-114572.t1QMQD.rst
Index: Python-3.8.19/Misc/NEWS.d/next/Security/2024-04-19-11-21-13.gh-issue-114572.t1QMQD.rst
===================================================================
--- /dev/null
+++ Python-3.8.19/Misc/NEWS.d/next/Security/2024-04-19-11-21-13.gh-issue-114572.t1QMQD.rst
@@ -0,0 +1,4 @@
+:meth:`ssl.SSLContext.cert_store_stats` and
+:meth:`ssl.SSLContext.get_ca_certs` now correctly lock access to the
+certificate store, when the :class:`ssl.SSLContext` is shared across
+multiple threads.
Index: Python-3.8.19/Modules/_ssl.c
===================================================================
--- Python-3.8.19.orig/Modules/_ssl.c
+++ Python-3.8.19/Modules/_ssl.c
@@ -168,6 +168,10 @@ extern const SSL_METHOD *TLSv1_2_method(
# define PY_OPENSSL_1_1_API 1
#endif
+#if (OPENSSL_VERSION_NUMBER >= 0x30300000L) && !defined(LIBRESSL_VERSION_NUMBER)
+# define OPENSSL_VERSION_3_3 1
+#endif
+
/* SNI support (client- and server-side) appeared in OpenSSL 1.0.0 and 0.9.8f
* This includes the SSL_set_SSL_CTX() function.
*/
@@ -212,6 +216,16 @@ extern const SSL_METHOD *TLSv1_2_method(
#define HAVE_OPENSSL_CRYPTO_LOCK
#endif
+/* OpenSSL 1.1+ allows locking X509_STORE, 1.0.2 doesn't. */
+#ifdef OPENSSL_VERSION_1_1
+#define HAVE_OPENSSL_X509_STORE_LOCK
+#endif
+
+/* OpenSSL 3.3 added the X509_STORE_get1_objects API */
+#ifdef OPENSSL_VERSION_3_3
+#define HAVE_OPENSSL_X509_STORE_GET1_OBJECTS 1
+#endif
+
#if defined(OPENSSL_VERSION_1_1) && !defined(OPENSSL_NO_SSL2)
#define OPENSSL_NO_SSL2
#endif
@@ -4678,6 +4692,54 @@ set_sni_callback(PySSLContext *self, PyO
#endif
}
+/* Shim of X509_STORE_get1_objects API from OpenSSL 3.3
+ * Only available with the X509_STORE_lock() API */
+#if defined(HAVE_OPENSSL_X509_STORE_LOCK) && !defined(OPENSSL_VERSION_3_3)
+#define HAVE_OPENSSL_X509_STORE_GET1_OBJECTS 1
+
+static X509_OBJECT *x509_object_dup(const X509_OBJECT *obj)
+{
+ int ok;
+ X509_OBJECT *ret = X509_OBJECT_new();
+ if (ret == NULL) {
+ return NULL;
+ }
+ switch (X509_OBJECT_get_type(obj)) {
+ case X509_LU_X509:
+ ok = X509_OBJECT_set1_X509(ret, X509_OBJECT_get0_X509(obj));
+ break;
+ case X509_LU_CRL:
+ /* X509_OBJECT_get0_X509_CRL was not const-correct prior to 3.0.*/
+ ok = X509_OBJECT_set1_X509_CRL(
+ ret, X509_OBJECT_get0_X509_CRL((X509_OBJECT *)obj));
+ break;
+ default:
+ /* We cannot duplicate unrecognized types in a polyfill, but it is
+ * safe to leave an empty object. The caller will ignore it. */
+ ok = 1;
+ break;
+ }
+ if (!ok) {
+ X509_OBJECT_free(ret);
+ return NULL;
+ }
+ return ret;
+}
+
+static STACK_OF(X509_OBJECT) *
+X509_STORE_get1_objects(X509_STORE *store)
+{
+ STACK_OF(X509_OBJECT) *ret;
+ if (!X509_STORE_lock(store)) {
+ return NULL;
+ }
+ ret = sk_X509_OBJECT_deep_copy(X509_STORE_get0_objects(store),
+ x509_object_dup, X509_OBJECT_free);
+ X509_STORE_unlock(store);
+ return ret;
+}
+#endif
+
PyDoc_STRVAR(PySSLContext_sni_callback_doc,
"Set a callback that will be called when a server name is provided by the SSL/TLS client in the SNI extension.\n\
\n\
@@ -4707,7 +4769,15 @@ _ssl__SSLContext_cert_store_stats_impl(P
int x509 = 0, crl = 0, ca = 0, i;
store = SSL_CTX_get_cert_store(self->ctx);
+#if HAVE_OPENSSL_X509_STORE_GET1_OBJECTS
+ objs = X509_STORE_get1_objects(store);
+ if (objs == NULL) {
+ PyErr_SetString(PyExc_MemoryError, "failed to query cert store");
+ return NULL;
+ }
+#else
objs = X509_STORE_get0_objects(store);
+#endif
for (i = 0; i < sk_X509_OBJECT_num(objs); i++) {
obj = sk_X509_OBJECT_value(objs, i);
switch (X509_OBJECT_get_type(obj)) {
@@ -4721,12 +4791,13 @@ _ssl__SSLContext_cert_store_stats_impl(P
crl++;
break;
default:
- /* Ignore X509_LU_FAIL, X509_LU_RETRY, X509_LU_PKEY.
- * As far as I can tell they are internal states and never
- * stored in a cert store */
+ /* Ignore unrecognized types. */
break;
}
}
+#if HAVE_OPENSSL_X509_STORE_GET1_OBJECTS
+ sk_X509_OBJECT_pop_free(objs, X509_OBJECT_free);
+#endif
return Py_BuildValue("{sisisi}", "x509", x509, "crl", crl,
"x509_ca", ca);
}
@@ -4758,7 +4829,15 @@ _ssl__SSLContext_get_ca_certs_impl(PySSL
}
store = SSL_CTX_get_cert_store(self->ctx);
+#if HAVE_OPENSSL_X509_STORE_GET1_OBJECTS
+ objs = X509_STORE_get1_objects(store);
+ if (objs == NULL) {
+ PyErr_SetString(PyExc_MemoryError, "failed to query cert store");
+ return NULL;
+ }
+#else
objs = X509_STORE_get0_objects(store);
+#endif
for (i = 0; i < sk_X509_OBJECT_num(objs); i++) {
X509_OBJECT *obj;
X509 *cert;
@@ -4786,9 +4865,15 @@ _ssl__SSLContext_get_ca_certs_impl(PySSL
}
Py_CLEAR(ci);
}
+#if HAVE_OPENSSL_X509_STORE_GET1_OBJECTS
+ sk_X509_OBJECT_pop_free(objs, X509_OBJECT_free);
+#endif
return rlist;
error:
+#if HAVE_OPENSSL_X509_STORE_GET1_OBJECTS
+ sk_X509_OBJECT_pop_free(objs, X509_OBJECT_free);
+#endif
Py_XDECREF(ci);
Py_XDECREF(rlist);
return NULL;

View File

@ -0,0 +1,444 @@
From 05a14677846ed0a35773cd2ba582f9a65a3dfa48 Mon Sep 17 00:00:00 2001
From: Petr Viktorin <encukou@gmail.com>
Date: Wed, 24 Apr 2024 14:29:30 +0200
Subject: [PATCH 1/3] gh-113171: gh-65056: Fix "private" (non-global) IP
address ranges (GH-113179) (GH-113186) (GH-118177)
* GH-113171: Fix "private" (non-global) IP address ranges (GH-113179)
The _private_networks variables, used by various is_private
implementations, were missing some ranges and at the same time had
overly strict ranges (where there are more specific ranges considered
globally reachable by the IANA registries).
This patch updates the ranges with what was missing or otherwise
incorrect.
100.64.0.0/10 is left alone, for now, as it's been made special in [1].
The _address_exclude_many() call returns 8 networks for IPv4, 121
networks for IPv6.
[1] https://github.com/python/cpython/issues/61602
* GH-65056: Improve the IP address' is_global/is_private documentation (GH-113186)
It wasn't clear what the semantics of is_global/is_private are and, when
one gets to the bottom of it, it's not quite so simple (hence the
exceptions listed).
(cherry picked from commit 2a4cbf17af19a01d942f9579342f77c39fbd23c4)
(cherry picked from commit 40d75c2b7f5c67e254d0a025e0f2e2c7ada7f69f)
---------
(cherry picked from commit f86b17ac511e68192ba71f27e752321a3252cee3)
Co-authored-by: Jakub Stasiak <jakub@stasiak.at>
---
Doc/library/ipaddress.rst | 43 ++++++++-
Doc/whatsnew/3.8.rst | 9 ++
Lib/ipaddress.py | 95 +++++++++++++++----
Lib/test/test_ipaddress.py | 52 ++++++++++
...-03-14-01-38-44.gh-issue-113171.VFnObz.rst | 9 ++
5 files changed, 187 insertions(+), 21 deletions(-)
create mode 100644 Misc/NEWS.d/next/Library/2024-03-14-01-38-44.gh-issue-113171.VFnObz.rst
diff --git a/Doc/library/ipaddress.rst b/Doc/library/ipaddress.rst
index 5e21d5db2ed9c3..5944e33bb1b339 100644
--- a/Doc/library/ipaddress.rst
+++ b/Doc/library/ipaddress.rst
@@ -179,18 +179,53 @@ write code that handles both IP versions correctly. Address objects are
.. attribute:: is_private
- ``True`` if the address is allocated for private networks. See
+ ``True`` if the address is defined as not globally reachable by
iana-ipv4-special-registry_ (for IPv4) or iana-ipv6-special-registry_
- (for IPv6).
+ (for IPv6) with the following exceptions:
+
+ * ``is_private`` is ``False`` for the shared address space (``100.64.0.0/10``)
+ * For IPv4-mapped IPv6-addresses the ``is_private`` value is determined by the
+ semantics of the underlying IPv4 addresses and the following condition holds
+ (see :attr:`IPv6Address.ipv4_mapped`)::
+
+ address.is_private == address.ipv4_mapped.is_private
+
+ ``is_private`` has value opposite to :attr:`is_global`, except for the shared address space
+ (``100.64.0.0/10`` range) where they are both ``False``.
+
+ .. versionchanged:: 3.8.20
+
+ Fixed some false positives and false negatives.
+
+ * ``192.0.0.0/24`` is considered private with the exception of ``192.0.0.9/32`` and
+ ``192.0.0.10/32`` (previously: only the ``192.0.0.0/29`` sub-range was considered private).
+ * ``64:ff9b:1::/48`` is considered private.
+ * ``2002::/16`` is considered private.
+ * There are exceptions within ``2001::/23`` (otherwise considered private): ``2001:1::1/128``,
+ ``2001:1::2/128``, ``2001:3::/32``, ``2001:4:112::/48``, ``2001:20::/28``, ``2001:30::/28``.
+ The exceptions are not considered private.
.. attribute:: is_global
- ``True`` if the address is allocated for public networks. See
+ ``True`` if the address is defined as globally reachable by
iana-ipv4-special-registry_ (for IPv4) or iana-ipv6-special-registry_
- (for IPv6).
+ (for IPv6) with the following exception:
+
+ For IPv4-mapped IPv6-addresses the ``is_private`` value is determined by the
+ semantics of the underlying IPv4 addresses and the following condition holds
+ (see :attr:`IPv6Address.ipv4_mapped`)::
+
+ address.is_global == address.ipv4_mapped.is_global
+
+ ``is_global`` has value opposite to :attr:`is_private`, except for the shared address space
+ (``100.64.0.0/10`` range) where they are both ``False``.
.. versionadded:: 3.4
+ .. versionchanged:: 3.8.20
+
+ Fixed some false positives and false negatives, see :attr:`is_private` for details.
+
.. attribute:: is_unspecified
``True`` if the address is unspecified. See :RFC:`5735` (for IPv4)
diff --git a/Doc/whatsnew/3.8.rst b/Doc/whatsnew/3.8.rst
index e5278da3f6a5be..de4dd856877543 100644
--- a/Doc/whatsnew/3.8.rst
+++ b/Doc/whatsnew/3.8.rst
@@ -2355,3 +2355,12 @@ tarfile
:exc:`DeprecationWarning`.
In Python 3.14, the default will switch to ``'data'``.
(Contributed by Petr Viktorin in :pep:`706`.)
+
+Notable changes in 3.8.20
+=========================
+
+ipaddress
+---------
+
+* Fixed ``is_global`` and ``is_private`` behavior in ``IPv4Address``,
+ ``IPv6Address``, ``IPv4Network`` and ``IPv6Network``.
diff --git a/Lib/ipaddress.py b/Lib/ipaddress.py
index d351f07a5bd960..142c3b13b1617e 100644
--- a/Lib/ipaddress.py
+++ b/Lib/ipaddress.py
@@ -1275,18 +1275,41 @@ def is_reserved(self):
@property
@functools.lru_cache()
def is_private(self):
- """Test if this address is allocated for private networks.
+ """``True`` if the address is defined as not globally reachable by
+ iana-ipv4-special-registry_ (for IPv4) or iana-ipv6-special-registry_
+ (for IPv6) with the following exceptions:
- Returns:
- A boolean, True if the address is reserved per
- iana-ipv4-special-registry.
+ * ``is_private`` is ``False`` for ``100.64.0.0/10``
+ * For IPv4-mapped IPv6-addresses the ``is_private`` value is determined by the
+ semantics of the underlying IPv4 addresses and the following condition holds
+ (see :attr:`IPv6Address.ipv4_mapped`)::
+
+ address.is_private == address.ipv4_mapped.is_private
+ ``is_private`` has value opposite to :attr:`is_global`, except for the ``100.64.0.0/10``
+ IPv4 range where they are both ``False``.
"""
- return any(self in net for net in self._constants._private_networks)
+ return (
+ any(self in net for net in self._constants._private_networks)
+ and all(self not in net for net in self._constants._private_networks_exceptions)
+ )
@property
@functools.lru_cache()
def is_global(self):
+ """``True`` if the address is defined as globally reachable by
+ iana-ipv4-special-registry_ (for IPv4) or iana-ipv6-special-registry_
+ (for IPv6) with the following exception:
+
+ For IPv4-mapped IPv6-addresses the ``is_private`` value is determined by the
+ semantics of the underlying IPv4 addresses and the following condition holds
+ (see :attr:`IPv6Address.ipv4_mapped`)::
+
+ address.is_global == address.ipv4_mapped.is_global
+
+ ``is_global`` has value opposite to :attr:`is_private`, except for the ``100.64.0.0/10``
+ IPv4 range where they are both ``False``.
+ """
return self not in self._constants._public_network and not self.is_private
@property
@@ -1490,13 +1513,15 @@ class _IPv4Constants:
_public_network = IPv4Network('100.64.0.0/10')
+ # Not globally reachable address blocks listed on
+ # https://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml
_private_networks = [
IPv4Network('0.0.0.0/8'),
IPv4Network('10.0.0.0/8'),
IPv4Network('127.0.0.0/8'),
IPv4Network('169.254.0.0/16'),
IPv4Network('172.16.0.0/12'),
- IPv4Network('192.0.0.0/29'),
+ IPv4Network('192.0.0.0/24'),
IPv4Network('192.0.0.170/31'),
IPv4Network('192.0.2.0/24'),
IPv4Network('192.168.0.0/16'),
@@ -1507,6 +1532,11 @@ class _IPv4Constants:
IPv4Network('255.255.255.255/32'),
]
+ _private_networks_exceptions = [
+ IPv4Network('192.0.0.9/32'),
+ IPv4Network('192.0.0.10/32'),
+ ]
+
_reserved_network = IPv4Network('240.0.0.0/4')
_unspecified_address = IPv4Address('0.0.0.0')
@@ -1897,23 +1927,42 @@ def is_site_local(self):
@property
@functools.lru_cache()
def is_private(self):
- """Test if this address is allocated for private networks.
+ """``True`` if the address is defined as not globally reachable by
+ iana-ipv4-special-registry_ (for IPv4) or iana-ipv6-special-registry_
+ (for IPv6) with the following exceptions:
- Returns:
- A boolean, True if the address is reserved per
- iana-ipv6-special-registry.
+ * ``is_private`` is ``False`` for ``100.64.0.0/10``
+ * For IPv4-mapped IPv6-addresses the ``is_private`` value is determined by the
+ semantics of the underlying IPv4 addresses and the following condition holds
+ (see :attr:`IPv6Address.ipv4_mapped`)::
+
+ address.is_private == address.ipv4_mapped.is_private
+ ``is_private`` has value opposite to :attr:`is_global`, except for the ``100.64.0.0/10``
+ IPv4 range where they are both ``False``.
"""
- return any(self in net for net in self._constants._private_networks)
+ ipv4_mapped = self.ipv4_mapped
+ if ipv4_mapped is not None:
+ return ipv4_mapped.is_private
+ return (
+ any(self in net for net in self._constants._private_networks)
+ and all(self not in net for net in self._constants._private_networks_exceptions)
+ )
@property
def is_global(self):
- """Test if this address is allocated for public networks.
+ """``True`` if the address is defined as globally reachable by
+ iana-ipv4-special-registry_ (for IPv4) or iana-ipv6-special-registry_
+ (for IPv6) with the following exception:
- Returns:
- A boolean, true if the address is not reserved per
- iana-ipv6-special-registry.
+ For IPv4-mapped IPv6-addresses the ``is_private`` value is determined by the
+ semantics of the underlying IPv4 addresses and the following condition holds
+ (see :attr:`IPv6Address.ipv4_mapped`)::
+
+ address.is_global == address.ipv4_mapped.is_global
+ ``is_global`` has value opposite to :attr:`is_private`, except for the ``100.64.0.0/10``
+ IPv4 range where they are both ``False``.
"""
return not self.is_private
@@ -2154,19 +2203,31 @@ class _IPv6Constants:
_multicast_network = IPv6Network('ff00::/8')
+ # Not globally reachable address blocks listed on
+ # https://www.iana.org/assignments/iana-ipv6-special-registry/iana-ipv6-special-registry.xhtml
_private_networks = [
IPv6Network('::1/128'),
IPv6Network('::/128'),
IPv6Network('::ffff:0:0/96'),
+ IPv6Network('64:ff9b:1::/48'),
IPv6Network('100::/64'),
IPv6Network('2001::/23'),
- IPv6Network('2001:2::/48'),
IPv6Network('2001:db8::/32'),
- IPv6Network('2001:10::/28'),
+ # IANA says N/A, let's consider it not globally reachable to be safe
+ IPv6Network('2002::/16'),
IPv6Network('fc00::/7'),
IPv6Network('fe80::/10'),
]
+ _private_networks_exceptions = [
+ IPv6Network('2001:1::1/128'),
+ IPv6Network('2001:1::2/128'),
+ IPv6Network('2001:3::/32'),
+ IPv6Network('2001:4:112::/48'),
+ IPv6Network('2001:20::/28'),
+ IPv6Network('2001:30::/28'),
+ ]
+
_reserved_networks = [
IPv6Network('::/8'), IPv6Network('100::/8'),
IPv6Network('200::/7'), IPv6Network('400::/6'),
diff --git a/Lib/test/test_ipaddress.py b/Lib/test/test_ipaddress.py
index 1297b8371d8583..46002111b3270a 100644
--- a/Lib/test/test_ipaddress.py
+++ b/Lib/test/test_ipaddress.py
@@ -1761,6 +1761,10 @@ def testReservedIpv4(self):
self.assertEqual(True, ipaddress.ip_address(
'172.31.255.255').is_private)
self.assertEqual(False, ipaddress.ip_address('172.32.0.0').is_private)
+ self.assertFalse(ipaddress.ip_address('192.0.0.0').is_global)
+ self.assertTrue(ipaddress.ip_address('192.0.0.9').is_global)
+ self.assertTrue(ipaddress.ip_address('192.0.0.10').is_global)
+ self.assertFalse(ipaddress.ip_address('192.0.0.255').is_global)
self.assertEqual(True,
ipaddress.ip_address('169.254.100.200').is_link_local)
@@ -1776,6 +1780,40 @@ def testReservedIpv4(self):
self.assertEqual(False, ipaddress.ip_address('128.0.0.0').is_loopback)
self.assertEqual(True, ipaddress.ip_network('0.0.0.0').is_unspecified)
+ def testPrivateNetworks(self):
+ self.assertEqual(False, ipaddress.ip_network("0.0.0.0/0").is_private)
+ self.assertEqual(False, ipaddress.ip_network("1.0.0.0/8").is_private)
+
+ self.assertEqual(True, ipaddress.ip_network("0.0.0.0/8").is_private)
+ self.assertEqual(True, ipaddress.ip_network("10.0.0.0/8").is_private)
+ self.assertEqual(True, ipaddress.ip_network("127.0.0.0/8").is_private)
+ self.assertEqual(True, ipaddress.ip_network("169.254.0.0/16").is_private)
+ self.assertEqual(True, ipaddress.ip_network("172.16.0.0/12").is_private)
+ self.assertEqual(True, ipaddress.ip_network("192.0.0.0/29").is_private)
+ self.assertEqual(False, ipaddress.ip_network("192.0.0.9/32").is_private)
+ self.assertEqual(True, ipaddress.ip_network("192.0.0.170/31").is_private)
+ self.assertEqual(True, ipaddress.ip_network("192.0.2.0/24").is_private)
+ self.assertEqual(True, ipaddress.ip_network("192.168.0.0/16").is_private)
+ self.assertEqual(True, ipaddress.ip_network("198.18.0.0/15").is_private)
+ self.assertEqual(True, ipaddress.ip_network("198.51.100.0/24").is_private)
+ self.assertEqual(True, ipaddress.ip_network("203.0.113.0/24").is_private)
+ self.assertEqual(True, ipaddress.ip_network("240.0.0.0/4").is_private)
+ self.assertEqual(True, ipaddress.ip_network("255.255.255.255/32").is_private)
+
+ self.assertEqual(False, ipaddress.ip_network("::/0").is_private)
+ self.assertEqual(False, ipaddress.ip_network("::ff/128").is_private)
+
+ self.assertEqual(True, ipaddress.ip_network("::1/128").is_private)
+ self.assertEqual(True, ipaddress.ip_network("::/128").is_private)
+ self.assertEqual(True, ipaddress.ip_network("::ffff:0:0/96").is_private)
+ self.assertEqual(True, ipaddress.ip_network("100::/64").is_private)
+ self.assertEqual(True, ipaddress.ip_network("2001:2::/48").is_private)
+ self.assertEqual(False, ipaddress.ip_network("2001:3::/48").is_private)
+ self.assertEqual(True, ipaddress.ip_network("2001:db8::/32").is_private)
+ self.assertEqual(True, ipaddress.ip_network("2001:10::/28").is_private)
+ self.assertEqual(True, ipaddress.ip_network("fc00::/7").is_private)
+ self.assertEqual(True, ipaddress.ip_network("fe80::/10").is_private)
+
def testReservedIpv6(self):
self.assertEqual(True, ipaddress.ip_network('ffff::').is_multicast)
@@ -1849,6 +1887,20 @@ def testReservedIpv6(self):
self.assertEqual(True, ipaddress.ip_address('0::0').is_unspecified)
self.assertEqual(False, ipaddress.ip_address('::1').is_unspecified)
+ self.assertFalse(ipaddress.ip_address('64:ff9b:1::').is_global)
+ self.assertFalse(ipaddress.ip_address('2001::').is_global)
+ self.assertTrue(ipaddress.ip_address('2001:1::1').is_global)
+ self.assertTrue(ipaddress.ip_address('2001:1::2').is_global)
+ self.assertFalse(ipaddress.ip_address('2001:2::').is_global)
+ self.assertTrue(ipaddress.ip_address('2001:3::').is_global)
+ self.assertFalse(ipaddress.ip_address('2001:4::').is_global)
+ self.assertTrue(ipaddress.ip_address('2001:4:112::').is_global)
+ self.assertFalse(ipaddress.ip_address('2001:10::').is_global)
+ self.assertTrue(ipaddress.ip_address('2001:20::').is_global)
+ self.assertTrue(ipaddress.ip_address('2001:30::').is_global)
+ self.assertFalse(ipaddress.ip_address('2001:40::').is_global)
+ self.assertFalse(ipaddress.ip_address('2002::').is_global)
+
# some generic IETF reserved addresses
self.assertEqual(True, ipaddress.ip_address('100::').is_reserved)
self.assertEqual(True, ipaddress.ip_network('4000::1/128').is_reserved)
diff --git a/Misc/NEWS.d/next/Library/2024-03-14-01-38-44.gh-issue-113171.VFnObz.rst b/Misc/NEWS.d/next/Library/2024-03-14-01-38-44.gh-issue-113171.VFnObz.rst
new file mode 100644
index 00000000000000..f9a72473be4e2c
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2024-03-14-01-38-44.gh-issue-113171.VFnObz.rst
@@ -0,0 +1,9 @@
+Fixed various false positives and false negatives in
+
+* :attr:`ipaddress.IPv4Address.is_private` (see these docs for details)
+* :attr:`ipaddress.IPv4Address.is_global`
+* :attr:`ipaddress.IPv6Address.is_private`
+* :attr:`ipaddress.IPv6Address.is_global`
+
+Also in the corresponding :class:`ipaddress.IPv4Network` and :class:`ipaddress.IPv6Network`
+attributes.
From 2e92223a4298fbf18c1c7f853b6d883943c00c52 Mon Sep 17 00:00:00 2001
From: Petr Viktorin <encukou@gmail.com>
Date: Wed, 24 Apr 2024 15:16:13 +0200
Subject: [PATCH 2/3] Adjust test for 3.10 semantics of is_private on networks
In 3.10 and below, is_private checks whether the network and broadcast
address are both private.
In later versions (where the test wss backported from), it checks
whether they both are in the same private network.
For 0.0.0.0/0, both 0.0.0.0 and 255.225.255.255 are private,
but one is in 0.0.0.0/8 ("This network") and the other in
255.255.255.255/32 ("Limited broadcast").
---
Lib/test/test_ipaddress.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Lib/test/test_ipaddress.py b/Lib/test/test_ipaddress.py
index 46002111b3270a..fad40334f0d317 100644
--- a/Lib/test/test_ipaddress.py
+++ b/Lib/test/test_ipaddress.py
@@ -1781,7 +1781,7 @@ def testReservedIpv4(self):
self.assertEqual(True, ipaddress.ip_network('0.0.0.0').is_unspecified)
def testPrivateNetworks(self):
- self.assertEqual(False, ipaddress.ip_network("0.0.0.0/0").is_private)
+ self.assertEqual(True, ipaddress.ip_network("0.0.0.0/0").is_private)
self.assertEqual(False, ipaddress.ip_network("1.0.0.0/8").is_private)
self.assertEqual(True, ipaddress.ip_network("0.0.0.0/8").is_private)
From e366724f6e290b71ec49005079e8472c3cac2594 Mon Sep 17 00:00:00 2001
From: Petr Viktorin <encukou@gmail.com>
Date: Wed, 1 May 2024 15:29:13 +0200
Subject: [PATCH 3/3] Add IPv6 addresses to suspignore.csv
That's a lot of semicolons!
---
Doc/tools/susp-ignored.csv | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/Doc/tools/susp-ignored.csv b/Doc/tools/susp-ignored.csv
index dd6aa38d72adcc..fadeab3f98f2d7 100644
--- a/Doc/tools/susp-ignored.csv
+++ b/Doc/tools/susp-ignored.csv
@@ -158,6 +158,14 @@ library/ipaddress,,:db00,2001:db00::0/24
library/ipaddress,,::,2001:db00::0/24
library/ipaddress,,:db00,2001:db00::0/ffff:ff00::
library/ipaddress,,::,2001:db00::0/ffff:ff00::
+library/ipaddress,,:ff9b,64:ff9b:1::/48
+library/ipaddress,,::,64:ff9b:1::/48
+library/ipaddress,,::,2001::
+library/ipaddress,,::,2001:1::
+library/ipaddress,,::,2001:3::
+library/ipaddress,,::,2001:4:112::
+library/ipaddress,,::,2001:20::
+library/ipaddress,,::,2001:30::
library/itertools,,:step,elements from seq[start:stop:step]
library/itertools,,:stop,elements from seq[start:stop:step]
library/logging.handlers,,:port,host:port

236
F00102-lib64.patch Normal file
View File

@ -0,0 +1,236 @@
From 81904771db8b112c8617a111e989b68e55af7a9c Mon Sep 17 00:00:00 2001
From: David Malcolm <dmalcolm@redhat.com>
Date: Wed, 13 Jan 2010 21:25:18 +0000
Subject: [PATCH] 00102: Change the various install paths to use /usr/lib64/
instead or /usr/lib/
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Only used when "%{_lib}" == "lib64".
Co-authored-by: David Malcolm <dmalcolm@redhat.com>
Co-authored-by: Thomas Spura <tomspur@fedoraproject.org>
Co-authored-by: Slavek Kabrda <bkabrda@redhat.com>
Co-authored-by: Matej Stuchlik <mstuchli@redhat.com>
Co-authored-by: Tomas Orsava <torsava@redhat.com>
Co-authored-by: Charalampos Stratakis <cstratak@redhat.com>
Co-authored-by: Petr Viktorin <pviktori@redhat.com>
Co-authored-by: Miro Hrončok <miro@hroncok.cz>
Co-authored-by: Iryna Shcherbina <shcherbina.iryna@gmail.com>
---
Lib/distutils/command/install.py | 4 ++--
Lib/distutils/sysconfig.py | 6 +++++-
Lib/distutils/tests/test_install.py | 3 ++-
Lib/site.py | 4 ++++
Lib/sysconfig.py | 12 ++++++------
Lib/test/test_site.py | 4 ++--
Makefile.pre.in | 2 +-
Modules/getpath.c | 6 +++---
configure | 4 ++--
configure.ac | 4 ++--
setup.py | 6 +++---
11 files changed, 32 insertions(+), 23 deletions(-)
--- a/Lib/distutils/command/install.py
+++ b/Lib/distutils/command/install.py
@@ -30,14 +30,14 @@ WINDOWS_SCHEME = {
INSTALL_SCHEMES = {
'unix_prefix': {
'purelib': '$base/lib/python$py_version_short/site-packages',
- 'platlib': '$platbase/lib/python$py_version_short/site-packages',
+ 'platlib': '$platbase/lib64/python$py_version_short/site-packages',
'headers': '$base/include/python$py_version_short$abiflags/$dist_name',
'scripts': '$base/bin',
'data' : '$base',
},
'unix_home': {
'purelib': '$base/lib/python',
- 'platlib': '$base/lib/python',
+ 'platlib': '$base/lib64/python',
'headers': '$base/include/python/$dist_name',
'scripts': '$base/bin',
'data' : '$base',
--- a/Lib/distutils/sysconfig.py
+++ b/Lib/distutils/sysconfig.py
@@ -146,8 +146,12 @@ def get_python_lib(plat_specific=0, stan
prefix = plat_specific and EXEC_PREFIX or PREFIX
if os.name == "posix":
+ if plat_specific or standard_lib:
+ lib = "lib64"
+ else:
+ lib = "lib"
libpython = os.path.join(prefix,
- "lib", "python" + get_python_version())
+ lib, "python" + get_python_version())
if standard_lib:
return libpython
else:
--- a/Lib/distutils/tests/test_install.py
+++ b/Lib/distutils/tests/test_install.py
@@ -57,8 +57,9 @@ class InstallTestCase(support.TempdirMan
self.assertEqual(got, expected)
libdir = os.path.join(destination, "lib", "python")
+ platlibdir = os.path.join(destination, "lib", "python")
check_path(cmd.install_lib, libdir)
- check_path(cmd.install_platlib, libdir)
+ check_path(cmd.install_platlib, platlibdir)
check_path(cmd.install_purelib, libdir)
check_path(cmd.install_headers,
os.path.join(destination, "include", "python", "foopkg"))
--- a/Lib/site.py
+++ b/Lib/site.py
@@ -344,11 +344,15 @@ def getsitepackages(prefixes=None):
seen.add(prefix)
if os.sep == '/':
+ sitepackages.append(os.path.join(prefix, "lib64",
+ "python" + sys.version[:3],
+ "site-packages"))
sitepackages.append(os.path.join(prefix, "lib",
"python%d.%d" % sys.version_info[:2],
"site-packages"))
else:
sitepackages.append(prefix)
+ sitepackages.append(os.path.join(prefix, "lib64", "site-packages"))
sitepackages.append(os.path.join(prefix, "lib", "site-packages"))
return sitepackages
--- a/Lib/sysconfig.py
+++ b/Lib/sysconfig.py
@@ -25,10 +25,10 @@ _ALWAYS_STR = {
_INSTALL_SCHEMES = {
'posix_prefix': {
- 'stdlib': '{installed_base}/lib/python{py_version_short}',
- 'platstdlib': '{platbase}/lib/python{py_version_short}',
+ 'stdlib': '{installed_base}/lib64/python{py_version_short}',
+ 'platstdlib': '{platbase}/lib64/python{py_version_short}',
'purelib': '{base}/lib/python{py_version_short}/site-packages',
- 'platlib': '{platbase}/lib/python{py_version_short}/site-packages',
+ 'platlib': '{platbase}/lib64/python{py_version_short}/site-packages',
'include':
'{installed_base}/include/python{py_version_short}{abiflags}',
'platinclude':
@@ -67,10 +67,10 @@ _INSTALL_SCHEMES = {
'data': '{userbase}',
},
'posix_user': {
- 'stdlib': '{userbase}/lib/python{py_version_short}',
- 'platstdlib': '{userbase}/lib/python{py_version_short}',
+ 'stdlib': '{userbase}/lib64/python{py_version_short}',
+ 'platstdlib': '{userbase}/lib64/python{py_version_short}',
'purelib': '{userbase}/lib/python{py_version_short}/site-packages',
- 'platlib': '{userbase}/lib/python{py_version_short}/site-packages',
+ 'platlib': '{userbase}/lib64/python{py_version_short}/site-packages',
'include': '{userbase}/include/python{py_version_short}',
'scripts': '{userbase}/bin',
'data': '{userbase}',
--- a/Lib/test/test_site.py
+++ b/Lib/test/test_site.py
@@ -307,8 +307,8 @@ class HelperFunctionsTests(unittest.Test
dirs = site.getsitepackages()
if os.sep == '/':
# OS X, Linux, FreeBSD, etc
- self.assertEqual(len(dirs), 1)
- wanted = os.path.join('xoxo', 'lib',
+ self.assertEqual(len(dirs), 2)
+ wanted = os.path.join('xoxo', 'lib64',
'python%d.%d' % sys.version_info[:2],
'site-packages')
self.assertEqual(dirs[0], wanted)
--- a/Makefile.pre.in
+++ b/Makefile.pre.in
@@ -143,7 +143,7 @@ LIBDIR= @libdir@
MANDIR= @mandir@
INCLUDEDIR= @includedir@
CONFINCLUDEDIR= $(exec_prefix)/include
-SCRIPTDIR= $(prefix)/lib
+SCRIPTDIR= $(prefix)/lib64
ABIFLAGS= @ABIFLAGS@
# Detailed destination directories
--- a/Modules/getpath.c
+++ b/Modules/getpath.c
@@ -730,7 +730,7 @@ calculate_exec_prefix(PyCalculatePath *c
if (safe_wcscpy(exec_prefix, calculate->exec_prefix, exec_prefix_len) < 0) {
return PATHLEN_ERR();
}
- status = joinpath(exec_prefix, L"lib/lib-dynload", exec_prefix_len);
+ status = joinpath(exec_prefix, L"lib64/lib-dynload", exec_prefix_len);
if (_PyStatus_EXCEPTION(status)) {
return status;
}
@@ -1063,7 +1063,7 @@ calculate_zip_path(PyCalculatePath *calc
return PATHLEN_ERR();
}
}
- status = joinpath(zip_path, L"lib/python00.zip", zip_path_len);
+ status = joinpath(zip_path, L"lib64/python00.zip", zip_path_len);
if (_PyStatus_EXCEPTION(status)) {
return status;
}
@@ -1193,7 +1193,7 @@ calculate_init(PyCalculatePath *calculat
if (!calculate->exec_prefix) {
return DECODE_LOCALE_ERR("EXEC_PREFIX define", len);
}
- calculate->lib_python = Py_DecodeLocale("lib/python" VERSION, &len);
+ calculate->lib_python = Py_DecodeLocale("lib64/python" VERSION, &len);
if (!calculate->lib_python) {
return DECODE_LOCALE_ERR("EXEC_PREFIX define", len);
}
--- a/configure
+++ b/configure
@@ -15276,9 +15276,9 @@ fi
if test x$PLATFORM_TRIPLET = x; then
- LIBPL='$(prefix)'"/lib/python${VERSION}/config-${LDVERSION}"
+ LIBPL='$(prefix)'"/lib64/python${VERSION}/config-${LDVERSION}"
else
- LIBPL='$(prefix)'"/lib/python${VERSION}/config-${LDVERSION}-${PLATFORM_TRIPLET}"
+ LIBPL='$(prefix)'"/lib64/python${VERSION}/config-${LDVERSION}-${PLATFORM_TRIPLET}"
fi
--- a/configure.ac
+++ b/configure.ac
@@ -4722,9 +4722,9 @@ fi
dnl define LIBPL after ABIFLAGS and LDVERSION is defined.
AC_SUBST(PY_ENABLE_SHARED)
if test x$PLATFORM_TRIPLET = x; then
- LIBPL='$(prefix)'"/lib/python${VERSION}/config-${LDVERSION}"
+ LIBPL='$(prefix)'"/lib64/python${VERSION}/config-${LDVERSION}"
else
- LIBPL='$(prefix)'"/lib/python${VERSION}/config-${LDVERSION}-${PLATFORM_TRIPLET}"
+ LIBPL='$(prefix)'"/lib64/python${VERSION}/config-${LDVERSION}-${PLATFORM_TRIPLET}"
fi
AC_SUBST(LIBPL)
--- a/setup.py
+++ b/setup.py
@@ -634,7 +634,7 @@ class PyBuildExt(build_ext):
# directories (i.e. '.' and 'Include') must be first. See issue
# 10520.
if not CROSS_COMPILING:
- add_dir_to_list(self.compiler.library_dirs, '/usr/local/lib')
+ add_dir_to_list(self.compiler.library_dirs, '/usr/local/lib64')
add_dir_to_list(self.compiler.include_dirs, '/usr/local/include')
# only change this for cross builds for 3.3, issues on Mageia
if CROSS_COMPILING:
@@ -938,11 +938,11 @@ class PyBuildExt(build_ext):
elif curses_library:
readline_libs.append(curses_library)
elif self.compiler.find_library_file(self.lib_dirs +
- ['/usr/lib/termcap'],
+ ['/usr/lib64/termcap'],
'termcap'):
readline_libs.append('termcap')
self.add(Extension('readline', ['readline.c'],
- library_dirs=['/usr/lib/termcap'],
+ library_dirs=['/usr/lib64/termcap'],
extra_link_args=readline_extra_link_args,
libraries=readline_libs))
else:

View File

@ -0,0 +1,57 @@
From 910f38d9768d39d4d31426743ae4081ed1ab66b6 Mon Sep 17 00:00:00 2001
From: Michal Cyprian <m.cyprian@gmail.com>
Date: Mon, 26 Jun 2017 16:32:56 +0200
Subject: [PATCH] 00251: Change user install location
Set values of prefix and exec_prefix in distutils install command
to /usr/local if executable is /usr/bin/python* and RPM build
is not detected to make pip and distutils install into separate location.
Fedora Change: https://fedoraproject.org/wiki/Changes/Making_sudo_pip_safe
---
Lib/distutils/command/install.py | 15 +++++++++++++--
Lib/site.py | 9 ++++++++-
2 files changed, 21 insertions(+), 3 deletions(-)
--- a/Lib/distutils/command/install.py
+++ b/Lib/distutils/command/install.py
@@ -418,8 +418,19 @@ class install(Command):
raise DistutilsOptionError(
"must not supply exec-prefix without prefix")
- self.prefix = os.path.normpath(sys.prefix)
- self.exec_prefix = os.path.normpath(sys.exec_prefix)
+ # self.prefix is set to sys.prefix + /local/
+ # if neither RPM build nor virtual environment is
+ # detected to make pip and distutils install packages
+ # into the separate location.
+ if (not (hasattr(sys, 'real_prefix') or
+ sys.prefix != sys.base_prefix) and
+ 'RPM_BUILD_ROOT' not in os.environ):
+ addition = "/local"
+ else:
+ addition = ""
+
+ self.prefix = os.path.normpath(sys.prefix) + addition
+ self.exec_prefix = os.path.normpath(sys.exec_prefix) + addition
else:
if self.exec_prefix is None:
--- a/Lib/site.py
+++ b/Lib/site.py
@@ -357,7 +357,14 @@ def getsitepackages(prefixes=None):
return sitepackages
def addsitepackages(known_paths, prefixes=None):
- """Add site-packages to sys.path"""
+ """Add site-packages to sys.path
+
+ '/usr/local' is included in PREFIXES if RPM build is not detected
+ to make packages installed into this location visible.
+
+ """
+ if ENABLE_USER_SITE and 'RPM_BUILD_ROOT' not in os.environ:
+ PREFIXES.insert(0, "/usr/local")
for sitedir in getsitepackages(prefixes):
if os.path.isdir(sitedir):
addsitedir(sitedir, known_paths)

26
PACKAGING-NOTES Normal file
View File

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

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

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

16
Python-3.8.19.tar.xz.asc Normal file
View File

@ -0,0 +1,16 @@
-----BEGIN PGP SIGNATURE-----
iQIzBAABCgAdFiEE4/8oOcBIslwITevpsmmV4xAlBWgFAmX5t/gACgkQsmmV4xAl
BWgW0RAAkQYR6L3LNvuAg3OS/wD6Kouv3CnXeAwYY/BHglsHawtz+gM4jZRK8fIo
vEKBk6uoZBvXX1yJR+cxLZOxb9K/X7zYJXyBxRav8veBzXePTVhJBNSS/ckE0ARN
bD8M2P/7byMlm616aNNE1hrIIaxNoX8/yTEK3DmISQonc8vCW6ygIXm3Vw/6rqG8
n16MGG2r4dNEI+pEs8LPj8/VBaHHkbyvK9y2DQ8ywBqsaE459bN4HdzTkMxh28s0
scDl33PwTabFgVUTXILs+vBNnHc6ylo6gEd6fAe7Epec5wnvexKykel9ZtidxHwB
KQl2YKErJGF97T1Aj/Cru82jBYS/YS2QVy2cX0sYhiTgOXsvB7vOViFESR3IlSEL
aQv+f+lBXZp8T4MbDuzz2H7dqNY0sYqmTcqJU9r4H+RGLw43PHLSRVfIDPiaheA+
n1ZYzzgfm2uucO+iIpDwAOvTWznj4YcFwX116fn2kJYLtJeI58wVIbtMTDCl/l9U
hNY+b5L5JsHlyoRSjDwAtQVBm3fS0YqV4OhWglhvvuEdobRK+F3+hmHvo18YxZyl
WXLBUwZy9LQoEyuc1YFemWYw7g3u1ru8WTCFtPm91OeErkKq3QuqwiCjROgUmN9D
xUypHTocPhkdF1yEVqG+HMDin9Rw+l2KMgFt5XLNYFvAycGlsk4=
=Uo2Y
-----END PGP SIGNATURE-----

43
README.SUSE Normal file
View File

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

411
SUSE-FEDORA-multilib.patch Normal file
View File

@ -0,0 +1,411 @@
---
Lib/distutils/command/install.py | 18 +++++++++---------
Lib/distutils/sysconfig.py | 7 ++-----
Lib/site.py | 19 +++++++++----------
Lib/sysconfig.py | 12 ++++++------
Lib/test/test_embed.py | 10 +++++++---
Lib/test/test_site.py | 7 +++++--
Lib/test/test_sysconfig.py | 14 +++++++++++++-
Makefile.pre.in | 6 +++++-
Modules/getpath.c | 24 ++++++++++++------------
configure | 4 ++--
configure.ac | 18 ++++++++++++++++--
setup.py | 6 +++---
12 files changed, 89 insertions(+), 56 deletions(-)
--- a/Lib/distutils/command/install.py
+++ b/Lib/distutils/command/install.py
@@ -30,14 +30,14 @@ WINDOWS_SCHEME = {
INSTALL_SCHEMES = {
'unix_prefix': {
'purelib': '$base/lib/python$py_version_short/site-packages',
- 'platlib': '$platbase/lib64/python$py_version_short/site-packages',
+ 'platlib': '$platbase/$platsubdir/python$py_version_short/site-packages',
'headers': '$base/include/python$py_version_short$abiflags/$dist_name',
'scripts': '$base/bin',
'data' : '$base',
},
'unix_home': {
'purelib': '$base/lib/python',
- 'platlib': '$base/lib64/python',
+ 'platlib': '$base/lib/python',
'headers': '$base/include/python/$dist_name',
'scripts': '$base/bin',
'data' : '$base',
@@ -281,7 +281,7 @@ class install(Command):
# about needing recursive variable expansion (shudder).
py_version = sys.version.split()[0]
- (prefix, exec_prefix) = get_config_vars('prefix', 'exec_prefix')
+ (prefix, exec_prefix, platsubdir) = get_config_vars('prefix', 'exec_prefix', 'platsubdir')
try:
abiflags = sys.abiflags
except AttributeError:
@@ -298,6 +298,7 @@ class install(Command):
'sys_exec_prefix': exec_prefix,
'exec_prefix': exec_prefix,
'abiflags': abiflags,
+ 'platsubdir': platsubdir,
}
if HAS_USER_SITE:
@@ -419,12 +420,11 @@ class install(Command):
"must not supply exec-prefix without prefix")
# self.prefix is set to sys.prefix + /local/
- # if neither RPM build nor virtual environment is
- # detected to make pip and distutils install packages
- # into the separate location.
- if (not (hasattr(sys, 'real_prefix') or
- sys.prefix != sys.base_prefix) and
- 'RPM_BUILD_ROOT' not in os.environ):
+ # if the executable is /usr/bin/python* and RPM build
+ # is not detected to make pip and distutils install into
+ # the separate location.
+ if (sys.executable.startswith("/usr/bin/python")
+ and 'RPM_BUILD_ROOT' not in os.environ):
addition = "/local"
else:
addition = ""
--- a/Lib/distutils/sysconfig.py
+++ b/Lib/distutils/sysconfig.py
@@ -146,12 +146,9 @@ def get_python_lib(plat_specific=0, stan
prefix = plat_specific and EXEC_PREFIX or PREFIX
if os.name == "posix":
- if plat_specific or standard_lib:
- lib = "lib64"
- else:
- lib = "lib"
+ libdir = plat_specific and get_config_var("platsubdir") or "lib"
libpython = os.path.join(prefix,
- lib, "python" + get_python_version())
+ libdir, "python" + get_python_version())
if standard_lib:
return libpython
else:
--- a/Lib/site.py
+++ b/Lib/site.py
@@ -344,12 +344,18 @@ def getsitepackages(prefixes=None):
seen.add(prefix)
if os.sep == '/':
- sitepackages.append(os.path.join(prefix, "lib64",
+ from sysconfig import get_config_var
+ platsubdir = get_config_var("platsubdir")
+ sitepackages.append(os.path.join(prefix, platsubdir,
"python" + sys.version[:3],
"site-packages"))
- sitepackages.append(os.path.join(prefix, "lib",
+ sitepackages.append(os.path.join(prefix, platsubdir,
"python%d.%d" % sys.version_info[:2],
"site-packages"))
+ if platsubdir != "lib":
+ sitepackages.append(os.path.join(prefix, "lib",
+ "python%d.%d" % sys.version_info[:2],
+ "site-packages"))
else:
sitepackages.append(prefix)
sitepackages.append(os.path.join(prefix, "lib64", "site-packages"))
@@ -357,14 +363,7 @@ def getsitepackages(prefixes=None):
return sitepackages
def addsitepackages(known_paths, prefixes=None):
- """Add site-packages to sys.path
-
- '/usr/local' is included in PREFIXES if RPM build is not detected
- to make packages installed into this location visible.
-
- """
- if ENABLE_USER_SITE and 'RPM_BUILD_ROOT' not in os.environ:
- PREFIXES.insert(0, "/usr/local")
+ """Add site-packages to sys.path"""
for sitedir in getsitepackages(prefixes):
if os.path.isdir(sitedir):
addsitedir(sitedir, known_paths)
--- a/Lib/sysconfig.py
+++ b/Lib/sysconfig.py
@@ -25,10 +25,10 @@ _ALWAYS_STR = {
_INSTALL_SCHEMES = {
'posix_prefix': {
- 'stdlib': '{installed_base}/lib64/python{py_version_short}',
- 'platstdlib': '{platbase}/lib64/python{py_version_short}',
+ 'stdlib': '{installed_base}/{platsubdir}/python{py_version_short}',
+ 'platstdlib': '{platbase}/{platsubdir}/python{py_version_short}',
'purelib': '{base}/lib/python{py_version_short}/site-packages',
- 'platlib': '{platbase}/lib64/python{py_version_short}/site-packages',
+ 'platlib': '{platbase}/{platsubdir}/python{py_version_short}/site-packages',
'include':
'{installed_base}/include/python{py_version_short}{abiflags}',
'platinclude':
@@ -67,10 +67,10 @@ _INSTALL_SCHEMES = {
'data': '{userbase}',
},
'posix_user': {
- 'stdlib': '{userbase}/lib64/python{py_version_short}',
- 'platstdlib': '{userbase}/lib64/python{py_version_short}',
+ 'stdlib': '{userbase}/lib/python{py_version_short}',
+ 'platstdlib': '{userbase}/lib/python{py_version_short}',
'purelib': '{userbase}/lib/python{py_version_short}/site-packages',
- 'platlib': '{userbase}/lib64/python{py_version_short}/site-packages',
+ 'platlib': '{userbase}/lib/python{py_version_short}/site-packages',
'include': '{userbase}/include/python{py_version_short}',
'scripts': '{userbase}/bin',
'data': '{userbase}',
--- a/Lib/test/test_embed.py
+++ b/Lib/test/test_embed.py
@@ -10,6 +10,7 @@ import re
import shutil
import subprocess
import sys
+import sysconfig
import tempfile
import textwrap
@@ -1072,12 +1073,13 @@ class InitConfigTests(EmbeddingTestsMixi
return config['config']['module_search_paths']
else:
ver = sys.version_info
+ platsubdir = sysconfig.get_config_var('platsubdir')
return [
os.path.join(prefix, 'lib',
f'python{ver.major}{ver.minor}.zip'),
- os.path.join(prefix, 'lib',
+ os.path.join(prefix, platsubdir,
f'python{ver.major}.{ver.minor}'),
- os.path.join(exec_prefix, 'lib',
+ os.path.join(exec_prefix, platsubdir,
f'python{ver.major}.{ver.minor}', 'lib-dynload'),
]
@@ -1188,13 +1190,15 @@ class InitConfigTests(EmbeddingTestsMixi
def test_init_pyvenv_cfg(self):
# Test path configuration with pyvenv.cfg configuration file
+ platsubdir = sysconfig.get_config_var('platsubdir')
+
with self.tmpdir_with_python() as tmpdir, \
tempfile.TemporaryDirectory() as pyvenv_home:
ver = sys.version_info
if not MS_WINDOWS:
lib_dynload = os.path.join(pyvenv_home,
- 'lib',
+ platsubdir,
f'python{ver.major}.{ver.minor}',
'lib-dynload')
os.makedirs(lib_dynload)
--- a/Lib/test/test_site.py
+++ b/Lib/test/test_site.py
@@ -307,8 +307,11 @@ class HelperFunctionsTests(unittest.Test
dirs = site.getsitepackages()
if os.sep == '/':
# OS X, Linux, FreeBSD, etc
- self.assertEqual(len(dirs), 2)
- wanted = os.path.join('xoxo', 'lib64',
+ self.assertTrue(len(dirs) in (1,2,3),
+ "dirs = {} has len not in (1,2,3).".format(dirs))
+
+ platsubdir = sysconfig.get_config_var('platsubdir')
+ wanted = os.path.join('xoxo', platsubdir,
'python%d.%d' % sys.version_info[:2],
'site-packages')
self.assertEqual(dirs[0], wanted)
--- a/Lib/test/test_sysconfig.py
+++ b/Lib/test/test_sysconfig.py
@@ -243,6 +243,7 @@ class TestSysConfig(unittest.TestCase):
# is similar to the global posix_prefix one
base = get_config_var('base')
user = get_config_var('userbase')
+ platsubdir = get_config_var("platsubdir")
# the global scheme mirrors the distinction between prefix and
# exec-prefix but not the user scheme, so we have to adapt the paths
# before comparing (issue #9100)
@@ -257,8 +258,19 @@ class TestSysConfig(unittest.TestCase):
# before comparing
global_path = global_path.replace(sys.base_prefix, sys.prefix)
base = base.replace(sys.base_prefix, sys.prefix)
+
+ if platsubdir != "lib":
+ platbase = os.path.join(base, platsubdir)
+ purebase = os.path.join(base, "lib")
+ userlib = os.path.join(user, "lib")
+ # replace platbase first because usually purebase is a prefix of platbase
+ # /usr/lib is prefix of /usr/lib64 and would get replaced first
+ modified_path = global_path.replace(platbase, userlib, 1).replace(purebase, userlib, 1)
+ else:
+ modified_path = global_path.replace(base, user, 1)
+
user_path = get_path(name, 'posix_user')
- self.assertEqual(user_path, global_path.replace(base, user, 1))
+ self.assertEqual(user_path, modified_path)
def test_main(self):
# just making sure _main() runs and returns things in the stdout
--- a/Makefile.pre.in
+++ b/Makefile.pre.in
@@ -137,13 +137,16 @@ exec_prefix= @exec_prefix@
# Install prefix for data files
datarootdir= @datarootdir@
+# Name of "lib" directory under prefix
+platsubdir= @platsubdir@
+
# Expanded directories
BINDIR= @bindir@
LIBDIR= @libdir@
MANDIR= @mandir@
INCLUDEDIR= @includedir@
CONFINCLUDEDIR= $(exec_prefix)/include
-SCRIPTDIR= $(prefix)/lib64
+SCRIPTDIR= @libdir@
ABIFLAGS= @ABIFLAGS@
# Detailed destination directories
@@ -766,6 +769,7 @@ Modules/getpath.o: $(srcdir)/Modules/get
-DEXEC_PREFIX='"$(exec_prefix)"' \
-DVERSION='"$(VERSION)"' \
-DVPATH='"$(VPATH)"' \
+ -DPLATLIBDIR='"$(platsubdir)"' \
-o $@ $(srcdir)/Modules/getpath.c
Programs/python.o: $(srcdir)/Programs/python.c
--- a/Modules/getpath.c
+++ b/Modules/getpath.c
@@ -55,12 +55,12 @@
* pybuilddir.txt. If the landmark is found, we're done.
*
* For the remaining steps, the prefix landmark will always be
- * lib/python$VERSION/os.py and the exec_prefix will always be
- * lib/python$VERSION/lib-dynload, where $VERSION is Python's version
- * number as supplied by the Makefile. Note that this means that no more
- * build directory checking is performed; if the first step did not find
- * the landmarks, the assumption is that python is running from an
- * installed setup.
+ * $lib/python$VERSION/os.py and the exec_prefix will always be
+ * $lib/python$VERSION/lib-dynload, where $VERSION is Python's version
+ * number and $lib is PLATLIBDIR as supplied by the Makefile. Note that
+ * this means that no more build directory checking is performed; if the
+ * first step did not find the landmarks, the assumption is that python
+ * is running from an installed setup.
*
* Step 2. See if the $PYTHONHOME environment variable points to the
* installed location of the Python libraries. If $PYTHONHOME is set, then
@@ -86,7 +86,7 @@
* containing the shared library modules is appended. The environment
* variable $PYTHONPATH is inserted in front of it all. Finally, the
* prefix and exec_prefix globals are tweaked so they reflect the values
- * expected by other code, by stripping the "lib/python$VERSION/..." stuff
+ * expected by other code, by stripping the "$lib/python$VERSION/..." stuff
* off. If either points to the build directory, the globals are reset to
* the corresponding preprocessor variables (so sys.prefix will reflect the
* installation location, even though sys.path points into the build
@@ -105,8 +105,8 @@ extern "C" {
#endif
-#if !defined(PREFIX) || !defined(EXEC_PREFIX) || !defined(VERSION) || !defined(VPATH)
-#error "PREFIX, EXEC_PREFIX, VERSION, and VPATH must be constant defined"
+#if !defined(PREFIX) || !defined(EXEC_PREFIX) || !defined(VERSION) || !defined(VPATH) || !defined(PLATLIBDIR)
+#error "PREFIX, EXEC_PREFIX, VERSION, VPATH, and PLATLIBDIR must be constant defined"
#endif
#ifndef LANDMARK
@@ -730,7 +730,7 @@ calculate_exec_prefix(PyCalculatePath *c
if (safe_wcscpy(exec_prefix, calculate->exec_prefix, exec_prefix_len) < 0) {
return PATHLEN_ERR();
}
- status = joinpath(exec_prefix, L"lib64/lib-dynload", exec_prefix_len);
+ status = joinpath(exec_prefix, L"lib/lib-dynload", exec_prefix_len);
if (_PyStatus_EXCEPTION(status)) {
return status;
}
@@ -1063,7 +1063,7 @@ calculate_zip_path(PyCalculatePath *calc
return PATHLEN_ERR();
}
}
- status = joinpath(zip_path, L"lib64/python00.zip", zip_path_len);
+ status = joinpath(zip_path, L"lib/python00.zip", zip_path_len);
if (_PyStatus_EXCEPTION(status)) {
return status;
}
@@ -1193,7 +1193,7 @@ calculate_init(PyCalculatePath *calculat
if (!calculate->exec_prefix) {
return DECODE_LOCALE_ERR("EXEC_PREFIX define", len);
}
- calculate->lib_python = Py_DecodeLocale("lib64/python" VERSION, &len);
+ calculate->lib_python = Py_DecodeLocale(PLATLIBDIR "/python" VERSION, &len);
if (!calculate->lib_python) {
return DECODE_LOCALE_ERR("EXEC_PREFIX define", len);
}
--- a/configure
+++ b/configure
@@ -15276,9 +15276,9 @@ fi
if test x$PLATFORM_TRIPLET = x; then
- LIBPL='$(prefix)'"/lib64/python${VERSION}/config-${LDVERSION}"
+ LIBPL='$(prefix)'"/${platsubdir}/python${VERSION}/config-${LDVERSION}"
else
- LIBPL='$(prefix)'"/lib64/python${VERSION}/config-${LDVERSION}-${PLATFORM_TRIPLET}"
+ LIBPL='$(prefix)'"/${platsubdir}/python${VERSION}/config-${LDVERSION}-${PLATFORM_TRIPLET}"
fi
--- a/configure.ac
+++ b/configure.ac
@@ -4719,12 +4719,26 @@ else
LIBPYTHON=''
fi
+# platsubdir must be defined before LIBPL definition
+AC_MSG_CHECKING(for custom platsubdir)
+AC_ARG_WITH(custom-platsubdir,
+ [AS_HELP_STRING([--with-custom-platsubdir=<libdirname>],
+ [set the platsubdir name to a custom string])],
+ [],
+ [with_custom_platsubdir=yes])
+AS_CASE($with_custom_platsubdir,
+ [yes],[platsubdir=`basename ${libdir}`],
+ [no],[platsubdir=lib],
+ [platsubdir=$with_custom_platsubdir])
+AC_MSG_RESULT($platsubdir)
+AC_SUBST(platsubdir)
+
dnl define LIBPL after ABIFLAGS and LDVERSION is defined.
AC_SUBST(PY_ENABLE_SHARED)
if test x$PLATFORM_TRIPLET = x; then
- LIBPL='$(prefix)'"/lib64/python${VERSION}/config-${LDVERSION}"
+ LIBPL='$(prefix)'"/${platsubdir}/python${VERSION}/config-${LDVERSION}"
else
- LIBPL='$(prefix)'"/lib64/python${VERSION}/config-${LDVERSION}-${PLATFORM_TRIPLET}"
+ LIBPL='$(prefix)'"/${platsubdir}/python${VERSION}/config-${LDVERSION}-${PLATFORM_TRIPLET}"
fi
AC_SUBST(LIBPL)
--- a/setup.py
+++ b/setup.py
@@ -634,7 +634,7 @@ class PyBuildExt(build_ext):
# directories (i.e. '.' and 'Include') must be first. See issue
# 10520.
if not CROSS_COMPILING:
- add_dir_to_list(self.compiler.library_dirs, '/usr/local/lib64')
+ add_dir_to_list(self.compiler.library_dirs, '/usr/local/lib')
add_dir_to_list(self.compiler.include_dirs, '/usr/local/include')
# only change this for cross builds for 3.3, issues on Mageia
if CROSS_COMPILING:
@@ -938,11 +938,11 @@ class PyBuildExt(build_ext):
elif curses_library:
readline_libs.append(curses_library)
elif self.compiler.find_library_file(self.lib_dirs +
- ['/usr/lib64/termcap'],
+ ['/usr/lib/termcap'],
'termcap'):
readline_libs.append('termcap')
self.add(Extension('readline', ['readline.c'],
- library_dirs=['/usr/lib64/termcap'],
+ library_dirs=['/usr/lib/termcap'],
extra_link_args=readline_extra_link_args,
libraries=readline_libs))
else:

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 @@
python38-base
python38
libpython3_8-1_0

View File

@ -0,0 +1,163 @@
From 5754521af1d51aa8e445cba07a093bbc0c88596d Mon Sep 17 00:00:00 2001
From: Zackery Spytz <zspytz@gmail.com>
Date: Mon, 16 Dec 2019 18:24:08 -0700
Subject: [PATCH] bpo-31046: ensurepip does not honour the value of $(prefix)
Co-Authored-By: Xavier de Gaye <xdegaye@gmail.com>
---
Doc/library/ensurepip.rst | 9 +++--
Lib/ensurepip/__init__.py | 18 +++++++---
Lib/test/test_ensurepip.py | 11 ++++++
Makefile.pre.in | 4 +-
Misc/NEWS.d/next/Build/2019-12-16-17-50-42.bpo-31046.XA-Qfr.rst | 1
5 files changed, 34 insertions(+), 9 deletions(-)
create mode 100644 Misc/NEWS.d/next/Build/2019-12-16-17-50-42.bpo-31046.XA-Qfr.rst
--- a/Doc/library/ensurepip.rst
+++ b/Doc/library/ensurepip.rst
@@ -56,8 +56,9 @@ is at least as recent as the one bundled
By default, ``pip`` is installed into the current virtual environment
(if one is active) or into the system site packages (if there is no
active virtual environment). The installation location can be controlled
-through two additional command line options:
+through some additional command line options:
+* ``--prefix <dir>``: Installs ``pip`` using the given directory prefix.
* ``--root <dir>``: Installs ``pip`` relative to the given root directory
rather than the root of the currently active virtual environment (if any)
or the default root for the current Python installation.
@@ -89,7 +90,7 @@ Module API
Returns a string specifying the bundled 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)
@@ -99,6 +100,8 @@ Module API
If *root* is ``None``, then installation uses the default install location
for the current environment.
+ *prefix* specifies the directory prefix to use when installing.
+
*upgrade* indicates whether or not to upgrade an existing installation
of an earlier version of ``pip`` to the bundled version.
@@ -119,6 +122,8 @@ Module API
*verbosity* controls the level of output to :data:`sys.stdout` from the
bootstrapping operation.
+ .. versionchanged:: 3.9 the *prefix* parameter was added.
+
.. audit-event:: ensurepip.bootstrap root ensurepip.bootstrap
.. note::
--- a/Lib/ensurepip/__init__.py
+++ b/Lib/ensurepip/__init__.py
@@ -55,27 +55,27 @@ def _disable_pip_configuration_settings(
os.environ['PIP_CONFIG_FILE'] = os.devnull
-def bootstrap(*, root=None, upgrade=False, user=False,
+def bootstrap(*, root=None, prefix=None, upgrade=False, user=False,
altinstall=False, default_pip=False,
verbosity=0):
"""
Bootstrap pip into the current Python installation (or the given root
- directory).
+ and directory prefix).
Note that calling this function will alter both sys.path and os.environ.
"""
# Discard the return value
- _bootstrap(root=root, upgrade=upgrade, user=user,
+ _bootstrap(root=root, prefix=prefix, upgrade=upgrade, user=user,
altinstall=altinstall, default_pip=default_pip,
verbosity=verbosity)
-def _bootstrap(*, root=None, upgrade=False, user=False,
+def _bootstrap(*, root=None, prefix=None, upgrade=False, user=False,
altinstall=False, default_pip=False,
verbosity=0):
"""
Bootstrap pip into the current Python installation (or the given root
- directory). Returns pip command status code.
+ and directory prefix). Returns pip command status code.
Note that calling this function will alter both sys.path and os.environ.
"""
@@ -118,6 +118,8 @@ def _bootstrap(*, root=None, upgrade=Fal
args = ["install", "--no-cache-dir", "--no-index", "--find-links", tmpdir]
if root:
args += ["--root", root]
+ if prefix:
+ args += ["--prefix", prefix]
if upgrade:
args += ["--upgrade"]
if user:
@@ -190,6 +192,11 @@ def _main(argv=None):
help="Install everything relative to this alternate root directory.",
)
parser.add_argument(
+ "--prefix",
+ default=None,
+ help="Install everything using this prefix.",
+ )
+ parser.add_argument(
"--altinstall",
action="store_true",
default=False,
@@ -208,6 +215,7 @@ def _main(argv=None):
return _bootstrap(
root=args.root,
+ prefix=args.prefix,
upgrade=args.upgrade,
user=args.user,
verbosity=args.verbosity,
--- a/Lib/test/test_ensurepip.py
+++ b/Lib/test/test_ensurepip.py
@@ -61,6 +61,17 @@ class TestBootstrap(EnsurepipMixin, unit
unittest.mock.ANY,
)
+ def test_bootstrapping_with_prefix(self):
+ ensurepip.bootstrap(prefix="/foo/bar/")
+ self.run_pip.assert_called_once_with(
+ [
+ "install", "--no-cache-dir", "--no-index", "--find-links",
+ unittest.mock.ANY, "--prefix", "/foo/bar/",
+ "setuptools", "pip",
+ ],
+ unittest.mock.ANY,
+ )
+
def test_bootstrapping_with_user(self):
ensurepip.bootstrap(user=True)
--- a/Makefile.pre.in
+++ b/Makefile.pre.in
@@ -1200,7 +1200,7 @@ install: @FRAMEWORKINSTALLFIRST@ commoni
install|*) ensurepip="" ;; \
esac; \
$(RUNSHARED) $(PYTHON_FOR_BUILD) -m ensurepip \
- $$ensurepip --root=$(DESTDIR)/ ; \
+ $$ensurepip --root=$(DESTDIR)/ --prefix=$(prefix) ; \
fi
altinstall: commoninstall
@@ -1210,7 +1210,7 @@ altinstall: commoninstall
install|*) ensurepip="--altinstall" ;; \
esac; \
$(RUNSHARED) $(PYTHON_FOR_BUILD) -m ensurepip \
- $$ensurepip --root=$(DESTDIR)/ ; \
+ $$ensurepip --root=$(DESTDIR)/ --prefix=$(prefix) ; \
fi
commoninstall: check-clean-src @FRAMEWORKALTINSTALLFIRST@ \
--- /dev/null
+++ b/Misc/NEWS.d/next/Build/2019-12-16-17-50-42.bpo-31046.XA-Qfr.rst
@@ -0,0 +1 @@
+A directory prefix can now be specified when using :mod:`ensurepip`.

View File

@ -0,0 +1,152 @@
From 2c096b513273a758b446405d9e5efe4860af1036 Mon Sep 17 00:00:00 2001
From: Elvis Pranskevichus <elvis@magic.io>
Date: Thu, 27 Sep 2018 13:05:14 -0400
Subject: [PATCH] bpo-34022: Stop forcing of hash-based invalidation with
SOURCE_DATE_EPOCH
Unconditional forcing of ``CHECKED_HASH`` invalidation was introduced in
3.7.0 in bpo-29708. The change is bad, as it unconditionally overrides
*invalidation_mode*, even if it was passed as an explicit argument to
``py_compile.compile()`` or ``compileall``. An environment variable
should *never* override an explicit argument to a library function.
That change leads to multiple test failures if the ``SOURCE_DATE_EPOCH``
environment variable is set.
This changes ``py_compile.compile()`` to only look at
``SOURCE_DATE_EPOCH`` if no explicit *invalidation_mode* was specified.
I also made various relevant tests run with explicit control over the
value of ``SOURCE_DATE_EPOCH``.
While looking at this, I noticed that ``zipimport`` does not work
with hash-based .pycs _at all_, though I left the fixes for
subsequent commits.
---
Doc/library/compileall.rst | 11 ++--
Doc/library/py_compile.rst | 13 ++--
Lib/compileall.py | 20 ++++--
Lib/py_compile.py | 13 +++-
Lib/test/test_compileall.py | 50 ++++++++++++--
.../test_importlib/source/test_file_loader.py | 15 +++++
Lib/test/test_py_compile.py | 66 +++++++++++++++++--
.../2018-09-27-13-14-15.bpo-34022.E2cl0r.rst | 3 +
8 files changed, 161 insertions(+), 30 deletions(-)
create mode 100644 Misc/NEWS.d/next/Library/2018-09-27-13-14-15.bpo-34022.E2cl0r.rst
--- a/Doc/library/py_compile.rst
+++ b/Doc/library/py_compile.rst
@@ -92,6 +92,11 @@ byte-code cache files in the directory c
.. versionchanged:: 3.8
The *quiet* parameter was added.
+ .. versionchanged:: 3.7.2
+ The :envvar:`SOURCE_DATE_EPOCH` environment variable no longer
+ overrides the value of the *invalidation_mode* argument, and determines
+ its default value instead.
+
.. class:: PycInvalidationMode
--- a/Lib/test/test_compileall.py
+++ b/Lib/test/test_compileall.py
@@ -209,6 +209,21 @@ class CompileallTestsWithoutSourceEpoch(
pass
+
+class CompileallTestsWithSourceEpoch(CompileallTestsBase,
+ unittest.TestCase,
+ metaclass=SourceDateEpochTestMeta,
+ source_date_epoch=True):
+ pass
+
+
+class CompileallTestsWithoutSourceEpoch(CompileallTestsBase,
+ unittest.TestCase,
+ metaclass=SourceDateEpochTestMeta,
+ source_date_epoch=False):
+ pass
+
+
class EncodingTest(unittest.TestCase):
"""Issue 6716: compileall should escape source code when printing errors
to stdout."""
@@ -620,6 +635,21 @@ class CommandLineTestsBase:
class CommmandLineTestsWithSourceEpoch(CommandLineTestsBase,
+ unittest.TestCase,
+ metaclass=SourceDateEpochTestMeta,
+ source_date_epoch=True):
+ pass
+
+
+class CommmandLineTestsNoSourceEpoch(CommandLineTestsBase,
+ unittest.TestCase,
+ metaclass=SourceDateEpochTestMeta,
+ source_date_epoch=False):
+ pass
+
+
+
+class CommmandLineTestsWithSourceEpoch(CommandLineTestsBase,
unittest.TestCase,
metaclass=SourceDateEpochTestMeta,
source_date_epoch=True):
--- a/Lib/test/test_importlib/source/test_file_loader.py
+++ b/Lib/test/test_importlib/source/test_file_loader.py
@@ -22,6 +22,9 @@ from test.support import make_legacy_pyc
from test.test_py_compile import without_source_date_epoch
from test.test_py_compile import SourceDateEpochTestMeta
+from test.test_py_compile import without_source_date_epoch
+from test.test_py_compile import SourceDateEpochTestMeta
+
class SimpleTest(abc.LoaderTests):
@@ -363,6 +366,17 @@ class SimpleTest(abc.LoaderTests):
class SourceDateEpochTestMeta(SourceDateEpochTestMeta,
+ type(Source_SimpleTest)):
+ pass
+
+
+class SourceDateEpoch_SimpleTest(Source_SimpleTest,
+ metaclass=SourceDateEpochTestMeta,
+ source_date_epoch=True):
+ pass
+
+
+class SourceDateEpochTestMeta(SourceDateEpochTestMeta,
type(Source_SimpleTest)):
pass
--- a/Lib/test/test_py_compile.py
+++ b/Lib/test/test_py_compile.py
@@ -272,5 +272,19 @@ class PyCompileCLITestCase(unittest.Test
self.assertIn(b'No such file or directory', stderr)
+class PyCompileTestsWithSourceEpoch(PyCompileTestsBase,
+ unittest.TestCase,
+ metaclass=SourceDateEpochTestMeta,
+ source_date_epoch=True):
+ pass
+
+
+class PyCompileTestsWithoutSourceEpoch(PyCompileTestsBase,
+ unittest.TestCase,
+ metaclass=SourceDateEpochTestMeta,
+ source_date_epoch=False):
+ pass
+
+
if __name__ == "__main__":
unittest.main()
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2018-09-27-13-14-15.bpo-34022.E2cl0r.rst
@@ -0,0 +1,3 @@
+The :envvar:`SOURCE_DATE_EPOCH` environment variable no longer overrides the
+value of the *invalidation_mode* argument to :func:`py_compile.compile`, and
+determines its default value instead.

View File

@ -0,0 +1,115 @@
From 9d3b6b2472f7c7ef841e652825de652bc8af85d7 Mon Sep 17 00:00:00 2001
From: "Miss Islington (bot)"
<31488909+miss-islington@users.noreply.github.com>
Date: Tue, 24 Aug 2021 08:07:31 -0700
Subject: [PATCH] [3.9] bpo-34990: Treat the pyc header's mtime in compileall
as an unsigned int (GH-19708)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
(cherry picked from commit bb21e28fd08f894ceff2405544a2f257d42b1354)
Co-authored-by: Ammar Askar <ammar@ammaraskar.com>
Co-authored-by: Stéphane Wirtel <stephane@wirtel.be>
---
Lib/compileall.py | 4 -
Lib/test/test_compileall.py | 23 +++++++++-
Lib/test/test_zipimport.py | 17 ++++---
Misc/NEWS.d/next/Library/2020-04-24-20-39-38.bpo-34990.3SmL9M.rst | 2
4 files changed, 35 insertions(+), 11 deletions(-)
create mode 100644 Misc/NEWS.d/next/Library/2020-04-24-20-39-38.bpo-34990.3SmL9M.rst
--- a/Lib/compileall.py
+++ b/Lib/compileall.py
@@ -148,8 +148,8 @@ def compile_file(fullname, ddir=None, fo
if not force:
try:
mtime = int(os.stat(fullname).st_mtime)
- expect = struct.pack('<4sll', importlib.util.MAGIC_NUMBER,
- 0, mtime)
+ expect = struct.pack('<4sLL', importlib.util.MAGIC_NUMBER,
+ 0, mtime & 0xFFFF_FFFF)
with open(cfile, 'rb') as chandle:
actual = chandle.read(12)
if expect == actual:
--- a/Lib/test/test_compileall.py
+++ b/Lib/test/test_compileall.py
@@ -54,9 +54,28 @@ class CompileallTestsBase:
with open(self.bc_path, 'rb') as file:
data = file.read(12)
mtime = int(os.stat(self.source_path).st_mtime)
- compare = struct.pack('<4sll', importlib.util.MAGIC_NUMBER, 0, mtime)
+ compare = struct.pack('<4sLL', importlib.util.MAGIC_NUMBER, 0,
+ mtime & 0xFFFF_FFFF)
return data, compare
+ def test_year_2038_mtime_compilation(self):
+ # Test to make sure we can handle mtimes larger than what a 32-bit
+ # signed number can hold as part of bpo-34990
+ try:
+ os.utime(self.source_path, (2**32 - 1, 2**32 - 1))
+ except (OverflowError, OSError):
+ self.skipTest("filesystem doesn't support timestamps near 2**32")
+ self.assertTrue(compileall.compile_file(self.source_path))
+
+ def test_larger_than_32_bit_times(self):
+ # This is similar to the test above but we skip it if the OS doesn't
+ # support modification times larger than 32-bits.
+ try:
+ os.utime(self.source_path, (2**35, 2**35))
+ except (OverflowError, OSError):
+ self.skipTest("filesystem doesn't support large timestamps")
+ self.assertTrue(compileall.compile_file(self.source_path))
+
def recreation_check(self, metadata):
"""Check that compileall recreates bytecode when the new metadata is
used."""
@@ -75,7 +94,7 @@ class CompileallTestsBase:
def test_mtime(self):
# Test a change in mtime leads to a new .pyc.
- self.recreation_check(struct.pack('<4sll', importlib.util.MAGIC_NUMBER,
+ self.recreation_check(struct.pack('<4sLL', importlib.util.MAGIC_NUMBER,
0, 1))
def test_magic_number(self):
--- a/Lib/test/test_zipimport.py
+++ b/Lib/test/test_zipimport.py
@@ -34,14 +34,9 @@ raise_src = 'def do_raise(): raise TypeE
def make_pyc(co, mtime, size):
data = marshal.dumps(co)
- if type(mtime) is type(0.0):
- # Mac mtimes need a bit of special casing
- if mtime < 0x7fffffff:
- mtime = int(mtime)
- else:
- mtime = int(-0x100000000 + int(mtime))
pyc = (importlib.util.MAGIC_NUMBER +
- struct.pack("<iii", 0, int(mtime), size & 0xFFFFFFFF) + data)
+ struct.pack("<iLL", 0,
+ int(mtime) & 0xFFFF_FFFF, size & 0xFFFF_FFFF) + data)
return pyc
def module_path_to_dotted_name(path):
@@ -253,6 +248,14 @@ class UncompressedZipImportTestCase(Impo
TESTMOD + pyc_ext: (NOW, badtime_pyc)}
self.doTest(".py", files, TESTMOD)
+ def test2038MTime(self):
+ # Make sure we can handle mtimes larger than what a 32-bit signed number
+ # can hold.
+ twenty_thirty_eight_pyc = make_pyc(test_co, 2**32 - 1, len(test_src))
+ files = {TESTMOD + ".py": (NOW, test_src),
+ TESTMOD + pyc_ext: (NOW, twenty_thirty_eight_pyc)}
+ self.doTest(".py", files, TESTMOD)
+
def testPackage(self):
packdir = TESTPACK + os.sep
files = {packdir + "__init__" + pyc_ext: (NOW, test_pyc),
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2020-04-24-20-39-38.bpo-34990.3SmL9M.rst
@@ -0,0 +1,2 @@
+Fixed a Y2k38 bug in the compileall module where it would fail to compile
+files with a modification time after the year 2038.

View File

@ -0,0 +1,49 @@
From ca04974425c84f306ddcebe88d6b31442e34e89d Mon Sep 17 00:00:00 2001
From: "Bernhard M. Wiedemann" <bwiedemann@suse.de>
Date: Mon, 5 Jun 2017 17:33:33 +0200
Subject: [PATCH] bpo-36302: Sort list of sources
when building packages (e.g. for openSUSE Linux)
(random) filesystem order of input files
influences ordering of functions in the output .so files.
Thus without the patch, builds (in disposable VMs) would usually differ.
Without this patch, all callers have to be patched individually
https://github.com/dugsong/libdnet/pull/42
https://github.com/sass/libsass-python/pull/212
https://github.com/tahoe-lafs/pycryptopp/pull/41
https://github.com/yt-project/yt/pull/2206
https://github.com/pyproj4/pyproj/pull/142
https://github.com/pytries/datrie/pull/49
https://github.com/Roche/pyreadstat/pull/37
but that is an infinite effort.
See https://reproducible-builds.org/ for why this matters.
---
Lib/distutils/command/build_ext.py | 3 ++-
.../next/Library/2019-03-21-19-23-46.bpo-36302.Yc591g.rst | 2 ++
2 files changed, 4 insertions(+), 1 deletion(-)
create mode 100644 Misc/NEWS.d/next/Library/2019-03-21-19-23-46.bpo-36302.Yc591g.rst
diff --git a/Lib/distutils/command/build_ext.py b/Lib/distutils/command/build_ext.py
index 2d7cdf063f01..38bb8fd93c27 100644
--- a/Lib/distutils/command/build_ext.py
+++ b/Lib/distutils/command/build_ext.py
@@ -490,7 +490,8 @@ def build_extension(self, ext):
"in 'ext_modules' option (extension '%s'), "
"'sources' must be present and must be "
"a list of source filenames" % ext.name)
- sources = list(sources)
+ # sort to make the resulting .so file build reproducible
+ sources = sorted(sources)
ext_path = self.get_ext_fullpath(ext.name)
depends = sources + ext.depends
diff --git a/Misc/NEWS.d/next/Library/2019-03-21-19-23-46.bpo-36302.Yc591g.rst b/Misc/NEWS.d/next/Library/2019-03-21-19-23-46.bpo-36302.Yc591g.rst
new file mode 100644
index 000000000000..fe01b5915d5d
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2019-03-21-19-23-46.bpo-36302.Yc591g.rst
@@ -0,0 +1,2 @@
+distutils sorts source file lists so that Extension .so files
+build more reproducibly by default

View File

@ -0,0 +1,44 @@
From 29b463879b71a3ade67541c34daafb2929269fc4 Mon Sep 17 00:00:00 2001
From: Mark Dickinson <mdickinson@enthought.com>
Date: Wed, 16 Jun 2021 18:43:49 +0100
Subject: [PATCH] bpo-44426: Use of 'complex' as a C variable name confuses
Sphinx; change it to 'num'. (GH-26744) (cherry picked from commit
7247f6f433846c6e37308a550e8e5eb6be379856)
Co-authored-by: Mark Dickinson <mdickinson@enthought.com>
---
Doc/c-api/complex.rst | 4 ++--
Doc/c-api/object.rst | 4 ++--
2 files changed, 4 insertions(+), 4 deletions(-)
--- a/Doc/c-api/complex.rst
+++ b/Doc/c-api/complex.rst
@@ -46,9 +46,9 @@ pointers. This is consistent throughout
:c:type:`Py_complex` representation.
-.. c:function:: Py_complex _Py_c_neg(Py_complex complex)
+.. c:function:: Py_complex _Py_c_neg(Py_complex num)
- Return the negation of the complex number *complex*, using the C
+ Return the negation of the complex number *num*, using the C
:c:type:`Py_complex` representation.
--- a/Doc/c-api/object.rst
+++ b/Doc/c-api/object.rst
@@ -483,12 +483,12 @@ Object Protocol
returned. This is the equivalent to the Python expression ``len(o)``.
-.. c:function:: Py_ssize_t PyObject_LengthHint(PyObject *o, Py_ssize_t default)
+.. c:function:: Py_ssize_t PyObject_LengthHint(PyObject *o, Py_ssize_t def_size)
Return an estimated length for the object *o*. First try to return its
actual length, then an estimate using :meth:`~object.__length_hint__`, and
finally return the default value. On error return ``-1``. This is the
- equivalent to the Python expression ``operator.length_hint(o, default)``.
+ equivalent to the Python expression ``operator.length_hint(o, def_size)``.
.. versionadded:: 3.4

36
decimal-3.8.patch Normal file
View File

@ -0,0 +1,36 @@
---
Modules/_decimal/_decimal.c | 4 ++--
setup.py | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
--- a/Modules/_decimal/_decimal.c
+++ b/Modules/_decimal/_decimal.c
@@ -3284,7 +3284,7 @@ dec_format(PyObject *dec, PyObject *args
}
else {
size_t n = strlen(spec.dot);
- if (n > 1 || (n == 1 && !isascii((uchar)spec.dot[0]))) {
+ if (n > 1 || (n == 1 && !isascii((unsigned char)spec.dot[0]))) {
/* fix locale dependent non-ascii characters */
dot = dotsep_as_utf8(spec.dot);
if (dot == NULL) {
@@ -3293,7 +3293,7 @@ dec_format(PyObject *dec, PyObject *args
spec.dot = PyBytes_AS_STRING(dot);
}
n = strlen(spec.sep);
- if (n > 1 || (n == 1 && !isascii((uchar)spec.sep[0]))) {
+ if (n > 1 || (n == 1 && !isascii((unsigned char)spec.sep[0]))) {
/* fix locale dependent non-ascii characters */
sep = dotsep_as_utf8(spec.sep);
if (sep == NULL) {
--- a/setup.py
+++ b/setup.py
@@ -2055,7 +2055,7 @@ class PyBuildExt(build_ext):
undef_macros = []
if '--with-system-libmpdec' in sysconfig.get_config_var("CONFIG_ARGS"):
include_dirs = []
- libraries = [':libmpdec.so.2']
+ libraries = ['mpdec']
sources = ['_decimal/_decimal.c']
depends = ['_decimal/docstrings.h']
else:

View File

@ -0,0 +1,11 @@
--- a/Lib/distutils/util.py
+++ b/Lib/distutils/util.py
@@ -432,7 +432,7 @@ byte_compile(files, optimize=%r, force=%
else:
from py_compile import compile
- for file in py_files:
+ for file in sorted(py_files):
if file[-3:] != ".py":
# This lets us be lazy and not filter filenames in
# the "install_lib" command.

35
idle3.appdata.xml Normal file
View File

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Copyright 2017 Zbigniew Jędrzejewski-Szmek -->
<application>
<id type="desktop">idle3.desktop</id>
<name>IDLE3</name>
<metadata_licence>CC0</metadata_licence>
<project_license>Python-2.0</project_license>
<summary>Python 3 Integrated Development and Learning Environment</summary>
<description>
<p>
IDLE is Pythons Integrated Development and Learning Environment.
The GUI is uniform between Windows, Unix, and Mac OS X.
IDLE provides an easy way to start writing, running, and debugging
Python code.
</p>
<p>
IDLE is written in pure Python, and uses the tkinter GUI toolkit.
It provides:
</p>
<ul>
<li>a Python shell window (interactive interpreter) with colorizing of code input, output, and error messages,</li>
<li>a multi-window text editor with multiple undo, Python colorizing, smart indent, call tips, auto completion, and other features,</li>
<li>search within any window, replace within editor windows, and search through multiple files (grep),</li>
<li>a debugger with persistent breakpoints, stepping, and viewing of global and local namespaces.</li>
</ul>
</description>
<url type="homepage">https://docs.python.org/3/library/idle.html</url>
<screenshots>
<screenshot type="default">http://in.waw.pl/~zbyszek/fedora/idle3-appdata/idle3-main-window.png</screenshot>
<screenshot>http://in.waw.pl/~zbyszek/fedora/idle3-appdata/idle3-class-browser.png</screenshot>
<screenshot>http://in.waw.pl/~zbyszek/fedora/idle3-appdata/idle3-code-viewer.png</screenshot>
</screenshots>
<update_contact>zbyszek@in.waw.pl</update_contact>
</application>

12
idle3.desktop Normal file
View File

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

7
import_failed.map Normal file
View File

@ -0,0 +1,7 @@
python38-curses: curses _curses _curses_panel
python38-dbm: dbm _dbm _gdbm
python38-idle: idlelib
python38-testsuite: test _ctypes_test _testbuffer _testcapi _testinternalcapi _testimportmultiple _testmultiphase xxlimited
python38-tk: tkinter _tkinter
python38-tools: turtledemo
python38: 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.""")

30
macros.python3 Normal file
View File

@ -0,0 +1,30 @@
# macros for the primary python3 providing python flavor
%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\
}
%python3_default %(s=$(rpm -qf /usr/bin/python3); echo ${s%%%%-*})
%default_python3 %(s=$(rpm -qf /usr/bin/python3); echo ${s%%%%-*})

79
old-libexpat.patch Normal file
View File

@ -0,0 +1,79 @@
---
Lib/test/test_sax.py | 10 +++++-----
Lib/test/test_xml_etree.py | 17 ++++++++---------
2 files changed, 13 insertions(+), 14 deletions(-)
--- a/Lib/test/test_sax.py
+++ b/Lib/test/test_sax.py
@@ -1207,10 +1207,9 @@ class ExpatReaderTest(XmlTestBase):
self.assertEqual(result.getvalue(), start + b"<doc>text</doc>")
+ @unittest.skipIf(pyexpat.version_info < (2, 6, 0),
+ "Reparse deferral not defined for libexpat < 2.6.0")
def test_flush_reparse_deferral_enabled(self):
- if pyexpat.version_info < (2, 6, 0):
- self.skipTest(f'Expat {pyexpat.version_info} does not support reparse deferral')
-
result = BytesIO()
xmlgen = XMLGenerator(result)
parser = create_parser()
@@ -1232,6 +1231,8 @@ class ExpatReaderTest(XmlTestBase):
self.assertEqual(result.getvalue(), start + b"<doc></doc>")
+ @unittest.skipIf(pyexpat.version_info < (2, 6, 0),
+ "Reparse deferral not defined for libexpat < 2.6.0")
def test_flush_reparse_deferral_disabled(self):
result = BytesIO()
xmlgen = XMLGenerator(result)
@@ -1241,8 +1242,7 @@ class ExpatReaderTest(XmlTestBase):
for chunk in ("<doc", ">"):
parser.feed(chunk)
- if pyexpat.version_info >= (2, 6, 0):
- parser._parser.SetReparseDeferralEnabled(False)
+ parser._parser.SetReparseDeferralEnabled(False)
self.assertEqual(result.getvalue(), start) # i.e. no elements started
self.assertFalse(parser._parser.GetReparseDeferralEnabled())
--- a/Lib/test/test_xml_etree.py
+++ b/Lib/test/test_xml_etree.py
@@ -1494,11 +1494,9 @@ class XMLPullParserTest(unittest.TestCas
with self.assertRaises(ValueError):
ET.XMLPullParser(events=('start', 'end', 'bogus'))
+ @unittest.skipIf(pyexpat.version_info < (2, 6, 0),
+ "Reparse deferral not defined for libexpat < 2.6.0")
def test_flush_reparse_deferral_enabled(self):
- if pyexpat.version_info < (2, 6, 0):
- self.skipTest(f'Expat {pyexpat.version_info} does not '
- 'support reparse deferral')
-
parser = ET.XMLPullParser(events=('start', 'end'))
for chunk in ("<doc", ">"):
@@ -1519,17 +1517,18 @@ class XMLPullParserTest(unittest.TestCas
self.assert_event_tags(parser, [('end', 'doc')])
+ @unittest.skipIf(pyexpat.version_info < (2, 6, 0),
+ "Reparse deferral not defined for libexpat < 2.6.0")
def test_flush_reparse_deferral_disabled(self):
parser = ET.XMLPullParser(events=('start', 'end'))
for chunk in ("<doc", ">"):
parser.feed(chunk)
- if pyexpat.version_info >= (2, 6, 0):
- if not ET is pyET:
- self.skipTest(f'XMLParser.(Get|Set)ReparseDeferralEnabled '
- 'methods not available in C')
- parser._parser._parser.SetReparseDeferralEnabled(False)
+ if not ET is pyET:
+ self.skipTest(f'XMLParser.(Get|Set)ReparseDeferralEnabled '
+ 'methods not available in C')
+ parser._parser._parser.SetReparseDeferralEnabled(False)
self.assert_event_tags(parser, []) # i.e. no elements started
if ET is pyET:

124
platlibdir-in-sys.patch Normal file
View File

@ -0,0 +1,124 @@
---
Include/cpython/initconfig.h | 1 +
Lib/test/test_embed.py | 1 +
Makefile.pre.in | 5 +++++
Python/initconfig.c | 21 +++++++++++++++++++++
Python/sysmodule.c | 1 +
5 files changed, 29 insertions(+)
--- a/Include/cpython/initconfig.h
+++ b/Include/cpython/initconfig.h
@@ -381,6 +381,7 @@ typedef struct {
wchar_t *base_prefix; /* sys.base_prefix */
wchar_t *exec_prefix; /* sys.exec_prefix */
wchar_t *base_exec_prefix; /* sys.base_exec_prefix */
+ wchar_t *platlibdir; /* sys.platlibdir */
/* --- Parameter only used by Py_Main() ---------- */
--- a/Lib/test/test_embed.py
+++ b/Lib/test/test_embed.py
@@ -382,6 +382,7 @@ class InitConfigTests(EmbeddingTestsMixi
'exec_prefix': GET_DEFAULT_CONFIG,
'base_exec_prefix': GET_DEFAULT_CONFIG,
'module_search_paths': GET_DEFAULT_CONFIG,
+ 'platlibdir': sys.platlibdir,
'site_import': 1,
'bytes_warning': 0,
--- a/Makefile.pre.in
+++ b/Makefile.pre.in
@@ -811,6 +811,11 @@ Python/sysmodule.o: $(srcdir)/Python/sys
$(MULTIARCH_CPPFLAGS) \
-o $@ $(srcdir)/Python/sysmodule.c
+Python/initconfig.o: $(srcdir)/Python/initconfig.c
+ $(CC) -c $(PY_CORE_CFLAGS) \
+ -DPLATLIBDIR='"$(platsubdir)"' \
+ -o $@ $(srcdir)/Python/initconfig.c
+
$(IO_OBJS): $(IO_H)
.PHONY: regen-grammar
--- a/Python/initconfig.c
+++ b/Python/initconfig.c
@@ -596,6 +596,7 @@ PyConfig_Clear(PyConfig *config)
CLEAR(config->base_prefix);
CLEAR(config->exec_prefix);
CLEAR(config->base_exec_prefix);
+ CLEAR(config->platlibdir);
CLEAR(config->filesystem_encoding);
CLEAR(config->filesystem_errors);
@@ -834,6 +835,7 @@ _PyConfig_Copy(PyConfig *config, const P
COPY_WSTR_ATTR(base_prefix);
COPY_WSTR_ATTR(exec_prefix);
COPY_WSTR_ATTR(base_exec_prefix);
+ COPY_WSTR_ATTR(platlibdir);
COPY_ATTR(site_import);
COPY_ATTR(bytes_warning);
@@ -935,6 +937,7 @@ config_as_dict(const PyConfig *config)
SET_ITEM_WSTR(base_prefix);
SET_ITEM_WSTR(exec_prefix);
SET_ITEM_WSTR(base_exec_prefix);
+ SET_ITEM_WSTR(platlibdir);
SET_ITEM_INT(site_import);
SET_ITEM_INT(bytes_warning);
SET_ITEM_INT(inspect);
@@ -1336,6 +1339,14 @@ config_read_env_vars(PyConfig *config)
config->malloc_stats = 1;
}
+ if(config->platlibdir == NULL) {
+ status = CONFIG_GET_ENV_DUP(config, &config->platlibdir,
+ L"PYTHONPLATLIBDIR", "PYTHONPLATLIBDIR");
+ if (_PyStatus_EXCEPTION(status)) {
+ return status;
+ }
+ }
+
if (config->pythonpath_env == NULL) {
status = CONFIG_GET_ENV_DUP(config, &config->pythonpath_env,
L"PYTHONPATH", "PYTHONPATH");
@@ -1786,6 +1797,14 @@ config_read(PyConfig *config)
}
}
+ if(config->platlibdir == NULL) {
+ status = CONFIG_SET_BYTES_STR(config, &config->platlibdir, PLATLIBDIR,
+ "PLATLIBDIR macro");
+ if (_PyStatus_EXCEPTION(status)) {
+ return status;
+ }
+ }
+
if (config->_install_importlib) {
status = _PyConfig_InitPathConfig(config);
if (_PyStatus_EXCEPTION(status)) {
@@ -2565,6 +2584,7 @@ PyConfig_Read(PyConfig *config)
assert(config->exec_prefix != NULL);
assert(config->base_exec_prefix != NULL);
}
+ assert(config->platlibdir != NULL);
assert(config->filesystem_encoding != NULL);
assert(config->filesystem_errors != NULL);
assert(config->stdio_encoding != NULL);
@@ -2715,6 +2735,7 @@ _Py_DumpPathConfig(PyThreadState *tstate
DUMP_SYS(_base_executable);
DUMP_SYS(base_prefix);
DUMP_SYS(base_exec_prefix);
+ DUMP_SYS(platlibdir);
DUMP_SYS(executable);
DUMP_SYS(prefix);
DUMP_SYS(exec_prefix);
--- a/Python/sysmodule.c
+++ b/Python/sysmodule.c
@@ -2981,6 +2981,7 @@ _PySys_InitMain(_PyRuntimeState *runtime
SET_SYS_FROM_WSTR("base_prefix", config->base_prefix);
SET_SYS_FROM_WSTR("exec_prefix", config->exec_prefix);
SET_SYS_FROM_WSTR("base_exec_prefix", config->base_exec_prefix);
+ SET_SYS_FROM_WSTR("platlibdir", config->platlibdir);
if (config->pycache_prefix != NULL) {
SET_SYS_FROM_WSTR("pycache_prefix", config->pycache_prefix);

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,25 @@
---
Makefile.pre.in | 7 +++++++
1 file changed, 7 insertions(+)
--- a/Makefile.pre.in
+++ b/Makefile.pre.in
@@ -758,11 +758,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 Makefile
$(CC) -c $(PY_CORE_CFLAGS) -DPYTHONPATH='"$(PYTHONPATH)"' \
-DPREFIX='"$(prefix)"' \

View File

@ -0,0 +1,15 @@
---
Lib/site.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
--- a/Lib/site.py
+++ b/Lib/site.py
@@ -77,7 +77,7 @@ import io
import stat
# Prefixes for site-packages; add additional prefixes like /usr/local here
-PREFIXES = [sys.prefix, sys.exec_prefix]
+PREFIXES = [sys.prefix, sys.exec_prefix, '/usr/local']
# Enable per user site-packages directory
# set it to False to disable the feature or True to force the feature
ENABLE_USER_SITE = None

View File

@ -0,0 +1,11 @@
--- a/Lib/test/test_posix.py
+++ b/Lib/test/test_posix.py
@@ -421,7 +421,7 @@ class PosixTester(unittest.TestCase):
def test_posix_fadvise(self):
fd = os.open(support.TESTFN, os.O_RDONLY)
try:
- posix.posix_fadvise(fd, 0, 0, posix.POSIX_FADV_WILLNEED)
+ posix.posix_fadvise(fd, 0, 0, posix.POSIX_FADV_RANDOM)
finally:
os.close(fd)

664
python.keyring Normal file
View File

@ -0,0 +1,664 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
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
Lrfee+7yz1F9N6mVsuyl21KxQV8PGw==
=r/zi
-----END PGP PUBLIC KEY BLOCK-----

View File

@ -0,0 +1,53 @@
From 7bd6f0e5500f778e940374237b94651f60ae1990 Mon Sep 17 00:00:00 2001
From: "Miss Islington (bot)"
<31488909+miss-islington@users.noreply.github.com>
Date: Fri, 6 Jul 2018 21:00:45 -0700
Subject: [PATCH] closes bpo-34056: Always return bytes from
_HackedGetData.get_data(). (GH-8130)
* Always return bytes from _HackedGetData.get_data().
Ensure the imp.load_source shim always returns bytes by reopening the file in
binary mode if needed. Hash-based pycs have to receive the source code in bytes.
It's tempting to change imp.get_suffixes() to always return 'rb' as a mode, but
that breaks some stdlib tests and likely 3rdparty code, too.
(cherry picked from commit b0274f2cddd36b49fe5080efbe160277ef546471)
Co-authored-by: Benjamin Peterson <benjamin@python.org>
---
Lib/imp.py | 13 ++++++-------
Lib/test/test_imp.py | 15 +++++++++++++++
.../2018-07-05-22-45-46.bpo-34056.86isrU.rst | 3 +++
3 files changed, 24 insertions(+), 7 deletions(-)
create mode 100644 Misc/NEWS.d/next/Library/2018-07-05-22-45-46.bpo-34056.86isrU.rst
--- a/Lib/test/test_imp.py
+++ b/Lib/test/test_imp.py
@@ -376,6 +376,20 @@ class ImportTests(unittest.TestCase):
mod = imp.load_module('mymod', file, path, description)
self.assertEqual(mod.x, 42)
+ def test_find_and_load_checked_pyc(self):
+ # issue 34056
+ with support.temp_cwd():
+ with open('mymod.py', 'wb') as fp:
+ fp.write(b'x = 42\n')
+ py_compile.compile(
+ 'mymod.py',
+ doraise=True,
+ invalidation_mode=py_compile.PycInvalidationMode.CHECKED_HASH,
+ )
+ file, path, description = imp.find_module('mymod', path=['.'])
+ mod = imp.load_module('mymod', file, path, description)
+ self.assertEqual(mod.x, 42)
+
class ReloadTests(unittest.TestCase):
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2018-07-05-22-45-46.bpo-34056.86isrU.rst
@@ -0,0 +1,3 @@
+Ensure the loader shim created by ``imp.load_module`` always returns bytes
+from its ``get_data()`` function. This fixes using ``imp.load_module`` with
+:pep:`552` hash-based pycs.

2657
python38.changes Normal file

File diff suppressed because it is too large Load Diff

1043
python38.spec Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,143 @@
From: Michel Normand <normand@linux.vnet.ibm.com>
Subject: skip random failing tests
Date: Thu, 18 Jan 2018 15:48:52 +0100
skip random failing tests:
in _test_multiprocessing.py:
test_async_timeout
test_waitfor_timeout
test_wait_integer
in test_events.py:
test_run_until_complete
test_signal_handling_args
test_call_later
Reported to fail on ppc64le host on multiple osc build trials:
(all failed for ppc64le, except one for ppc)
===
[michel@abanc:~/work/home:michel_mno:branches:devel:languages:python:Factory/python3]
$idx=1; while test 1; do echo "trial $idx:"; osc build \
--vm-type kvm -j 8 --threads 4 openSUSE_Factory_PowerPC ppc64le \
>/tmp/python3_trialx_${idx}.log 2>&1 || break; ((idx++)); done
===
FAIL: test_async_timeout (test.test_multiprocessing_fork.WithProcessesTestPool)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/abuild/rpmbuild/BUILD/Python-3.6.4/Lib/test/_test_multiprocessing.py", line 2017, in test_async_timeout
self.assertRaises(multiprocessing.TimeoutError, get, timeout=TIMEOUT2)
AssertionError: TimeoutError not raised by <test._test_multiprocessing.TimingWrapper object at 0x7fff89b45f28>
===
FAIL: test_waitfor_timeout (test.test_multiprocessing_spawn.WithManagerTestCondition)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/abuild/rpmbuild/BUILD/Python-3.6.4/Lib/test/_test_multiprocessing.py", line 1169, in test_waitfor_timeout
self.assertTrue(success.value)
AssertionError: False is not true
===
FAIL: test_run_until_complete (test.test_asyncio.test_events.SelectEventLoopTests)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/abuild/rpmbuild/BUILD/Python-3.6.4/Lib/test/test_asyncio/test_events.py", line 285, in test_run_until_complete
self.assertTrue(0.08 <= t1-t0 <= 0.8, t1-t0)
AssertionError: False is not true : 3.966844968999993
===
FAIL: test_signal_handling_args (test.test_asyncio.test_events.SelectEventLoopTests)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/abuild/rpmbuild/BUILD/Python-3.6.4/Lib/test/test_asyncio/test_events.py", line 566, in test_signal_handling_args
self.assertEqual(caught, 1)
AssertionError: 0 != 1
=== (ppc)
FAIL: test_wait_integer (test.test_multiprocessing_spawn.TestWait)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/abuild/rpmbuild/BUILD/Python-3.6.4/Lib/test/_test_multiprocessing.py", line 3762, in test_wait_integer
self.assertLess(delta, expected + 2)
AssertionError: 5.576360702514648 not less than 5
===
===
======================================================================
FAIL: test_call_later (test.test_asyncio.test_events.PollEventLoopTests)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/abuild/rpmbuild/BUILD/Python-3.6.4/Lib/test/test_asyncio/test_events.py", line 309, in test_call_later
self.assertTrue(0.08 <= t1-t0 <= 0.8, t1-t0)
AssertionError: False is not true : 2.7154626529999746
======================================================================
FAIL: test_call_later (test.test_asyncio.test_events.SelectEventLoopTests)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/abuild/rpmbuild/BUILD/Python-3.6.4/Lib/test/test_asyncio/test_events.py", line 309, in test_call_later
self.assertTrue(0.08 <= t1-t0 <= 0.8, t1-t0)
AssertionError: False is not true : 4.137590406000015
===
Signed-off-by: Michel Normand <normand@linux.vnet.ibm.com>
---
Lib/test/_test_multiprocessing.py | 3 +++
Lib/test/test_asyncio/test_events.py | 4 +++-
Lib/test/test_buffer.py | 1 +
3 files changed, 7 insertions(+), 1 deletion(-)
--- a/Lib/test/_test_multiprocessing.py
+++ b/Lib/test/_test_multiprocessing.py
@@ -1542,6 +1542,7 @@ class _TestCondition(BaseTestCase):
success.value = True
@unittest.skipUnless(HAS_SHAREDCTYPES, 'needs sharedctypes')
+ @unittest.skip("transient failure on PowerPC")
def test_waitfor_timeout(self):
# based on test in test/lock_tests.py
cond = self.Condition()
@@ -2432,6 +2433,7 @@ class _TestPool(BaseTestCase):
self.assertEqual(get(), 49)
self.assertTimingAlmostEqual(get.elapsed, TIMEOUT1)
+ @unittest.skip("transient failure on PowerPC")
def test_async_timeout(self):
res = self.pool.apply_async(sqr, (6, TIMEOUT2 + 1.0))
get = TimingWrapper(res.get)
@@ -4651,6 +4653,7 @@ class TestWait(unittest.TestCase):
sem.release()
time.sleep(period)
+ @unittest.skip("transient failure on PowerPC")
def test_wait_integer(self):
from multiprocessing.connection import wait
--- a/Lib/test/test_asyncio/test_events.py
+++ b/Lib/test/test_asyncio/test_events.py
@@ -268,11 +268,12 @@ class EventLoopTestsMixin:
# Note: because of the default Windows timing granularity of
# 15.6 msec, we use fairly long sleep times here (~100 msec).
+ @unittest.skip("transient failure on PowerPC")
def test_run_until_complete(self):
t0 = self.loop.time()
self.loop.run_until_complete(asyncio.sleep(0.1))
t1 = self.loop.time()
- self.assertTrue(0.08 <= t1-t0 <= 0.8, t1-t0)
+ self.assertTrue(0.08 <= t1-t0 <= 5.0, t1-t0)
def test_run_until_complete_stopped(self):
@@ -477,6 +478,7 @@ class EventLoopTestsMixin:
self.assertEqual(caught, 1)
@unittest.skipUnless(hasattr(signal, 'SIGALRM'), 'No SIGALRM')
+ @unittest.skip("transient failure on PowerPC")
def test_signal_handling_args(self):
some_args = (42,)
caught = 0
--- a/Lib/test/test_buffer.py
+++ b/Lib/test/test_buffer.py
@@ -2506,6 +2506,7 @@ class TestBufferProtocol(unittest.TestCa
a = ndarray(items, shape=[2, 2, 2], format="b")
check(memoryview(a), vsize(base_struct + 3 * per_dim))
+ @unittest.skip("transient failure on PowerPC")
def test_memoryview_struct_module(self):
class INT(object):

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 = "python38.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,26 @@
From 960bb883769e5c64a63b014590d75654db87ffb0 Mon Sep 17 00:00:00 2001
From: Pablo Galindo <Pablogsal@gmail.com>
Date: Fri, 10 May 2019 22:58:17 +0100
Subject: [PATCH] Fix sphinx deprecation warning about env.note_versionchange()
(GH-13236)
---
Doc/tools/extensions/pyspecific.py | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
--- a/Doc/tools/extensions/pyspecific.py
+++ b/Doc/tools/extensions/pyspecific.py
@@ -384,7 +384,12 @@ class DeprecatedRemoved(Directive):
translatable=False)
node.append(para)
env = self.state.document.settings.env
- env.get_domain('changeset').note_changeset(node)
+ # new method
+ if hasattr(env, 'get_domain'):
+ env.get_domain('changeset').note_changeset(node)
+ # deprecated pre-Sphinx-2 method
+ else:
+ env.note_versionchange('deprecated', version[0], node, self.lineno)
return [node] + messages

View File

@ -0,0 +1,12 @@
--- a/Lib/test/test_subprocess.py
+++ b/Lib/test/test_subprocess.py
@@ -1147,7 +1147,8 @@ class ProcessTestCase(BaseTestCase):
self.assertIn("0.0001", str(c.exception)) # For coverage of __str__.
# Some heavily loaded buildbots (sparc Debian 3.x) require this much
# time to start.
- self.assertEqual(p.wait(timeout=3), 0)
+ # OBS might require even more
+ self.assertEqual(p.wait(timeout=10), 0)
def test_invalid_bufsize(self):
# an invalid type of the bufsize argument should raise

View File

@ -0,0 +1,71 @@
From 7da97f61816f3cadaa6788804b22a2434b40e8c5 Mon Sep 17 00:00:00 2001
From: "Miss Islington (bot)"
<31488909+miss-islington@users.noreply.github.com>
Date: Mon, 21 Feb 2022 08:16:09 -0800
Subject: [PATCH] bpo-46811: Make test suite support Expat >=2.4.5 (GH-31453)
(GH-31472)
Curly brackets were never allowed in namespace URIs
according to RFC 3986, and so-called namespace-validating
XML parsers have the right to reject them a invalid URIs.
libexpat >=2.4.5 has become strcter in that regard due to
related security issues; with ET.XML instantiating a
namespace-aware parser under the hood, this test has no
future in CPython.
References:
- https://datatracker.ietf.org/doc/html/rfc3968
- https://www.w3.org/TR/xml-names/
Also, test_minidom.py: Support Expat >=2.4.5
(cherry picked from commit 2cae93832f46b245847bdc252456ddf7742ef45e)
Co-authored-by: Sebastian Pipping <sebastian@pipping.org>
---
Lib/test/test_minidom.py | 25 +++++++++++--------------
1 file changed, 11 insertions(+), 14 deletions(-)
create mode 100644 Misc/NEWS.d/next/Library/2022-02-20-21-03-31.bpo-46811.8BxgdQ.rst
--- a/Lib/test/test_minidom.py
+++ b/Lib/test/test_minidom.py
@@ -1149,14 +1149,12 @@ class MinidomTest(unittest.TestCase):
# Verify that character decoding errors raise exceptions instead
# of crashing
- if pyexpat.version_info >= (2, 4, 5):
- self.assertRaises(ExpatError, parseString,
- b'<fran\xe7ais></fran\xe7ais>')
- self.assertRaises(ExpatError, parseString,
- b'<franais>Comment \xe7a va ? Tr\xe8s bien ?</franais>')
- else:
- self.assertRaises(UnicodeDecodeError, parseString,
- b'<fran\xe7ais>Comment \xe7a va ? Tr\xe8s bien ?</fran\xe7ais>')
+ # It doesnt make any sense to insist on the exact text of the
+ # error message, or even the exact Exception … it is enough that
+ # the error has been discovered.
+ with self.assertRaises((UnicodeDecodeError, ExpatError)):
+ parseString(
+ b'<fran\xe7ais>Comment \xe7a va ? Tr\xe8s bien ?</fran\xe7ais>')
doc.unlink()
@@ -1601,13 +1599,12 @@ class MinidomTest(unittest.TestCase):
self.confirm(doc2.namespaceURI == xml.dom.EMPTY_NAMESPACE)
def testExceptionOnSpacesInXMLNSValue(self):
- if pyexpat.version_info >= (2, 4, 5):
- context = self.assertRaisesRegex(ExpatError, 'syntax error')
- else:
- context = self.assertRaisesRegex(ValueError, 'Unsupported syntax')
+ # It doesnt make any sense to insist on the exact text of the
+ # error message, or even the exact Exception … it is enough that
+ # the error has been discovered.
+ with self.assertRaises((ExpatError, ValueError)):
+ parseString('<element xmlns:abc="http:abc.com/de f g/hi/j k"><abc:foo /></element>')
- with context:
- parseString('<element xmlns:abc="http:abc.com/de f g/hi/j k"><abc:foo /></element>')
def testDocRemoveChild(self):
doc = parse(tstfile)