From 190b3ef2cbf7ccae03c2a47ffe36ea8be2966a97 Mon Sep 17 00:00:00 2001
From: Grant Herman
Date: Tue, 19 May 2026 19:22:28 -0400
Subject: [PATCH 1/9] fix(150075): adding tarinfo offset info before and after
reading the file
Signed-off-by: Grant Herman
---
Lib/tarfile.py | 5 ++++-
Lib/test/test_tarfile.py | 23 +++++++++++++++++++++++
2 files changed, 27 insertions(+), 1 deletion(-)
diff --git a/Lib/tarfile.py b/Lib/tarfile.py
index 87500c726ce9a8..5c39a84b5ce855 100644
--- a/Lib/tarfile.py
+++ b/Lib/tarfile.py
@@ -2360,11 +2360,14 @@ def addfile(self, tarinfo, fileobj=None):
raise ValueError("fileobj not provided for non zero-size regular file")
tarinfo = copy.copy(tarinfo)
-
+ # get current offset
+ tarinfo.offset = self.offset
buf = tarinfo.tobuf(self.format, self.encoding, self.errors)
self.fileobj.write(buf)
self.offset += len(buf)
+ # add original offset to block size
bufsize=self.copybufsize
+ tarinfo.offset_data = self.offset
# If there's data to follow, append it.
if fileobj is not None:
copyfileobj(fileobj, self.fileobj, tarinfo.size, bufsize=bufsize)
diff --git a/Lib/test/test_tarfile.py b/Lib/test/test_tarfile.py
index 02fd9620bcf33d..ca778bd12ab801 100644
--- a/Lib/test/test_tarfile.py
+++ b/Lib/test/test_tarfile.py
@@ -1456,6 +1456,29 @@ class WriteTest(WriteTestBase, unittest.TestCase):
prefix = "w:"
+ def test_addfile_sets_offsets(self):
+ # gh-150075: addfile() must set offset and offset_data on the
+ # TarInfo stored in the archive so they match a subsequent read.
+ data = b"data"
+
+ with tarfile.open(tmpname, self.mode) as tar:
+ t1 = tarfile.TarInfo("test1.txt")
+ t1.size = len(data)
+ tar.addfile(t1, io.BytesIO(data))
+
+ t2 = tarfile.TarInfo("test2.txt")
+ t2.size = len(data)
+ tar.addfile(t2, io.BytesIO(data))
+
+ write_members = tar.getmembers()
+
+ with tarfile.open(tmpname) as tar:
+ read_members = tar.getmembers()
+
+ for w, r in zip(write_members, read_members):
+ self.assertEqual(w.offset, r.offset)
+ self.assertEqual(w.offset_data, r.offset_data)
+
def test_100_char_name(self):
# The name field in a tar header stores strings of at most 100 chars.
# If a string is shorter than 100 chars it has to be padded with '\0',
From 2705a52c17c0ffb52ed3611bf1704f9ceee8e781 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:21:08 +0000
Subject: [PATCH 2/9] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20blu?=
=?UTF-8?q?rb=5Fit.?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../2026-05-21-14-21-06.gh-issue-150075.dLI21Z.rst | 1 +
1 file changed, 1 insertion(+)
create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-05-21-14-21-06.gh-issue-150075.dLI21Z.rst
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-05-21-14-21-06.gh-issue-150075.dLI21Z.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-05-21-14-21-06.gh-issue-150075.dLI21Z.rst
new file mode 100644
index 00000000000000..bf04e688025c71
--- /dev/null
+++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-05-21-14-21-06.gh-issue-150075.dLI21Z.rst
@@ -0,0 +1 @@
+tar.addfile() doesn't set member offsets in 3.15. From reviewing the file it seemed like the copy was not adding the proper offsets to the tarinfo object. The way I fixed this is that I added the classes current offset of when.addFile is called and then added the block size after .tobuf function was called.
From 982749a62f7766bee945915bf395ea8c1b61daca Mon Sep 17 00:00:00 2001
From: Jelle Zijlstra
Date: Thu, 21 May 2026 21:06:42 -0700
Subject: [PATCH 3/9] 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 6807e9d03fc68d0af2fc5a15cef667ef8756a5ca Mon Sep 17 00:00:00 2001
From: adang1345
Date: Fri, 22 May 2026 00:20:08 -0700
Subject: [PATCH 4/9] 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 41f1cdca409eb98984f1a45b94b8ee6d08e2cdc7 Mon Sep 17 00:00:00 2001
From: Serhiy Storchaka
Date: Fri, 22 May 2026 12:17:34 +0300
Subject: [PATCH 5/9] 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 d69206097c654e6f082375edfe47364cf9b05629 Mon Sep 17 00:00:00 2001
From: Mia Albert
Date: Fri, 22 May 2026 07:32:14 -0400
Subject: [PATCH 6/9] 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 808ea130b9ac387724c7c153b2f5026a59f919b5 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 7/9] 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 16d1813873ec2261a42ef04d8d97b92b8915fefc Mon Sep 17 00:00:00 2001
From: Victor Stinner
Date: Fri, 22 May 2026 15:47:38 +0200
Subject: [PATCH 8/9] 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 f26b7bb33d53c240e99548caee3422276a5ba53c Mon Sep 17 00:00:00 2001
From: Brett Cannon
Date: Fri, 22 May 2026 07:21:16 -0700
Subject: [PATCH 9/9] 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?