From 4040bf0cf8857b8bb382be457f744728b2368b43 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Sat, 9 May 2026 16:24:48 -0700 Subject: [PATCH 1/3] Add more Python 3.15 stdlib updates --- stdlib/@tests/stubtest_allowlists/py315.txt | 25 ---------- stdlib/_interpqueues.pyi | 17 ++++++- stdlib/collections/__init__.pyi | 5 ++ stdlib/multiprocessing/forkserver.pyi | 15 +++++- stdlib/sys/__init__.pyi | 25 +++++++++- stdlib/tarfile.pyi | 4 +- stdlib/threading.pyi | 21 +++++++- stdlib/timeit.pyi | 8 +++- stdlib/types.pyi | 53 +++++++++++++++++++-- stdlib/xml/__init__.pyi | 8 +++- stdlib/xml/utils.pyi | 2 + 11 files changed, 143 insertions(+), 40 deletions(-) create mode 100644 stdlib/xml/utils.pyi diff --git a/stdlib/@tests/stubtest_allowlists/py315.txt b/stdlib/@tests/stubtest_allowlists/py315.txt index 25032c0a2219..7ad11b599c5c 100644 --- a/stdlib/@tests/stubtest_allowlists/py315.txt +++ b/stdlib/@tests/stubtest_allowlists/py315.txt @@ -12,8 +12,6 @@ _frozen_importlib_external.SourceFileLoader.source_to_code _frozen_importlib_external.SourceLoader.source_to_code _frozen_importlib_external._LoaderBasics.load_module _frozen_importlib_external.cache_from_source -_interpqueues.create -_interpqueues.put _pyrepl.base_eventqueue _pyrepl.commands _pyrepl.completing_reader @@ -62,8 +60,6 @@ codecs.namereplace_errors codecs.replace_errors codecs.strict_errors codecs.xmlcharrefreplace_errors -collections.Counter.__ixor__ -collections.Counter.__xor__ concurrent.interpreters._crossinterp.UNBOUND_ERROR concurrent.interpreters._crossinterp.UNBOUND_REMOVE concurrent.interpreters._crossinterp.UnboundItem.singleton @@ -137,7 +133,6 @@ math.issubnormal math.signbit multiprocessing.context.BaseContext.set_forkserver_preload multiprocessing.forkserver.ForkServer.set_forkserver_preload -multiprocessing.forkserver.main multiprocessing.managers.BaseListProxy.clear multiprocessing.managers.BaseListProxy.copy multiprocessing.managers._BaseDictProxy.__iter__ @@ -209,19 +204,8 @@ sre_parse symtable.symtable sys.__jit sys._monitoring -sys.abi_info -sys.get_lazy_imports -sys.get_lazy_imports_filter sys.last_exc -sys.lazy_modules -sys.set_lazy_imports -sys.set_lazy_imports_filter threading.Condition.locked -threading.__all__ -threading.concurrent_tee -threading.serialize_iterator -threading.synchronized_iterator -timeit.Timer.autorange tkinter.Grid.content tkinter.Grid.grid_content tkinter.Image.__iter__ @@ -238,12 +222,6 @@ tkinter.Text.search tkinter.Text.search_all tkinter.font.Font.__iter__ tkinter.simpledialog.__all__ -types.AsyncGeneratorType.ag_state -types.CodeType.co_lnotab -types.CoroutineType.cr_state -types.FrameLocalsProxyType -types.GeneratorType.gi_state -types.LazyImportType types.MappingProxyType.get types.SimpleNamespace.__delattr__ types.SimpleNamespace.__setattr__ @@ -251,7 +229,6 @@ types.UnionType.__class_getitem__ types.UnionType.__mro_entries__ types.UnionType.__name__ types.UnionType.__qualname__ -types.__all__ typing.LiteralString typing.NewType.__mro_entries__ typing.ParamSpec.__mro_entries__ @@ -274,6 +251,4 @@ urllib.parse.urlsplit urllib.parse.urlunparse urllib.parse.urlunsplit xml.etree.ElementTree.__all__ -xml.is_valid_name -xml.utils zipimport.zipimporter.load_module diff --git a/stdlib/_interpqueues.pyi b/stdlib/_interpqueues.pyi index 5586b9bd7b20..94605e4c0dda 100644 --- a/stdlib/_interpqueues.pyi +++ b/stdlib/_interpqueues.pyi @@ -1,3 +1,4 @@ +import sys from typing import Any, Literal, SupportsIndex, TypeAlias _UnboundOp: TypeAlias = Literal[1, 2, 3] @@ -6,7 +7,13 @@ class QueueError(RuntimeError): ... class QueueNotFoundError(QueueError): ... def bind(qid: SupportsIndex) -> None: ... -def create(maxsize: SupportsIndex, fmt: SupportsIndex, unboundop: _UnboundOp) -> int: ... + +if sys.version_info >= (3, 15): + def create(maxsize: SupportsIndex, unboundop: SupportsIndex = -1, fallback: SupportsIndex = -1) -> int: ... + +else: + def create(maxsize: SupportsIndex, fmt: SupportsIndex, unboundop: _UnboundOp) -> int: ... + def destroy(qid: SupportsIndex) -> None: ... def get(qid: SupportsIndex) -> tuple[Any, int, _UnboundOp | None]: ... def get_count(qid: SupportsIndex) -> int: ... @@ -14,5 +21,11 @@ def get_maxsize(qid: SupportsIndex) -> int: ... def get_queue_defaults(qid: SupportsIndex) -> tuple[int, _UnboundOp]: ... def is_full(qid: SupportsIndex) -> bool: ... def list_all() -> list[tuple[int, int, _UnboundOp]]: ... -def put(qid: SupportsIndex, obj: Any, fmt: SupportsIndex, unboundop: _UnboundOp) -> None: ... + +if sys.version_info >= (3, 15): + def put(qid: SupportsIndex, obj: Any, unboundop: SupportsIndex = -1, fallback: SupportsIndex = -1) -> None: ... + +else: + def put(qid: SupportsIndex, obj: Any, fmt: SupportsIndex, unboundop: _UnboundOp) -> None: ... + def release(qid: SupportsIndex) -> None: ... diff --git a/stdlib/collections/__init__.pyi b/stdlib/collections/__init__.pyi index e835060468ca..699d4544bf4a 100644 --- a/stdlib/collections/__init__.pyi +++ b/stdlib/collections/__init__.pyi @@ -315,6 +315,9 @@ class Counter(dict[_T, int], Generic[_T]): def __sub__(self, other: Counter[_T]) -> Counter[_T]: ... def __and__(self, other: Counter[_T]) -> Counter[_T]: ... def __or__(self, other: Counter[_S]) -> Counter[_T | _S]: ... # type: ignore[override] + if sys.version_info >= (3, 15): + def __xor__(self, other: Counter[_S]) -> Counter[_T | _S]: ... # type: ignore[override] + def __pos__(self) -> Counter[_T]: ... def __neg__(self) -> Counter[_T]: ... # several type: ignores because __iadd__ is supposedly incompatible with __add__, etc. @@ -322,6 +325,8 @@ class Counter(dict[_T, int], Generic[_T]): def __isub__(self, other: SupportsItems[_T, int]) -> Self: ... def __iand__(self, other: SupportsItems[_T, int]) -> Self: ... def __ior__(self, other: SupportsItems[_T, int]) -> Self: ... # type: ignore[override,misc] + if sys.version_info >= (3, 15): + def __ixor__(self, other: Counter[_T]) -> Self: ... # type: ignore[misc] # The pure-Python implementations of the "views" classes # These are exposed at runtime in `collections/__init__.py` diff --git a/stdlib/multiprocessing/forkserver.pyi b/stdlib/multiprocessing/forkserver.pyi index 570b492e9daf..f0af1ce7a827 100644 --- a/stdlib/multiprocessing/forkserver.pyi +++ b/stdlib/multiprocessing/forkserver.pyi @@ -15,7 +15,20 @@ class ForkServer: def connect_to_new_process(self, fds: Sequence[int]) -> tuple[int, int]: ... def ensure_running(self) -> None: ... -if sys.version_info >= (3, 14): +if sys.version_info >= (3, 15): + def main( + listener_fd: int | None, + alive_r: FileDescriptorLike, + preload: Sequence[str], + main_path: str | None = None, + sys_path: list[str] | None = None, + *, + sys_argv: list[str] | None = None, + authkey_r: int | None = None, + on_error: str = "ignore", + ) -> None: ... + +elif sys.version_info >= (3, 14): # `sys_argv` parameter added in Python 3.14.3 def main( listener_fd: int | None, diff --git a/stdlib/sys/__init__.pyi b/stdlib/sys/__init__.pyi index 7976656e5359..51c6d1deeecc 100644 --- a/stdlib/sys/__init__.pyi +++ b/stdlib/sys/__init__.pyi @@ -4,18 +4,30 @@ from _typeshed.importlib import MetaPathFinderProtocol, PathEntryFinderProtocol from builtins import object as _object from collections.abc import AsyncGenerator, Callable, Sequence from io import TextIOWrapper -from types import FrameType, ModuleType, TracebackType +from types import FrameType, ModuleType, SimpleNamespace, TracebackType from typing import Any, Final, Literal, NoReturn, Protocol, TextIO, TypeAlias, TypeVar, final, overload, type_check_only from typing_extensions import LiteralString, deprecated _T = TypeVar("_T") +_LazyImportMode: TypeAlias = Literal["normal", "all", "none"] +_LazyImportFilter: TypeAlias = Callable[[str, str, tuple[str, ...] | None], bool] # see https://github.com/python/typeshed/issues/8513#issue-1333671093 for the rationale behind this alias _ExitCode: TypeAlias = str | int | None +if sys.version_info >= (3, 15): + @type_check_only + class _AbiInfo(SimpleNamespace): + pointer_bits: int + free_threaded: bool + debug: bool + byteorder: Literal["little", "big"] + # ----- sys variables ----- if sys.platform != "win32": abiflags: str +if sys.version_info >= (3, 15): + abi_info: _AbiInfo argv: list[str] base_exec_prefix: str base_prefix: str @@ -40,6 +52,8 @@ maxsize: int maxunicode: int meta_path: list[MetaPathFinderProtocol] modules: dict[str, ModuleType] +if sys.version_info >= (3, 15): + lazy_modules: dict[str, set[str]] orig_argv: list[str] path: list[str] path_hooks: list[Callable[[str], PathEntryFinderProtocol]] @@ -376,6 +390,11 @@ if sys.platform != "win32": def getfilesystemencoding() -> LiteralString: ... def getfilesystemencodeerrors() -> LiteralString: ... + +if sys.version_info >= (3, 15): + def get_lazy_imports() -> _LazyImportMode: ... + def get_lazy_imports_filter() -> _LazyImportFilter | None: ... + def getrefcount(object: Any, /) -> int: ... def getrecursionlimit() -> int: ... def getsizeof(obj: object, default: int = ...) -> int: ... @@ -486,6 +505,10 @@ def set_coroutine_origin_tracking_depth(depth: int) -> None: ... def set_int_max_str_digits(maxdigits: int) -> None: ... def get_int_max_str_digits() -> int: ... +if sys.version_info >= (3, 15): + def set_lazy_imports(mode: _LazyImportMode) -> None: ... + def set_lazy_imports_filter(filter: _LazyImportFilter | None) -> None: ... + if sys.version_info >= (3, 12): if sys.version_info >= (3, 13): def getunicodeinternedsize(*, _only_immortal: bool = False) -> int: ... diff --git a/stdlib/tarfile.pyi b/stdlib/tarfile.pyi index a046fa3e4e1f..9bc738c9fd7b 100644 --- a/stdlib/tarfile.pyi +++ b/stdlib/tarfile.pyi @@ -132,9 +132,8 @@ class TarFile: errorlevel: Literal[0, 1, 2] offset: int # undocumented extraction_filter: _FilterFunction | None - if sys.version_info >= (3, 13): - stream: bool if sys.version_info >= (3, 15): + stream: bool def __init__( self, name: StrOrBytesPath | None = None, @@ -154,6 +153,7 @@ class TarFile: mtime: float | None = None, ) -> None: ... elif sys.version_info >= (3, 13): + stream: bool def __init__( self, name: StrOrBytesPath | None = None, diff --git a/stdlib/threading.pyi b/stdlib/threading.pyi index bd9bf7693824..6b51b424cee6 100644 --- a/stdlib/threading.pyi +++ b/stdlib/threading.pyi @@ -2,11 +2,11 @@ import _thread import sys from _thread import _ExceptHookArgs, get_native_id as get_native_id from _typeshed import ProfileFunction, TraceFunction -from collections.abc import Callable, Iterable, Mapping +from collections.abc import Callable, Iterable, Iterator, Mapping from contextvars import Context from types import TracebackType from typing import Any, Final, TypeVar, final -from typing_extensions import deprecated +from typing_extensions import Self, deprecated _T = TypeVar("_T") @@ -42,6 +42,9 @@ __all__ = [ if sys.version_info >= (3, 12): __all__ += ["setprofile_all_threads", "settrace_all_threads"] +if sys.version_info >= (3, 15): + __all__ += ["concurrent_tee", "serialize_iterator", "synchronized_iterator"] + _profile_hook: ProfileFunction | None def active_count() -> int: ... @@ -62,6 +65,20 @@ if sys.version_info >= (3, 12): def gettrace() -> TraceFunction | None: ... def getprofile() -> ProfileFunction | None: ... + +if sys.version_info >= (3, 15): + @final + class serialize_iterator(Iterator[_T]): + def __init__(self, iterable: Iterable[_T]) -> None: ... + def __iter__(self) -> Self: ... + def __next__(self) -> _T: ... + def send(self, value: Any, /) -> _T: ... + def throw(self, typ: type[BaseException], val: BaseException | object = ..., tb: TracebackType | None = ...) -> _T: ... + def close(self) -> None: ... + + def synchronized_iterator(func: Callable[..., Iterable[_T]]) -> Callable[..., Iterator[_T]]: ... + def concurrent_tee(iterable: Iterable[_T], n: int = 2) -> tuple[Iterator[_T], ...]: ... + def stack_size(size: int = 0, /) -> int: ... TIMEOUT_MAX: Final[float] diff --git a/stdlib/timeit.pyi b/stdlib/timeit.pyi index 9ce00952c882..cc2b2027a820 100644 --- a/stdlib/timeit.pyi +++ b/stdlib/timeit.pyi @@ -1,3 +1,4 @@ +import sys import time from collections.abc import Callable, Sequence from typing import IO, Any, TypeAlias @@ -20,7 +21,12 @@ class Timer: def print_exc(self, file: IO[str] | None = None) -> None: ... def timeit(self, number: int = 1000000) -> float: ... def repeat(self, repeat: int = 5, number: int = 1000000) -> list[float]: ... - def autorange(self, callback: Callable[[int, float], object] | None = None) -> tuple[int, float]: ... + if sys.version_info >= (3, 15): + def autorange( + self, callback: Callable[[int, float], object] | None = None, target_time: float = 0.2 + ) -> tuple[int, float]: ... + else: + def autorange(self, callback: Callable[[int, float], object] | None = None) -> tuple[int, float]: ... def timeit( stmt: _Stmt = "pass", diff --git a/stdlib/types.pyi b/stdlib/types.pyi index a67a3eda6574..ed4a285d09ea 100644 --- a/stdlib/types.pyi +++ b/stdlib/types.pyi @@ -12,6 +12,7 @@ from collections.abc import ( Iterator, KeysView, Mapping, + MutableMapping, MutableSequence, ValuesView, ) @@ -62,6 +63,9 @@ if sys.version_info >= (3, 12): if sys.version_info >= (3, 13): __all__ += ["CapsuleType"] +if sys.version_info >= (3, 15): + __all__ += ["FrameLocalsProxyType", "LazyImportType"] + # Note, all classes "defined" here require special handling. _T1 = TypeVar("_T1") @@ -149,9 +153,13 @@ class CodeType: def co_name(self) -> str: ... @property def co_firstlineno(self) -> int: ... - @property - @deprecated("Deprecated since Python 3.10; will be removed in Python 3.15. Use `CodeType.co_lines()` instead.") - def co_lnotab(self) -> bytes: ... + if sys.version_info >= (3, 15): + pass + else: + @property + @deprecated("Deprecated since Python 3.10; will be removed in Python 3.15. Use `CodeType.co_lines()` instead.") + def co_lnotab(self) -> bytes: ... + @property def co_freevars(self) -> tuple[str, ...]: ... @property @@ -360,6 +368,9 @@ class GeneratorType(Generator[_YieldT_co, _SendT_contra, _ReturnT_co]): if sys.version_info >= (3, 11): @property def gi_suspended(self) -> bool: ... + if sys.version_info >= (3, 15): + @property + def gi_state(self) -> Literal["GEN_CREATED", "GEN_SUSPENDED", "GEN_RUNNING", "GEN_CLOSED"]: ... __name__: str __qualname__: str def __iter__(self) -> Self: ... @@ -389,6 +400,9 @@ class AsyncGeneratorType(AsyncGenerator[_YieldT_co, _SendT_contra]): if sys.version_info >= (3, 12): @property def ag_suspended(self) -> bool: ... + if sys.version_info >= (3, 15): + @property + def ag_state(self) -> Literal["AGEN_CREATED", "AGEN_SUSPENDED", "AGEN_RUNNING", "AGEN_CLOSED"]: ... def __aiter__(self) -> Self: ... def __anext__(self) -> Coroutine[Any, Any, _YieldT_co]: ... @@ -423,6 +437,9 @@ class CoroutineType(Coroutine[_YieldT_co, _SendT_nd_contra, _ReturnT_nd_co]): if sys.version_info >= (3, 11): @property def cr_suspended(self) -> bool: ... + if sys.version_info >= (3, 15): + @property + def cr_state(self) -> Literal["CORO_CREATED", "CORO_SUSPENDED", "CORO_RUNNING", "CORO_CLOSED"]: ... def close(self) -> None: ... def __await__(self) -> Generator[Any, None, _ReturnT_nd_co]: ... @@ -552,8 +569,12 @@ class FrameType: # An `int | None` annotation here causes too many false-positive errors, so applying `int | Any`. @property def f_lineno(self) -> int | MaybeNone: ... - @property - def f_locals(self) -> dict[str, Any]: ... + if sys.version_info >= (3, 15): + @property + def f_locals(self) -> FrameLocalsProxyType | dict[str, Any]: ... + else: + @property + def f_locals(self) -> dict[str, Any]: ... f_trace: Callable[[FrameType, str, Any], Any] | None f_trace_lines: bool f_trace_opcodes: bool @@ -562,6 +583,28 @@ class FrameType: @property def f_generator(self) -> GeneratorType[Any, Any, Any] | CoroutineType[Any, Any, Any] | None: ... +if sys.version_info >= (3, 15): + @final + class FrameLocalsProxyType(MutableMapping[str, Any]): + def __new__(cls, frame: FrameType, /) -> Self: ... + def __getitem__(self, key: str, /) -> Any: ... + def __setitem__(self, key: str, value: Any, /) -> None: ... + def __delitem__(self, key: str, /) -> None: ... + def __iter__(self) -> Iterator[str]: ... + def __len__(self) -> int: ... + def __contains__(self, key: object, /) -> bool: ... + def __reversed__(self) -> Iterator[str]: ... + def copy(self) -> dict[str, Any]: ... + def pop(self, key: str, default: Any = ..., /) -> Any: ... + def setdefault(self, key: str, default: Any = ..., /) -> Any: ... + def update(self, object: SupportsKeysAndGetItem[str, Any] | Iterable[tuple[str, Any]], /) -> None: ... # type: ignore[override] + + @final + class LazyImportType: + @property + def __name__(self) -> str: ... + def resolve(self) -> Any: ... + @final class GetSetDescriptorType: @property diff --git a/stdlib/xml/__init__.pyi b/stdlib/xml/__init__.pyi index 7a240965136e..fa47f9eccbd2 100644 --- a/stdlib/xml/__init__.pyi +++ b/stdlib/xml/__init__.pyi @@ -1,3 +1,9 @@ # At runtime, listing submodules in __all__ without them being imported is # valid, and causes them to be included in a star import. See #6523 -__all__ = ["dom", "parsers", "sax", "etree"] # noqa: F822 # pyright: ignore[reportUnsupportedDunderAll] +import sys + +if sys.version_info >= (3, 15): + __all__ = ["dom", "parsers", "sax", "etree", "is_valid_name"] # noqa: F822 # pyright: ignore[reportUnsupportedDunderAll] + from xml.utils import is_valid_name as is_valid_name, is_valid_text as is_valid_text +else: + __all__ = ["dom", "parsers", "sax", "etree"] # noqa: F822 # pyright: ignore[reportUnsupportedDunderAll] diff --git a/stdlib/xml/utils.pyi b/stdlib/xml/utils.pyi new file mode 100644 index 000000000000..1c3bb877a7cc --- /dev/null +++ b/stdlib/xml/utils.pyi @@ -0,0 +1,2 @@ +def is_valid_name(name: str) -> bool: ... +def is_valid_text(data: str) -> bool: ... From d298529319cb4e51ce08475a0cd92460a090c566 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Sat, 9 May 2026 16:27:04 -0700 Subject: [PATCH 2/3] Mark xml.utils as Python 3.15 only --- stdlib/VERSIONS | 1 + 1 file changed, 1 insertion(+) diff --git a/stdlib/VERSIONS b/stdlib/VERSIONS index 5487195b7165..a715f4203b65 100644 --- a/stdlib/VERSIONS +++ b/stdlib/VERSIONS @@ -335,6 +335,7 @@ wsgiref: 3.0- wsgiref.types: 3.11- xdrlib: 3.0-3.12 xml: 3.0- +xml.utils: 3.15- xmlrpc: 3.0- xxlimited: 3.2- zipapp: 3.5- From 0a347376e7443863dd016d3849231ab9aa50aaec Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Sat, 9 May 2026 18:08:36 -0700 Subject: [PATCH 3/3] Address review on 3.15 types changes --- stdlib/tarfile.pyi | 4 ++-- stdlib/types.pyi | 4 +--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/stdlib/tarfile.pyi b/stdlib/tarfile.pyi index 9bc738c9fd7b..a046fa3e4e1f 100644 --- a/stdlib/tarfile.pyi +++ b/stdlib/tarfile.pyi @@ -132,8 +132,9 @@ class TarFile: errorlevel: Literal[0, 1, 2] offset: int # undocumented extraction_filter: _FilterFunction | None - if sys.version_info >= (3, 15): + if sys.version_info >= (3, 13): stream: bool + if sys.version_info >= (3, 15): def __init__( self, name: StrOrBytesPath | None = None, @@ -153,7 +154,6 @@ class TarFile: mtime: float | None = None, ) -> None: ... elif sys.version_info >= (3, 13): - stream: bool def __init__( self, name: StrOrBytesPath | None = None, diff --git a/stdlib/types.pyi b/stdlib/types.pyi index ed4a285d09ea..d2a8335b0908 100644 --- a/stdlib/types.pyi +++ b/stdlib/types.pyi @@ -153,9 +153,7 @@ class CodeType: def co_name(self) -> str: ... @property def co_firstlineno(self) -> int: ... - if sys.version_info >= (3, 15): - pass - else: + if sys.version_info < (3, 15): @property @deprecated("Deprecated since Python 3.10; will be removed in Python 3.15. Use `CodeType.co_lines()` instead.") def co_lnotab(self) -> bytes: ...