Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Lib/asyncio/taskgroups.py
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,9 @@ def create_task(self, coro, **kwargs):
# the current task too early. gh-128550, gh-128588
self._tasks.add(task)
task.add_done_callback(self._on_task_done)
# gh-155418: an eager task can cancel the group before joining _tasks
if self._aborting and not task.done():
task.cancel()
try:
return task
finally:
Expand Down
8 changes: 6 additions & 2 deletions Lib/enum.py
Original file line number Diff line number Diff line change
Expand Up @@ -624,9 +624,13 @@ def __new__(metacls, cls, bases, classdict, *, boundary=None, _simple=False, **k
'__invert__'
):
if name not in classdict:
# check for mixin overrides before replacing
enum_method = getattr(Flag, name)
setattr(enum_class, name, enum_method)
classdict[name] = enum_method
found_method = getattr(enum_class, name)
data_type_method = getattr(member_type, name, None)
if found_method in (enum_method, data_type_method):
setattr(enum_class, name, enum_method)
classdict[name] = enum_method
#
# replace any other __new__ with our own (as long as Enum is not None,
# anyway) -- again, this is to support pickle
Expand Down
2 changes: 1 addition & 1 deletion Lib/http/cookiejar.py
Original file line number Diff line number Diff line change
Expand Up @@ -627,7 +627,7 @@ def request_host(request):

"""
url = request.get_full_url()
host = urllib.parse.urlparse(url)[1]
host = urllib.parse.urlparse(url).netloc
if host == "":
host = request.get_header("Host", "")

Expand Down
34 changes: 15 additions & 19 deletions Lib/test/libregrtest/refleak.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import os
import sys
import warnings
from array import array
from inspect import isabstract
from typing import Any
import linecache
Expand Down Expand Up @@ -100,24 +101,20 @@ def runtest_refleak(test_name, test_func,
for obj in ByteString.__subclasses__() + [ByteString]: # type: ignore[attr-defined]
abcs[obj] = _get_dump(obj)[0]

# bpo-31217: Integer pool to get a single integer object for the same
# value. The pool is used to prevent false alarm when checking for memory
# block leaks. Fill the pool with values in -1000..1000 which are the most
# common (reference, memory block, file descriptor) differences.
int_pool = {value: value for value in range(-1000, 1000)}
def get_pooled_int(value):
return int_pool.setdefault(value, value)

warmups = hunt_refleak.warmups
runs = hunt_refleak.runs
filename = hunt_refleak.filename
repcount = warmups + runs

# Pre-allocate to ensure that the loop doesn't allocate anything new
# Pre-allocate to ensure that the loop doesn't allocate anything new.
# Store the deltas as raw values in arrays rather than as int objects in
# lists: each unique delta stored as an object would live until the end of
# the loop and show up in the following repetition's reference and memory
# block deltas (gh-75400, gh-155981).
rep_range = list(range(repcount))
rc_deltas = [0] * repcount
alloc_deltas = [0] * repcount
fd_deltas = [0] * repcount
rc_deltas = array('q', [0]) * repcount
alloc_deltas = array('q', [0]) * repcount
fd_deltas = array('q', [0]) * repcount
getallocatedblocks = sys.getallocatedblocks
gettotalrefcount = sys.gettotalrefcount
getunicodeinternedsize = sys.getunicodeinternedsize
Expand Down Expand Up @@ -161,12 +158,11 @@ def get_pooled_int(value):
rc_after = gettotalrefcount()
fd_after = fd_count()

rc_deltas[i] = get_pooled_int(rc_after - rc_before)
alloc_deltas[i] = get_pooled_int(alloc_after - alloc_before)
fd_deltas[i] = get_pooled_int(fd_after - fd_before)
rc_deltas[i] = rc_after - rc_before
alloc_deltas[i] = alloc_after - alloc_before
fd_deltas[i] = fd_after - fd_before

if not quiet:
# use max, not sum, so total_leaks is one of the pooled ints
total_leaks = max(rc_deltas[i], alloc_deltas[i], fd_deltas[i])
if total_leaks <= 0:
symbol = '.'
Expand Down Expand Up @@ -212,13 +208,13 @@ def check_fd_deltas(deltas):
return any(deltas)

failed = False
for deltas, item_name, checker in [
for raw_deltas, item_name, checker in [
(rc_deltas, 'references', check_rc_deltas),
(alloc_deltas, 'memory blocks', check_rc_deltas),
(fd_deltas, 'file descriptors', check_fd_deltas)
]:
# ignore warmup runs
deltas = deltas[warmups:]
# ignore warmup runs; convert to a list for reporting
deltas = list(raw_deltas[warmups:])
failing = checker(deltas)
suspicious = any(deltas)
if failing or suspicious:
Expand Down
2 changes: 1 addition & 1 deletion Lib/test/ssl_servers.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ def translate_path(self, path):

"""
# abandon query parameters
path = urllib.parse.urlparse(path)[2]
path = urllib.parse.urlparse(path).path
path = os.path.normpath(urllib.parse.unquote(path))
words = path.split('/')
words = filter(None, words)
Expand Down
2 changes: 1 addition & 1 deletion Lib/test/support/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -868,7 +868,7 @@ def open_urlresource(url, *args, **kw):

check = kw.pop('check', None)

filename = urllib.parse.urlparse(url)[2].split('/')[-1] # '/': it's URL!
filename = urllib.parse.urlparse(url).path.split('/')[-1] # '/': it's URL!

fn = os.path.join(TEST_DATA_DIR, filename)

Expand Down
11 changes: 11 additions & 0 deletions Lib/test/test_asyncio/test_taskgroups.py
Original file line number Diff line number Diff line change
Expand Up @@ -1187,6 +1187,17 @@ async def test_taskgroup_cancel_before_create_task(self):
with self.assertRaises(RuntimeError):
tg.create_task(asyncio.sleep(1))

async def test_taskgroup_cancel_from_child_before_first_suspension(self):
# gh-155418: an eager task can cancel the group before joining _tasks
async def child(tg):
tg.cancel()
await asyncio.sleep(10)
self.fail("the child was not cancelled")

async with asyncio.TaskGroup() as tg:
task = tg.create_task(child(tg))
self.assertTrue(task.cancelled())

async def test_taskgroup_cancel_keeps_outer_cancellation(self):
# gh-155433: any cancellation from outside the group must propagate.
async def child():
Expand Down
53 changes: 53 additions & 0 deletions Lib/test/test_enum.py
Original file line number Diff line number Diff line change
Expand Up @@ -4080,6 +4080,39 @@ class NeverEnum(WhereEnum):
self.assertFalse(NeverEnum.__dict__.get('_test1', False))
self.assertFalse(NeverEnum.__dict__.get('_test2', False))

def test_mixin_operator_override(self):
# a mixin's own bitwise-operator overrides must not be clobbered
# by Flag's default __or__/__and__/__xor__/__invert__ -- gh-121291
class OperatorMixin:
def __or__(self, other):
return 'mixin-or'
def __ror__(self, other):
return 'mixin-ror'
def __invert__(self):
return 'mixin-invert'
class MixedFlag(OperatorMixin, Flag):
A = 1
B = 2
self.assertIs(MixedFlag.__or__, OperatorMixin.__or__)
self.assertIs(MixedFlag.__ror__, OperatorMixin.__ror__)
self.assertIs(MixedFlag.__invert__, OperatorMixin.__invert__)
self.assertEqual(MixedFlag.A | MixedFlag.B, 'mixin-or')
self.assertEqual(1 | MixedFlag.A, 'mixin-ror')
self.assertEqual(~MixedFlag.A, 'mixin-invert')
# dunders the mixin didn't override still get Flag's own
self.assertIs(MixedFlag.__and__, Flag.__and__)
self.assertIs(MixedFlag.__xor__, Flag.__xor__)
self.assertIs(MixedFlag.__rand__, Flag.__rand__)
self.assertIs(MixedFlag.__rxor__, Flag.__rxor__)
self.assertEqual(MixedFlag.A & MixedFlag.B, MixedFlag(0))
#
# a plain (non-mixin) Flag subclass is unaffected
class PlainFlag(Flag):
A = 1
B = 2
self.assertIs(PlainFlag.__or__, Flag.__or__)
self.assertEqual(PlainFlag.A | PlainFlag.B, PlainFlag(3))


class OldTestIntFlag(unittest.TestCase):
"""Tests of the IntFlags."""
Expand Down Expand Up @@ -4564,6 +4597,26 @@ def cycle_enum():
'at least one thread failed while creating composite members')
self.assertEqual(256, len(seen), 'too many composite members created')

def test_mixin_operator_override(self):
# IntFlag's own mixed-in `int` also defines these operators, so the
# fix for gh-121291 must still override `int`'s raw operators with
# Flag's (returning IntFlag instances, not plain ints), while still
# respecting a genuine, separate mixin's override.
Color = self.Color
combined = Color.RED | Color.BLUE
self.assertIs(type(combined), Color)
self.assertEqual(combined, Color.PURPLE)
self.assertEqual(repr(combined), '<Color.PURPLE: 5>')
#
class OperatorMixin:
def __or__(self, other):
return 'mixin-or'
class MixedIntFlag(OperatorMixin, IntFlag):
A = 1
B = 2
self.assertIs(MixedIntFlag.__or__, OperatorMixin.__or__)
self.assertEqual(MixedIntFlag.A | MixedIntFlag.B, 'mixin-or')


class TestEmptyAndNonLatinStrings(unittest.TestCase):

Expand Down
28 changes: 28 additions & 0 deletions Lib/test/test_xml_etree.py
Original file line number Diff line number Diff line change
Expand Up @@ -1068,6 +1068,34 @@ def bxml(encoding, body=''):
self.assertRaises(ValueError, ET.XML, xml('undefined').encode('ascii'))
self.assertRaises(LookupError, ET.XML, xml('xxx').encode('ascii'))

def test_parse_text_source(self):
# gh-99064: The encoding declared in the document does not apply
# to a source which is already decoded.
def check(encoding, body):
xml = (f"<?xml version='1.0' encoding='{encoding}'?>"
f"<xml>{body}</xml>")
with self.subTest(encoding=encoding):
self.assertEqual(ET.parse(io.StringIO(xml)).getroot().text,
body)
# the same with an explicitly created parser
self.assertEqual(
ET.parse(io.StringIO(xml), ET.XMLParser()).getroot().text,
body)
check("ascii", 'a')
check("iso-8859-1", '\xbd')
check("iso-8859-15", '\u20ac')
check("cp437", '\u221a')
check("utf-8", '\u4e2d')
# not ASCII compatible, unsupported for a bytes source
check("utf-16", '\u4e2d')
check("utf-32", '\u4e2d')

def test_parse_text_source_multiple_chunks(self):
# the encoding is overridden before the first chunk is parsed
body = '\xe4' * 100_000
xml = "<?xml version='1.0' encoding='ISO-8859-1'?><xml>%s</xml>" % body
self.assertEqual(ET.parse(io.StringIO(xml)).getroot().text, body)

@support.subTests('sample,exception', [
(b'<x> \xa1</x>', UnicodeDecodeError), # crashed
(b'<x> \xa1</x', UnicodeDecodeError), # crashed
Expand Down
12 changes: 6 additions & 6 deletions Lib/urllib/request.py
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,7 @@ def request_host(request):

"""
url = request.full_url
host = urlparse(url)[1]
host = urlparse(url).netloc
if host == "":
host = request.get_header("Host", "")

Expand Down Expand Up @@ -833,11 +833,11 @@ def reduce_uri(self, uri, default_port=True):
"""Accept authority or URI and extract only the authority and path."""
# note HTTP URLs do not have a userinfo component
parts = urlsplit(uri)
if parts[1]:
if parts.netloc:
# URI
scheme = parts[0]
authority = parts[1]
path = parts[2] or '/'
scheme = parts.scheme
authority = parts.netloc
path = parts.path or '/'
else:
# host or host:port
scheme = None
Expand Down Expand Up @@ -1222,7 +1222,7 @@ class HTTPDigestAuthHandler(BaseHandler, AbstractDigestAuthHandler):
handler_order = 490 # before Basic auth

def http_error_401(self, req, fp, code, msg, headers):
host = urlparse(req.full_url)[1]
host = urlparse(req.full_url).netloc
retry = self.http_error_auth_reqed('www-authenticate',
host, req, headers)
self.reset_retry_count()
Expand Down
4 changes: 3 additions & 1 deletion Lib/urllib/robotparser.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,9 @@ def set_url(self, url):

if isinstance(url, urllib.request.Request):
url = url.full_url
self.host, self.path = urllib.parse.urlsplit(url)[1:3]
parts = urllib.parse.urlsplit(url)
self.host = parts.netloc
self.path = parts.path

def read(self):
"""Reads the robots.txt URL and feeds it to the parser."""
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Don't set the result in error in :c:func:`PyLong_AsInt32`,
:c:func:`PyLong_AsUInt32`, :c:func:`PyLong_AsInt64` and
:c:func:`PyLong_AsUInt64`. Leave the result unchanged in this case. Patch by
Victor Stinner.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fix :class:`asyncio.TaskGroup` hang when a task created by
:func:`asyncio.eager_task_factory` cancels the group before suspending.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
:class:`enum.Flag` (and :class:`enum.IntFlag`) subclasses no longer have
a mixin base's own ``__or__``, ``__and__``, ``__xor__``, ``__ror__``,
``__rand__``, ``__rxor__``, or ``__invert__`` override silently replaced
by :class:`~enum.Flag`'s default implementation.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Fix :func:`xml.etree.ElementTree.parse` with a text file or other source
of :class:`str` data in the C implementation.
The encoding declared in the document was applied to the already decoded
text, which produced mojibake. It is now ignored, as when parsing with
:meth:`!XMLParser.feed` or :func:`~xml.etree.ElementTree.fromstring`.
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
Update bundled `libexpat <https://libexpat.github.io/>`_ to version 2.8.3
for the fix to :cve:`2026-72522`.
Update bundled `libexpat <https://libexpat.github.io/>`_ to version 2.8.4.
24 changes: 12 additions & 12 deletions Misc/sbom.spdx.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading