From a8f0c9c995af33d746fa8b33df44e92b055219cd Mon Sep 17 00:00:00 2001
From: grantlouisherman
Date: Wed, 20 May 2026 00:17:31 -0400
Subject: [PATCH 01/15] bugfix(150107): adding isinstance check on offset to
include if available
Signed-off-by: grantlouisherman
---
Lib/asyncio/base_events.py | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/Lib/asyncio/base_events.py b/Lib/asyncio/base_events.py
index 1fedb066f94c53..344b5d8b4e03ac 100644
--- a/Lib/asyncio/base_events.py
+++ b/Lib/asyncio/base_events.py
@@ -969,7 +969,7 @@ async def _sock_sendfile_native(self, sock, file, offset, count):
f"and file {file!r} combination")
async def _sock_sendfile_fallback(self, sock, file, offset, count):
- if offset:
+ if isinstance(offset, int):
file.seek(offset)
blocksize = (
min(count, constants.SENDFILE_FALLBACK_READBUFFER_SIZE)
@@ -1286,7 +1286,6 @@ async def sendfile(self, transport, file, offset=0, count=None,
raise RuntimeError(
f"fallback is disabled and native sendfile is not "
f"supported for transport {transport!r}")
-
return await self._sendfile_fallback(transport, file,
offset, count)
@@ -1295,7 +1294,7 @@ async def _sendfile_native(self, transp, file, offset, count):
"sendfile syscall is not supported")
async def _sendfile_fallback(self, transp, file, offset, count):
- if offset:
+ if isinstance(offset, int):
file.seek(offset)
blocksize = min(count, 16384) if count else 16384
buf = bytearray(blocksize)
From d2a9ce0dd026e53635dfc28aaf0a74c987574848 Mon Sep 17 00:00:00 2001
From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com>
Date: Thu, 21 May 2026 14:17:31 +0000
Subject: [PATCH 02/15] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20b?=
=?UTF-8?q?lurb=5Fit.?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../2026-05-21-14-17-29.gh-issue-150107.hwPKW6.rst | 1 +
1 file changed, 1 insertion(+)
create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-05-21-14-17-29.gh-issue-150107.hwPKW6.rst
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-05-21-14-17-29.gh-issue-150107.hwPKW6.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-05-21-14-17-29.gh-issue-150107.hwPKW6.rst
new file mode 100644
index 00000000000000..8bcd83407d2ee9
--- /dev/null
+++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-05-21-14-17-29.gh-issue-150107.hwPKW6.rst
@@ -0,0 +1 @@
+Offset was not being honored in asyncio because conditional was checking if offset was truthy or falsey. However, if offset is 0 then it is deemed falsey even though 0 is acceptable number to file.seek to. I changed the conditional to use instance of ints to validate if the program should file.seek or not
From b525cb2c9b6cb75e567bfad905dea820f04c447f Mon Sep 17 00:00:00 2001
From: grantlouisherman
Date: Thu, 21 May 2026 11:57:59 -0400
Subject: [PATCH 03/15] addressing PR comments
Signed-off-by: grantlouisherman
---
Lib/asyncio/base_events.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/Lib/asyncio/base_events.py b/Lib/asyncio/base_events.py
index 344b5d8b4e03ac..3732294c5848f0 100644
--- a/Lib/asyncio/base_events.py
+++ b/Lib/asyncio/base_events.py
@@ -969,7 +969,7 @@ async def _sock_sendfile_native(self, sock, file, offset, count):
f"and file {file!r} combination")
async def _sock_sendfile_fallback(self, sock, file, offset, count):
- if isinstance(offset, int):
+ if hasattr(file, 'seek'):
file.seek(offset)
blocksize = (
min(count, constants.SENDFILE_FALLBACK_READBUFFER_SIZE)
@@ -1294,7 +1294,7 @@ async def _sendfile_native(self, transp, file, offset, count):
"sendfile syscall is not supported")
async def _sendfile_fallback(self, transp, file, offset, count):
- if isinstance(offset, int):
+ if hasattr(file, 'seek'):
file.seek(offset)
blocksize = min(count, 16384) if count else 16384
buf = bytearray(blocksize)
From f247be00a639854a599d2465c198fd05af9fe52e Mon Sep 17 00:00:00 2001
From: grantlouisherman
Date: Thu, 21 May 2026 12:01:13 -0400
Subject: [PATCH 04/15] addressing PR comments
Signed-off-by: grantlouisherman
---
.../2026-05-21-14-17-29.gh-issue-150107.hwPKW6.rst | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-05-21-14-17-29.gh-issue-150107.hwPKW6.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-05-21-14-17-29.gh-issue-150107.hwPKW6.rst
index 8bcd83407d2ee9..76f83630d37cd7 100644
--- a/Misc/NEWS.d/next/Core_and_Builtins/2026-05-21-14-17-29.gh-issue-150107.hwPKW6.rst
+++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-05-21-14-17-29.gh-issue-150107.hwPKW6.rst
@@ -1 +1,3 @@
-Offset was not being honored in asyncio because conditional was checking if offset was truthy or falsey. However, if offset is 0 then it is deemed falsey even though 0 is acceptable number to file.seek to. I changed the conditional to use instance of ints to validate if the program should file.seek or not
+:mod:`asyncio`: ``sendfile()`` and ``sock_sendfile()`` event loop methods
+now call ``file.seek(offset)`` if *file* has a `seek()` method,
+even if *offset* is `0` (default value)`.
From 84cdc6c41fffb7e9382ecc71a0d728b8ef3fffed Mon Sep 17 00:00:00 2001
From: grantlouisherman
Date: Thu, 21 May 2026 12:12:02 -0400
Subject: [PATCH 05/15] fixing lints
Signed-off-by: grantlouisherman
---
.../2026-05-21-14-17-29.gh-issue-150107.hwPKW6.rst | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-05-21-14-17-29.gh-issue-150107.hwPKW6.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-05-21-14-17-29.gh-issue-150107.hwPKW6.rst
index 76f83630d37cd7..a13f249e48cc02 100644
--- a/Misc/NEWS.d/next/Core_and_Builtins/2026-05-21-14-17-29.gh-issue-150107.hwPKW6.rst
+++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-05-21-14-17-29.gh-issue-150107.hwPKW6.rst
@@ -1,3 +1,3 @@
:mod:`asyncio`: ``sendfile()`` and ``sock_sendfile()`` event loop methods
-now call ``file.seek(offset)`` if *file* has a `seek()` method,
-even if *offset* is `0` (default value)`.
+now call ``file.seek(offset)`` if *file* has a ``seek()`` method,
+even if *offset* is ``0`` (default value).
From 801932f11e159b1dca6291c3dafceb980516114c Mon Sep 17 00:00:00 2001
From: grantlouisherman
Date: Thu, 21 May 2026 12:56:04 -0400
Subject: [PATCH 06/15] adding unit test
Signed-off-by: grantlouisherman
---
Lib/test/test_asyncio/test_sendfile.py | 28 +++++++++++++++++++++++++-
1 file changed, 27 insertions(+), 1 deletion(-)
diff --git a/Lib/test/test_asyncio/test_sendfile.py b/Lib/test/test_asyncio/test_sendfile.py
index dcd963b3355ef8..efab4b84bc8ee8 100644
--- a/Lib/test/test_asyncio/test_sendfile.py
+++ b/Lib/test/test_asyncio/test_sendfile.py
@@ -227,7 +227,34 @@ def test_sock_sendfile_zero_size(self):
self.assertEqual(ret, 0)
self.assertEqual(self.file.tell(), 0)
+ def check_sock_sendfile_offset(self, data, offset, force_fallback=False):
+ sock, proto = self.prepare_socksendfile()
+ with tempfile.TemporaryFile() as f:
+ f.write(data)
+ f.flush()
+ self.assertEqual(f.tell(), len(data))
+
+ if force_fallback:
+ async def _sock_sendfile_fail(sock, file, offset, count):
+ raise asyncio.exceptions.SendfileNotAvailableError()
+ with support.swap_attr(self.loop, '_sock_sendfile_native', _sock_sendfile_fail):
+ ret = self.run_loop(self.loop.sock_sendfile(sock, f, offset, None))
+ else:
+ ret = self.run_loop(self.loop.sock_sendfile(sock, f, offset, None))
+ self.assertEqual(f.tell(), len(data))
+
+ sock.close()
+ self.run_loop(proto.wait_closed())
+
+ self.assertEqual(ret, len(data) - offset)
+
+ def test_sock_sendfile_offset(self):
+ data = b'abcdef'
+ for offset in (0, len(data) // 2, len(data)):
+ for force_fallback in (False, True):
+ with self.subTest(offset=offset, force_fallback=force_fallback):
+ self.check_sock_sendfile_offset(data, offset, force_fallback)
def test_sock_sendfile_mix_with_regular_send(self):
buf = b"mix_regular_send" * (4 * 1024) # 64 KiB
sock, proto = self.prepare_socksendfile()
@@ -450,7 +477,6 @@ def test_sendfile_ssl_close_peer_after_receiving(self):
self.assertEqual(ret, len(self.DATA))
self.assertEqual(srv_proto.nbytes, len(self.DATA))
self.assertEqual(srv_proto.data, self.DATA)
- self.assertEqual(self.file.tell(), len(self.DATA))
# On Solaris, lowering SO_RCVBUF on a TCP connection after it has been
# established has no effect. Due to its age, this bug affects both Oracle
From 800a2d51a995e9c2fe2a0ea66984720845abd7b9 Mon Sep 17 00:00:00 2001
From: grantlouisherman
Date: Thu, 21 May 2026 13:48:44 -0400
Subject: [PATCH 07/15] fixing test failures and lint errors
Signed-off-by: grantlouisherman
---
Lib/test/test_asyncio/test_sendfile.py | 26 +++++++++++++-------------
1 file changed, 13 insertions(+), 13 deletions(-)
diff --git a/Lib/test/test_asyncio/test_sendfile.py b/Lib/test/test_asyncio/test_sendfile.py
index efab4b84bc8ee8..ae7a2e32c52687 100644
--- a/Lib/test/test_asyncio/test_sendfile.py
+++ b/Lib/test/test_asyncio/test_sendfile.py
@@ -227,6 +227,7 @@ def test_sock_sendfile_zero_size(self):
self.assertEqual(ret, 0)
self.assertEqual(self.file.tell(), 0)
+
def check_sock_sendfile_offset(self, data, offset, force_fallback=False):
sock, proto = self.prepare_socksendfile()
with tempfile.TemporaryFile() as f:
@@ -234,20 +235,17 @@ def check_sock_sendfile_offset(self, data, offset, force_fallback=False):
f.flush()
self.assertEqual(f.tell(), len(data))
- if force_fallback:
- async def _sock_sendfile_fail(sock, file, offset, count):
- raise asyncio.exceptions.SendfileNotAvailableError()
- with support.swap_attr(self.loop, '_sock_sendfile_native', _sock_sendfile_fail):
+ if force_fallback:
+ async def _sock_sendfile_fail(sock, file, offset, count):
+ raise asyncio.exceptions.SendfileNotAvailableError()
+ with support.swap_attr(self.loop, '_sock_sendfile_native', _sock_sendfile_fail):
+ ret = self.run_loop(self.loop.sock_sendfile(sock, f, offset, None))
+ else:
ret = self.run_loop(self.loop.sock_sendfile(sock, f, offset, None))
- else:
- ret = self.run_loop(self.loop.sock_sendfile(sock, f, offset, None))
-
- self.assertEqual(f.tell(), len(data))
-
- sock.close()
- self.run_loop(proto.wait_closed())
-
- self.assertEqual(ret, len(data) - offset)
+ self.assertEqual(f.tell(), len(data))
+ sock.close()
+ self.run_loop(proto.wait_closed())
+ self.assertEqual(ret, len(data) - offset)
def test_sock_sendfile_offset(self):
data = b'abcdef'
@@ -255,6 +253,7 @@ def test_sock_sendfile_offset(self):
for force_fallback in (False, True):
with self.subTest(offset=offset, force_fallback=force_fallback):
self.check_sock_sendfile_offset(data, offset, force_fallback)
+
def test_sock_sendfile_mix_with_regular_send(self):
buf = b"mix_regular_send" * (4 * 1024) # 64 KiB
sock, proto = self.prepare_socksendfile()
@@ -477,6 +476,7 @@ def test_sendfile_ssl_close_peer_after_receiving(self):
self.assertEqual(ret, len(self.DATA))
self.assertEqual(srv_proto.nbytes, len(self.DATA))
self.assertEqual(srv_proto.data, self.DATA)
+ self.assertEqual(self.file.tell(), len(self.DATA))
# On Solaris, lowering SO_RCVBUF on a TCP connection after it has been
# established has no effect. Due to its age, this bug affects both Oracle
From 2af301529773c7d782b88a4c901c006bd49dd774 Mon Sep 17 00:00:00 2001
From: grantlouisherman
Date: Thu, 21 May 2026 16:22:34 -0400
Subject: [PATCH 08/15] fixing windows tests
Signed-off-by: grantlouisherman
---
Lib/asyncio/windows_events.py | 3 +++
1 file changed, 3 insertions(+)
diff --git a/Lib/asyncio/windows_events.py b/Lib/asyncio/windows_events.py
index 5f75b17d8ca649..0bf7732136f1f8 100644
--- a/Lib/asyncio/windows_events.py
+++ b/Lib/asyncio/windows_events.py
@@ -610,6 +610,9 @@ def sendfile(self, sock, file, offset, count):
ov = _overlapped.Overlapped(NULL)
offset_low = offset & 0xffff_ffff
offset_high = (offset >> 32) & 0xffff_ffff
+ # TransmitFile ignores OVERLAPPED.Offset for handles not opened with
+ # FILE_FLAG_OVERLAPPED, so seek the CRT file pointer to match.
+ file.seek(offset)
ov.TransmitFile(sock.fileno(),
msvcrt.get_osfhandle(file.fileno()),
offset_low, offset_high,
From aaa1a2ddef44d4dfe3e3b12334c08b2625d1217a Mon Sep 17 00:00:00 2001
From: Jelle Zijlstra
Date: Thu, 21 May 2026 21:06:42 -0700
Subject: [PATCH 09/15] gh-149995: Update typing.py docstrings and
documentation (#149996)
Some of these docstrings read as if they were written when typing.py was
first written, and things have evolved since then.
A few motivations:
- Call protocols protocols instead of ABCs. They are also ABCs, but the fact
they are protocols is more relevant to typing.
- Avoid recommending direct use of .__annotations__ and steer users to
annotationlib instead.
- For TypedDict, mention NotRequired before total=False since it is more
general and probably more frequently useful.
- For overloads, mention runtime use first instead of stub use. I think early on
there was talk of allowing overload only in stubs, but it is now heavily used at
runtime too and that's more likely to be relevant to users.
---
Doc/library/typing.rst | 46 +++----
Lib/typing.py | 115 +++++++++---------
...-05-18-07-44-46.gh-issue-149995.vvtFHn.rst | 1 +
3 files changed, 81 insertions(+), 81 deletions(-)
create mode 100644 Misc/NEWS.d/next/Library/2026-05-18-07-44-46.gh-issue-149995.vvtFHn.rst
diff --git a/Doc/library/typing.rst b/Doc/library/typing.rst
index 71b395c80166cc..b2167cbc63a1ff 100644
--- a/Doc/library/typing.rst
+++ b/Doc/library/typing.rst
@@ -719,8 +719,8 @@ The :data:`Any` type
====================
A special kind of type is :data:`Any`. A static type checker will treat
-every type as being compatible with :data:`Any` and :data:`Any` as being
-compatible with every type.
+every type as assignable to :data:`Any` and :data:`Any` as assignable to
+every type.
This means that it is possible to perform any operation or method call on a
value of type :data:`Any` and assign it to any variable::
@@ -785,7 +785,7 @@ it as a return value) of a more specialized type is a type error. For example::
hash_a(42)
hash_a("foo")
- # Passes type checking, since Any is compatible with all types
+ # Passes type checking, since Any is assignable to all types
hash_b(42)
hash_b("foo")
@@ -851,8 +851,8 @@ using ``[]``.
Special type indicating an unconstrained type.
- * Every type is compatible with :data:`Any`.
- * :data:`Any` is compatible with every type.
+ * Every type is assignable to :data:`Any`.
+ * :data:`Any` is assignable to every type.
.. versionchanged:: 3.11
:data:`Any` can now be used as a base class. This can be useful for
@@ -1292,10 +1292,10 @@ These can be used as types in annotations. They all support subscription using
:data:`ClassVar` accepts only types and cannot be further subscribed.
- :data:`ClassVar` is not a class itself, and should not
+ :data:`ClassVar` is not a class itself, and cannot
be used with :func:`isinstance` or :func:`issubclass`.
:data:`ClassVar` does not change Python runtime behavior, but
- it can be used by third-party type checkers. For example, a type checker
+ it can be used by static type checkers. For example, a type checker
might flag the following code as an error::
enterprise_d = Starship(3000)
@@ -1365,7 +1365,7 @@ These can be used as types in annotations. They all support subscription using
def mutate_movie(m: Movie) -> None:
m["year"] = 1999 # allowed
- m["title"] = "The Matrix" # typechecker error
+ m["title"] = "The Matrix" # type checker error
There is no runtime checking for this property.
@@ -2472,9 +2472,9 @@ types.
Fields with a default value must come after any fields without a default.
- The resulting class has an extra attribute ``__annotations__`` giving a
- dict that maps the field names to the field types. (The field names are in
- the ``_fields`` attribute and the default values are in the
+ The types for each field name can be retrieved by calling
+ :func:`annotationlib.get_annotations` on the resulting class. (The field
+ names are in the ``_fields`` attribute and the default values are in the
``_field_defaults`` attribute, both of which are part of the :func:`~collections.namedtuple`
API.)
@@ -2535,7 +2535,7 @@ types.
Helper class to create low-overhead :ref:`distinct types `.
- A ``NewType`` is considered a distinct type by a typechecker. At runtime,
+ A ``NewType`` is considered a distinct type by a type checker. At runtime,
however, calling a ``NewType`` returns its argument unchanged.
Usage::
@@ -2616,7 +2616,7 @@ types.
Mark a protocol class as a runtime protocol.
Such a protocol can be used with :func:`isinstance` and :func:`issubclass`.
- This allows a simple-minded structural check, very similar to "one trick ponies"
+ This allows a simple-minded structural check, very similar to "one-trick ponies"
in :mod:`collections.abc` such as :class:`~collections.abc.Iterable`. For example::
@runtime_checkable
@@ -2855,7 +2855,7 @@ types.
key: T
group: list[T]
- A ``TypedDict`` can be introspected via annotations dicts
+ A ``TypedDict`` can be introspected via :func:`annotationlib.get_annotations`
(see :ref:`annotations-howto` for more information on annotations best practices)
and the following attributes:
@@ -2898,7 +2898,7 @@ types.
For backwards compatibility with Python 3.10 and below,
it is also possible to use inheritance to declare both required and
- non-required keys in the same ``TypedDict`` . This is done by declaring a
+ non-required keys in the same ``TypedDict``. This is done by declaring a
``TypedDict`` with one value for the ``total`` argument and then
inheriting from it in another ``TypedDict`` with a different value for
``total``:
@@ -2982,34 +2982,34 @@ with :deco:`runtime_checkable`.
.. class:: SupportsAbs
- An ABC with one abstract method ``__abs__`` that is covariant
+ A protocol with one abstract method ``__abs__`` that is covariant
in its return type.
.. class:: SupportsBytes
- An ABC with one abstract method ``__bytes__``.
+ A protocol with one abstract method ``__bytes__``.
.. class:: SupportsComplex
- An ABC with one abstract method ``__complex__``.
+ A protocol with one abstract method ``__complex__``.
.. class:: SupportsFloat
- An ABC with one abstract method ``__float__``.
+ A protocol with one abstract method ``__float__``.
.. class:: SupportsIndex
- An ABC with one abstract method ``__index__``.
+ A protocol with one abstract method ``__index__``.
.. versionadded:: 3.8
.. class:: SupportsInt
- An ABC with one abstract method ``__int__``.
+ A protocol with one abstract method ``__int__``.
.. class:: SupportsRound
- An ABC with one abstract method ``__round__``
+ A protocol with one abstract method ``__round__``
that is covariant in its return type.
.. _typing-io:
@@ -3763,7 +3763,7 @@ Constant
.. data:: TYPE_CHECKING
- A special constant that is assumed to be ``True`` by 3rd party static
+ A special constant that is assumed to be ``True`` by static
type checkers. It's ``False`` at runtime.
A module which is expensive to import, and which only contain types
diff --git a/Lib/typing.py b/Lib/typing.py
index 130e09be4b9127..715d08e0e1603e 100644
--- a/Lib/typing.py
+++ b/Lib/typing.py
@@ -5,7 +5,7 @@
* Generic, Protocol, and internal machinery to support generic aliases.
All subscripted types like X[int], Union[int, str] are generic aliases.
* Various "special forms" that have unique meanings in type annotations:
- NoReturn, Never, ClassVar, Self, Concatenate, Unpack, and others.
+ Any, Never, ClassVar, Self, Concatenate, Unpack, and others.
* Classes whose instances can be type arguments to generic classes and functions:
TypeVar, ParamSpec, TypeVarTuple.
* Public helper functions: get_type_hints, overload, cast, final, and others.
@@ -591,12 +591,12 @@ def __repr__(self):
class Any(metaclass=_AnyMeta):
"""Special type indicating an unconstrained type.
- - Any is compatible with every type.
- - Any assumed to have all methods.
- - All values assumed to be instances of Any.
+ - Any is assignable to every type.
+ - Any assumed to have all methods and attributes.
+ - All values are assignable to Any.
Note that all the above statements are true from the point of view of
- static type checkers. At runtime, Any should not be used with instance
+ static type checkers. At runtime, Any cannot be used with instance
checks.
"""
@@ -715,7 +715,7 @@ class Starship:
ClassVar accepts only types and cannot be further subscribed.
- Note that ClassVar is not a class itself, and should not
+ Note that ClassVar is not a class itself, and cannot
be used with isinstance() or issubclass().
"""
item = _type_check(parameters, f'{self} accepts only single type.', allow_special_forms=True)
@@ -745,7 +745,7 @@ class FastConnector(Connection):
@_SpecialForm
def Optional(self, parameters):
- """Optional[X] is equivalent to Union[X, None]."""
+ """Optional[X] is equivalent to X | None."""
arg = _type_check(parameters, f"{self} requires a single type.")
return Union[arg, type(None)]
@@ -788,7 +788,7 @@ def open_helper(file: str, mode: MODE) -> str:
def TypeAlias(self, parameters):
"""Special form for marking type aliases.
- Use TypeAlias to indicate that an assignment should
+ TypeAlias can be used to indicate that an assignment should
be recognized as a proper type alias definition by type
checkers.
@@ -1796,7 +1796,7 @@ class Movie(TypedDict):
def foo(**kwargs: Unpack[Movie]): ...
Note that there is only some runtime checking of this operator. Not
- everything the runtime allows may be accepted by static type checkers.
+ everything the runtime allows is accepted by static type checkers.
For more information, see PEPs 646 and 692.
"""
@@ -2307,7 +2307,7 @@ def runtime_checkable(cls):
Such protocol can be used with isinstance() and issubclass().
Raise TypeError if applied to a non-protocol class.
This allows a simple-minded structural check very similar to
- one trick ponies in collections.abc such as Iterable.
+ one-trick ponies in collections.abc such as Iterable.
For example::
@@ -2377,8 +2377,8 @@ def get_type_hints(obj, globalns=None, localns=None, include_extras=False,
*, format=None):
"""Return type hints for an object.
- This is often the same as obj.__annotations__, but it handles
- forward references encoded as string literals and recursively replaces all
+ This is often the same as annotationlib.get_annotations(obj) or obj.__annotations__,
+ but it handles forward references encoded as string literals and recursively replaces all
'Annotated[T, ...]' with 'T' (unless 'include_extras=True').
The argument may be a module, class, method, or function. The annotations
@@ -2590,7 +2590,7 @@ def get_args(tp):
def is_typeddict(tp):
- """Check if an annotation is a TypedDict class.
+ """Check if an object is a TypedDict class.
For example::
@@ -2687,10 +2687,10 @@ def _overload_dummy(*args, **kwds):
def overload(func):
"""Decorator for overloaded functions/methods.
- In a stub file, place two or more stub definitions for the same
- function in a row, each decorated with @overload.
-
- For example::
+ In a non-stub file, place two or more stub definitions for the same
+ function in a row, each decorated with @overload, followed
+ by an implementation. The implementation should *not*
+ be decorated with @overload::
@overload
def utf8(value: None) -> None: ...
@@ -2698,10 +2698,11 @@ def utf8(value: None) -> None: ...
def utf8(value: bytes) -> bytes: ...
@overload
def utf8(value: str) -> bytes: ...
+ def utf8(value):
+ ... # implementation goes here
- In a non-stub file (i.e. a regular .py file), do the same but
- follow it with an implementation. The implementation should *not*
- be decorated with @overload::
+ In a stub file or in an abstract method (for example, in a Protocol definition),
+ the implementation may be omitted::
@overload
def utf8(value: None) -> None: ...
@@ -2709,8 +2710,6 @@ def utf8(value: None) -> None: ...
def utf8(value: bytes) -> bytes: ...
@overload
def utf8(value: str) -> bytes: ...
- def utf8(value):
- ... # implementation goes here
The overloads for a function can be retrieved at runtime using the
get_overloads() function.
@@ -2746,7 +2745,7 @@ def final(f):
"""Decorator to indicate final methods and final classes.
Use this decorator to indicate to type checkers that the decorated
- method cannot be overridden, and decorated class cannot be subclassed.
+ method cannot be overridden, and the decorated class cannot be subclassed.
For example::
@@ -2811,7 +2810,7 @@ class Disjoint3(Disjoint1, Disjoint2): pass # Type checker error
V_co = TypeVar('V_co', covariant=True) # Any type covariant containers.
VT_co = TypeVar('VT_co', covariant=True) # Value type covariant containers.
T_contra = TypeVar('T_contra', contravariant=True) # Ditto contravariant.
-# Internal type variable used for Type[].
+# Internal type bound to class object types.
CT_co = TypeVar('CT_co', covariant=True, bound=type)
@@ -2899,7 +2898,7 @@ class TeamUser(User): ...
And a function that takes a class argument that's a subclass of
User and returns an instance of the corresponding class::
- def new_user[U](user_class: Type[U]) -> U:
+ def new_user[U](user_class: type[U]) -> U:
user = user_class()
# (Here we could write the user object to a database)
return user
@@ -2912,7 +2911,7 @@ def new_user[U](user_class: Type[U]) -> U:
@runtime_checkable
class SupportsInt(Protocol):
- """An ABC with one abstract method __int__."""
+ """A protocol with one abstract method __int__."""
__slots__ = ()
@@ -2923,7 +2922,7 @@ def __int__(self) -> int:
@runtime_checkable
class SupportsFloat(Protocol):
- """An ABC with one abstract method __float__."""
+ """A protocol with one abstract method __float__."""
__slots__ = ()
@@ -2934,7 +2933,7 @@ def __float__(self) -> float:
@runtime_checkable
class SupportsComplex(Protocol):
- """An ABC with one abstract method __complex__."""
+ """A protocol with one abstract method __complex__."""
__slots__ = ()
@@ -2945,7 +2944,7 @@ def __complex__(self) -> complex:
@runtime_checkable
class SupportsBytes(Protocol):
- """An ABC with one abstract method __bytes__."""
+ """A protocol with one abstract method __bytes__."""
__slots__ = ()
@@ -2956,7 +2955,7 @@ def __bytes__(self) -> bytes:
@runtime_checkable
class SupportsIndex(Protocol):
- """An ABC with one abstract method __index__."""
+ """A protocol with one abstract method __index__."""
__slots__ = ()
@@ -2967,7 +2966,7 @@ def __index__(self) -> int:
@runtime_checkable
class SupportsAbs[T](Protocol):
- """An ABC with one abstract method __abs__ that is covariant in its return type."""
+ """A protocol with one abstract method __abs__ that is covariant in its return type."""
__slots__ = ()
@@ -2978,7 +2977,7 @@ def __abs__(self) -> T:
@runtime_checkable
class SupportsRound[T](Protocol):
- """An ABC with one abstract method __round__ that is covariant in its return type."""
+ """A protocol with one abstract method __round__ that is covariant in its return type."""
__slots__ = ()
@@ -3095,7 +3094,7 @@ def annotate(format):
def NamedTuple(typename, fields, /):
- """Typed version of namedtuple.
+ """Typed version of collections.namedtuple.
Usage::
@@ -3107,8 +3106,8 @@ class Employee(NamedTuple):
Employee = collections.namedtuple('Employee', ['name', 'id'])
- The resulting class has an extra __annotations__ attribute, giving a
- dict that maps field names to types. (The field names are also in
+ The types for each field name can be retrieved by calling
+ annotationlib.get_annotations(Employee). (The field names are also in
the _fields attribute, which is part of the namedtuple API.)
An alternative equivalent functional syntax is also accepted::
@@ -3161,7 +3160,7 @@ def __new__(cls, name, bases, ns, total=True, closed=None,
This method is called when TypedDict is subclassed,
or when TypedDict is instantiated. This way
- TypedDict supports all three syntax forms described in its docstring.
+ TypedDict classes can be created through both class-based and functional syntax.
Subclasses and instances of TypedDict return actual dictionaries.
"""
for base in bases:
@@ -3315,14 +3314,22 @@ def TypedDict(typename, fields, /, *, total=True, closed=None,
>>> Point2D(x=1, y=2, label='first') == dict(x=1, y=2, label='first')
True
- The type info can be accessed via the Point2D.__annotations__ dict, and
- the Point2D.__required_keys__ and Point2D.__optional_keys__ frozensets.
+ The type info can be accessed by calling annotationlib.get_annotations(Point2D), and
+ via the Point2D.__required_keys__ and Point2D.__optional_keys__ frozensets.
TypedDict supports an additional equivalent form::
Point2D = TypedDict('Point2D', {'x': int, 'y': int, 'label': str})
By default, all keys must be present in a TypedDict. It is possible
- to override this by specifying totality::
+ to override this by using the NotRequired and Required special forms::
+
+ class Point2D(TypedDict):
+ x: int # the "x" key must always be present (Required is the default)
+ y: NotRequired[int] # the "y" key can be omitted
+
+ This means that a Point2D TypedDict can have the "y" key omitted, but the "x" key must be present.
+ Items are required by default, so the Required special form is not necessary in this example.
+ In addition, the total argument to the TypedDict function can be used to make all items not required::
class Point2D(TypedDict, total=False):
x: int
@@ -3331,16 +3338,8 @@ class Point2D(TypedDict, total=False):
This means that a Point2D TypedDict can have any of the keys omitted. A type
checker is only expected to support a literal False or True as the value of
the total argument. True is the default, and makes all items defined in the
- class body be required.
-
- The Required and NotRequired special forms can also be used to mark
- individual keys as being required or not required::
-
- class Point2D(TypedDict):
- x: int # the "x" key must always be present (Required is the default)
- y: NotRequired[int] # the "y" key can be omitted
-
- See PEP 655 for more details on Required and NotRequired.
+ class body be required. The Required special form can be used to mark individual
+ keys as required in a total=False TypedDict.
The ReadOnly special form can be used
to mark individual keys as immutable for type checkers::
@@ -3374,7 +3373,7 @@ class Point3D(Point2D):
by default, and it may not be used with the closed argument at the same
time.
- See PEP 728 for more information about closed and extra_items.
+ See PEPs 589, 655, 705, and 728 for more information.
"""
ns = {'__annotations__': dict(fields)}
module = _caller()
@@ -3404,7 +3403,7 @@ class Movie(TypedDict, total=False):
year: int
m = Movie(
- title='The Matrix', # typechecker error if key is omitted
+ title='The Matrix', # type checker error if key is omitted
year=1999,
)
@@ -3426,7 +3425,7 @@ class Movie(TypedDict):
year: NotRequired[int]
m = Movie(
- title='The Matrix', # typechecker error if key is omitted
+ title='The Matrix', # type checker error if key is omitted
year=1999,
)
"""
@@ -3446,7 +3445,7 @@ class Movie(TypedDict):
def mutate_movie(m: Movie) -> None:
m["year"] = 1992 # allowed
- m["title"] = "The Matrix" # typechecker error
+ m["title"] = "The Matrix" # type checker error
There is no runtime checking for this property.
"""
@@ -3533,8 +3532,8 @@ class IO(Generic[AnyStr]):
classes (text vs. binary, read vs. write vs. read/write,
append-only, unbuffered). The TextIO and BinaryIO subclasses
below capture the distinctions between text vs. binary, which is
- pervasive in the interface; however we currently do not offer a
- way to track the other distinctions in the type system.
+ pervasive in the interface. For more precise types, define a custom
+ Protocol.
"""
__slots__ = ()
@@ -3624,7 +3623,7 @@ def __exit__(self, type, value, traceback, /) -> None:
class BinaryIO(IO[bytes]):
- """Typed version of the return of open() in binary mode."""
+ """Typed approximation of the return of open() in binary mode."""
__slots__ = ()
@@ -3638,7 +3637,7 @@ def __enter__(self) -> BinaryIO:
class TextIO(IO[str]):
- """Typed version of the return of open() in text mode."""
+ """Typed approximation of the return of open() in text mode."""
__slots__ = ()
@@ -3705,7 +3704,7 @@ def dataclass_transform(
field_specifiers: tuple[type[Any] | Callable[..., Any], ...] = (),
**kwargs: Any,
) -> _IdentityCallable:
- """Decorator to mark an object as providing dataclass-like behaviour.
+ """Decorator to mark an object as providing dataclass-like behavior.
The decorator can be applied to a function, class, or metaclass.
diff --git a/Misc/NEWS.d/next/Library/2026-05-18-07-44-46.gh-issue-149995.vvtFHn.rst b/Misc/NEWS.d/next/Library/2026-05-18-07-44-46.gh-issue-149995.vvtFHn.rst
new file mode 100644
index 00000000000000..a8e412b578da37
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-05-18-07-44-46.gh-issue-149995.vvtFHn.rst
@@ -0,0 +1 @@
+Update various docstrings in :mod:`typing`.
From 6b3c418b4c504729cc7cef9188a18fdad7727c68 Mon Sep 17 00:00:00 2001
From: adang1345
Date: Fri, 22 May 2026 00:20:08 -0700
Subject: [PATCH 10/15] gh-133998: Fix gzip file creation when time is out of
range (GH-134278)
Co-authored-by: Serhiy Storchaka
---
Doc/library/gzip.rst | 10 ++++++---
Lib/gzip.py | 10 +++++++--
Lib/test/test_gzip.py | 21 +++++++++++++++++++
...-05-19-20-29-35.gh-issue-133998.KmElUw.rst | 5 +++++
4 files changed, 41 insertions(+), 5 deletions(-)
create mode 100644 Misc/NEWS.d/next/Library/2025-05-19-20-29-35.gh-issue-133998.KmElUw.rst
diff --git a/Doc/library/gzip.rst b/Doc/library/gzip.rst
index ed9fdaf1d727b0..2c667ddc522399 100644
--- a/Doc/library/gzip.rst
+++ b/Doc/library/gzip.rst
@@ -108,9 +108,13 @@ The module defines the following items:
is no compression. The default is ``9``.
The optional *mtime* argument is the timestamp requested by gzip. The time
- is in Unix format, i.e., seconds since 00:00:00 UTC, January 1, 1970.
- If *mtime* is omitted or ``None``, the current time is used. Use *mtime* = 0
- to generate a compressed stream that does not depend on creation time.
+ is in Unix format, i.e., seconds since 00:00:00 UTC, January 1, 1970. Set
+ *mtime* to ``0`` to generate a compressed stream that does not depend on
+ creation time. If *mtime* is omitted or ``None``, the current time is used;
+ however, if the current time is outside the range 00:00:00 UTC, January 1,
+ 1970 through 06:28:15 UTC, February 7, 2106, or explicitly passed *mtime*
+ argument is outside the range ``0`` to ``2**32-1``, then the value ``0``
+ is used instead.
See below for the :attr:`mtime` attribute that is set when decompressing.
diff --git a/Lib/gzip.py b/Lib/gzip.py
index 1e05f43c0c9e24..8720acc4db9976 100644
--- a/Lib/gzip.py
+++ b/Lib/gzip.py
@@ -188,8 +188,10 @@ def __init__(self, filename=None, mode=None,
The optional mtime argument is the timestamp requested by gzip. The time
is in Unix format, i.e., seconds since 00:00:00 UTC, January 1, 1970.
- If mtime is omitted or None, the current time is used. Use mtime = 0
- to generate a compressed stream that does not depend on creation time.
+ Set mtime to 0 to generate a compressed stream that does not depend on
+ creation time. If mtime is omitted or None, the current time is used.
+ If the resulting mtime is outside the range 0 to 2**32-1, then the
+ value 0 is used instead.
"""
@@ -295,6 +297,8 @@ def _write_gzip_header(self, compresslevel):
mtime = self._write_mtime
if mtime is None:
mtime = time.time()
+ if not 0 <= mtime < 2**32:
+ mtime = 0
write32u(self.fileobj, int(mtime))
if compresslevel == _COMPRESS_LEVEL_BEST:
xfl = b'\002'
@@ -663,6 +667,8 @@ def compress(data, compresslevel=_COMPRESS_LEVEL_TRADEOFF, *, mtime=0):
gzip_data = zlib.compress(data, level=compresslevel, wbits=31)
if mtime is None:
mtime = time.time()
+ if not 0 <= mtime < 2**32:
+ mtime = 0
# Reuse gzip header created by zlib, replace mtime and OS byte for
# consistency.
header = struct.pack("<4sLBB", gzip_data, int(mtime), gzip_data[8], 255)
diff --git a/Lib/test/test_gzip.py b/Lib/test/test_gzip.py
index b3b7c8f87e4f9f..cafac9d3c8be6e 100644
--- a/Lib/test/test_gzip.py
+++ b/Lib/test/test_gzip.py
@@ -10,6 +10,7 @@
import sys
import unittest
from subprocess import PIPE, Popen
+from unittest import mock
from test.support import catch_unraisable_exception
from test.support import force_not_colorized_test_class, import_helper
from test.support import os_helper
@@ -350,6 +351,26 @@ def test_mtime(self):
self.assertEqual(dataRead, data1)
self.assertEqual(fRead.mtime, mtime)
+ def test_mtime_out_of_range(self):
+ for mtime in (-1, 2**32):
+ with gzip.GzipFile(self.filename, 'w', mtime=mtime) as fWrite:
+ fWrite.write(data1)
+ with gzip.GzipFile(self.filename) as fRead:
+ fRead.read(1)
+ self.assertEqual(fRead.mtime, 0)
+ datac = gzip.compress(data1, mtime=mtime)
+ with gzip.GzipFile(fileobj=io.BytesIO(datac)) as fRead:
+ fRead.read(1)
+ self.assertEqual(fRead.mtime, 0)
+
+ for mtime in (-1, 2**32):
+ with mock.patch('time.time', return_value=float(mtime)):
+ with gzip.GzipFile(self.filename, 'w') as fWrite:
+ fWrite.write(data1)
+ with gzip.GzipFile(self.filename) as fRead:
+ fRead.read(1)
+ self.assertEqual(fRead.mtime, 0)
+
def test_metadata(self):
mtime = 123456789
diff --git a/Misc/NEWS.d/next/Library/2025-05-19-20-29-35.gh-issue-133998.KmElUw.rst b/Misc/NEWS.d/next/Library/2025-05-19-20-29-35.gh-issue-133998.KmElUw.rst
new file mode 100644
index 00000000000000..77d92628beefac
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-05-19-20-29-35.gh-issue-133998.KmElUw.rst
@@ -0,0 +1,5 @@
+Fix :exc:`struct.error` exception when creating a file with
+:class:`gzip.GzipFile` or compressing data with :func:`gzip.compress`
+if the system time is outside the range 00:00:00 UTC, January 1, 1970
+through 06:28:15 UTC, February 7, 2106, or explicitly passed *mtime*
+argument is outside the range ``0`` to ``2**32-1``.
From 0f8420523e913f0af1a959e9ae6b4170a6947bf9 Mon Sep 17 00:00:00 2001
From: Serhiy Storchaka
Date: Fri, 22 May 2026 12:17:34 +0300
Subject: [PATCH 11/15] gh-137571: Protect against possible UnboundLocalError
in gzip._GzipReader.read() (GH-150222)
This has not been observed in practice, but we cannot be 100% sure that
it will not happen with some weird gzip data.
---
Lib/gzip.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/Lib/gzip.py b/Lib/gzip.py
index 8720acc4db9976..0713b922522ee1 100644
--- a/Lib/gzip.py
+++ b/Lib/gzip.py
@@ -610,10 +610,10 @@ def read(self, size=-1):
# Read a chunk of data from the file
if self._decompressor.needs_input:
buf = self._fp.read(READ_BUFFER_SIZE)
- uncompress = self._decompressor.decompress(buf, size)
else:
- uncompress = self._decompressor.decompress(b"", size)
+ buf = b""
+ uncompress = self._decompressor.decompress(buf, size)
if self._decompressor.unused_data != b"":
# Prepend the already read bytes to the fileobj so they can
# be seen by _read_eof() and _read_gzip_header()
From 68a27652debc7fb2a3bcf60741ca92142cfc071e Mon Sep 17 00:00:00 2001
From: Mia Albert
Date: Fri, 22 May 2026 07:32:14 -0400
Subject: [PATCH 12/15] gh-149902: Remove dead packaging docs link and add a
new section for external resources (#150030)
Co-authored-by: Stan Ulbrych
Co-authored-by: Ned Batchelder
Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com>
---
Doc/tools/templates/indexcontent.html | 152 ++++++++++++++++----------
1 file changed, 97 insertions(+), 55 deletions(-)
diff --git a/Doc/tools/templates/indexcontent.html b/Doc/tools/templates/indexcontent.html
index 544cc4234f441e..4982bcbfe3673c 100644
--- a/Doc/tools/templates/indexcontent.html
+++ b/Doc/tools/templates/indexcontent.html
@@ -14,6 +14,35 @@
+
{%- endblock -%}
{% block body %}
{{ docstitle|e }}
@@ -21,63 +50,76 @@ {{ docstitle|e }}
{% trans %}Welcome! This is the official documentation for Python {{ release }}.{% endtrans %}
{% trans %}Documentation sections:{% endtrans %}
-
- |
- {% trans %}What's new in Python {{ version }}?{% endtrans %}
- {% trans whatsnew_index=pathto("whatsnew/index") %}Or all "What's new" documents since Python 2.0{% endtrans %}
- {% trans %}Tutorial{% endtrans %}
- {% trans %}Start here: a tour of Python's syntax and features{% endtrans %}
- {% trans %}Library reference{% endtrans %}
- {% trans %}Standard library and builtins{% endtrans %}
- {% trans %}Language reference{% endtrans %}
- {% trans %}Syntax and language elements{% endtrans %}
- {% trans %}Python setup and usage{% endtrans %}
- {% trans %}How to install, configure, and use Python{% endtrans %}
- {% trans %}Python HOWTOs{% endtrans %}
- {% trans %}In-depth topic manuals{% endtrans %}
- |
- {% trans %}Installing Python modules{% endtrans %}
- {% trans %}Third-party modules and PyPI.org{% endtrans %}
- {% trans %}Distributing Python modules{% endtrans %}
- {% trans %}Publishing modules for use by other people{% endtrans %}
- {% trans %}Extending and embedding{% endtrans %}
- {% trans %}For C/C++ programmers{% endtrans %}
- {% trans %}Python's C API{% endtrans %}
- {% trans %}C API reference{% endtrans %}
- {% trans %}FAQs{% endtrans %}
- {% trans %}Frequently asked questions (with answers!){% endtrans %}
- {% trans %}Deprecations{% endtrans %}
- {% trans %}Deprecated functionality{% endtrans %}
- |
-
+
+
+ {% trans %}Other resources:{% endtrans %}
+
{% trans %}Indices, glossary, and search:{% endtrans %}
-
- |
- {% trans %}Global module index{% endtrans %}
- {% trans %}All modules and libraries{% endtrans %}
- {% trans %}General index{% endtrans %}
- {% trans %}All functions, classes, and terms{% endtrans %}
- {% trans %}Glossary{% endtrans %}
- {% trans %}Terms explained{% endtrans %}
- |
- {% trans %}Search page{% endtrans %}
- {% trans %}Search this documentation{% endtrans %}
- {% trans %}Complete table of contents{% endtrans %}
- {% trans %}Lists all sections and subsections{% endtrans %}
- |
-
+
{% trans %}Project information:{% endtrans %}
-
+
{% endblock %}
From 344f35d404408e115212bf834aa15443fe7ae693 Mon Sep 17 00:00:00 2001
From: Marin Misur <50244077+ellaellela@users.noreply.github.com>
Date: Fri, 22 May 2026 14:14:25 +0200
Subject: [PATCH 13/15] gh-91372: Add mtime to gzip.open() (GH-32310)
---
Doc/library/gzip.rst | 13 ++++++++++---
Doc/whatsnew/3.16.rst | 8 ++++++++
Lib/gzip.py | 7 ++++---
Lib/test/test_gzip.py | 11 +++++++++++
.../2022-04-04-17-58-05.bpo-47216.gPyPte.rst | 2 ++
5 files changed, 35 insertions(+), 6 deletions(-)
create mode 100644 Misc/NEWS.d/next/Library/2022-04-04-17-58-05.bpo-47216.gPyPte.rst
diff --git a/Doc/library/gzip.rst b/Doc/library/gzip.rst
index 2c667ddc522399..9211e5f18c6b6e 100644
--- a/Doc/library/gzip.rst
+++ b/Doc/library/gzip.rst
@@ -28,7 +28,7 @@ Note that additional file formats which can be decompressed by the
The module defines the following items:
-.. function:: open(filename, mode='rb', compresslevel=6, encoding=None, errors=None, newline=None)
+.. function:: open(filename, mode='rb', compresslevel=6, encoding=None, errors=None, newline=None, *, mtime=None)
Open a gzip-compressed file in binary or text mode, returning a :term:`file
object`.
@@ -43,9 +43,12 @@ The module defines the following items:
The *compresslevel* argument is an integer from 0 to 9, as for the
:class:`GzipFile` constructor.
+ The keyword-only argument *mtime* represents a Unix timestamp.
+
For binary mode, this function is equivalent to the :class:`GzipFile`
- constructor: ``GzipFile(filename, mode, compresslevel)``. In this case, the
- *encoding*, *errors* and *newline* arguments must not be provided.
+ constructor: ``GzipFile(filename, mode, compresslevel, mtime=mtime)``.
+ In this case, the *encoding*, *errors* and *newline* arguments must not
+ be provided.
For text mode, a :class:`GzipFile` object is created, and wrapped in an
:class:`io.TextIOWrapper` instance with the specified encoding, error
@@ -66,6 +69,10 @@ The module defines the following items:
It is the default level used by most compression tools and a better
tradeoff between speed and performance.
+ .. versionchanged:: next
+ Added keyword-only argument *mtime* which is passed to the class
+ constructor of :class:`~gzip.GzipFile`.
+
.. exception:: BadGzipFile
An exception raised for invalid gzip files. It inherits from :exc:`OSError`.
diff --git a/Doc/whatsnew/3.16.rst b/Doc/whatsnew/3.16.rst
index 2e5342e4f02053..f1ff4fcf9bafde 100644
--- a/Doc/whatsnew/3.16.rst
+++ b/Doc/whatsnew/3.16.rst
@@ -86,6 +86,14 @@ New modules
Improved modules
================
+
+gzip
+----
+
+* :func:`gzip.open` now accepts an optional argument ``mtime``
+ which is passed on to the constructor of the :class:`~gzip.GzipFile` class.
+ (Contributed by Marin Misur in :gh:`91372`.)
+
os
--
diff --git a/Lib/gzip.py b/Lib/gzip.py
index 0713b922522ee1..14c47fc86f217a 100644
--- a/Lib/gzip.py
+++ b/Lib/gzip.py
@@ -31,7 +31,7 @@
def open(filename, mode="rb", compresslevel=_COMPRESS_LEVEL_TRADEOFF,
- encoding=None, errors=None, newline=None):
+ encoding=None, errors=None, newline=None, *, mtime=None):
"""Open a gzip-compressed file in binary or text mode.
The filename argument can be an actual filename (a str or bytes object), or
@@ -63,9 +63,10 @@ def open(filename, mode="rb", compresslevel=_COMPRESS_LEVEL_TRADEOFF,
gz_mode = mode.replace("t", "")
if isinstance(filename, (str, bytes, os.PathLike)):
- binary_file = GzipFile(filename, gz_mode, compresslevel)
+ binary_file = GzipFile(filename, gz_mode, compresslevel, mtime=mtime)
elif hasattr(filename, "read") or hasattr(filename, "write"):
- binary_file = GzipFile(None, gz_mode, compresslevel, filename)
+ binary_file = GzipFile(None, gz_mode, compresslevel, filename,
+ mtime=mtime)
else:
raise TypeError("filename must be a str or bytes object, or a file")
diff --git a/Lib/test/test_gzip.py b/Lib/test/test_gzip.py
index cafac9d3c8be6e..8bc8e507683cb5 100644
--- a/Lib/test/test_gzip.py
+++ b/Lib/test/test_gzip.py
@@ -351,6 +351,17 @@ def test_mtime(self):
self.assertEqual(dataRead, data1)
self.assertEqual(fRead.mtime, mtime)
+ def test_mtime_with_open(self):
+ mtime = 123456789
+ with gzip.open(self.filename, "wb", mtime=mtime) as fWrite:
+ fWrite.write(data1)
+ with gzip.open(self.filename, "rb") as fRead:
+ self.assertTrue(hasattr(fRead, 'mtime'))
+ self.assertIsNone(fRead.mtime)
+ dataRead = fRead.read()
+ self.assertEqual(dataRead, data1)
+ self.assertEqual(fRead.mtime, mtime)
+
def test_mtime_out_of_range(self):
for mtime in (-1, 2**32):
with gzip.GzipFile(self.filename, 'w', mtime=mtime) as fWrite:
diff --git a/Misc/NEWS.d/next/Library/2022-04-04-17-58-05.bpo-47216.gPyPte.rst b/Misc/NEWS.d/next/Library/2022-04-04-17-58-05.bpo-47216.gPyPte.rst
new file mode 100644
index 00000000000000..066e17c1c6e06a
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2022-04-04-17-58-05.bpo-47216.gPyPte.rst
@@ -0,0 +1,2 @@
+Added *mtime* option to :func:`gzip.open`, which will be passed
+to the constructor of :class:`~gzip.GzipFile`.
From 403322e15165d9ae294ec035aa7813a332d99abc Mon Sep 17 00:00:00 2001
From: Victor Stinner
Date: Fri, 22 May 2026 15:47:38 +0200
Subject: [PATCH 14/15] gh-149879: Fix test_c_locale_coercion on Cygwin
(#150250)
---
Lib/test/test_c_locale_coercion.py | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/Lib/test/test_c_locale_coercion.py b/Lib/test/test_c_locale_coercion.py
index 340bec3c71b68f..52323a0ec0ac2b 100644
--- a/Lib/test/test_c_locale_coercion.py
+++ b/Lib/test/test_c_locale_coercion.py
@@ -47,9 +47,8 @@
# FS encoding is UTF-8 on macOS
EXPECTED_C_LOCALE_FS_ENCODING = "utf-8"
elif sys.platform == "cygwin":
- # Cygwin defaults to using C.UTF-8
- # TODO: Work out a robust dynamic test for this that doesn't rely on
- # CPython's own locale handling machinery
+ DEFAULT_LOCALE_IS_C = False
+ DEFAULT_ENCODING = "utf-8"
EXPECT_COERCION_IN_DEFAULT_LOCALE = False
elif sys.platform == "vxworks":
# VxWorks defaults to using UTF-8 for all system interfaces
From 7b50ffcaabe83f0936f96012c390fb37cf8b71f2 Mon Sep 17 00:00:00 2001
From: Brett Cannon
Date: Fri, 22 May 2026 07:21:16 -0700
Subject: [PATCH 15/15] Remove 'expat' dependency for Linux in `Misc/Brewfile`
(#150118)
---
Misc/Brewfile | 1 -
1 file changed, 1 deletion(-)
diff --git a/Misc/Brewfile b/Misc/Brewfile
index b62c6e943989a0..c799f099957f75 100644
--- a/Misc/Brewfile
+++ b/Misc/Brewfile
@@ -7,7 +7,6 @@ brew "xz"
brew "zstd"
brew "bzip2" if OS.linux?
-brew "expat" if OS.linux?
brew "libedit" if OS.linux?
brew "libffi" if OS.linux?
brew "ncurses" if OS.linux?