Skip to content

Add @type_check_only to stub-only private classes in stdlib #14512

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Aug 3, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion stdlib/_compression.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
from _typeshed import Incomplete, WriteableBuffer
from collections.abc import Callable
from io import DEFAULT_BUFFER_SIZE, BufferedIOBase, RawIOBase
from typing import Any, Protocol
from typing import Any, Protocol, type_check_only

BUFFER_SIZE = DEFAULT_BUFFER_SIZE

@type_check_only
class _Reader(Protocol):
def read(self, n: int, /) -> bytes: ...
def seekable(self) -> bool: ...
Expand Down
3 changes: 3 additions & 0 deletions stdlib/_ctypes.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,10 @@ class _SimpleCData(_CData, Generic[_T], metaclass=_PyCSimpleType):
def __init__(self, value: _T = ...) -> None: ... # pyright: ignore[reportInvalidTypeVarUse]
def __ctypes_from_outparam__(self, /) -> _T: ... # type: ignore[override]

@type_check_only
class _CanCastTo(_CData): ...

@type_check_only
class _PointerLike(_CanCastTo): ...

# This type is not exposed. It calls itself _ctypes.PyCPointerType.
Expand Down
3 changes: 2 additions & 1 deletion stdlib/_gdbm.pyi
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import sys
from _typeshed import ReadOnlyBuffer, StrOrBytesPath
from types import TracebackType
from typing import TypeVar, overload
from typing import TypeVar, overload, type_check_only
from typing_extensions import Self, TypeAlias

if sys.platform != "win32":
Expand All @@ -13,6 +13,7 @@ if sys.platform != "win32":

class error(OSError): ...
# Actual typename gdbm, not exposed by the implementation
@type_check_only
class _gdbm:
def firstkey(self) -> bytes | None: ...
def nextkey(self, key: _KeyType) -> bytes | None: ...
Expand Down
1 change: 1 addition & 0 deletions stdlib/_io.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ class BytesIO(BufferedIOBase, _BufferedIOBase, BinaryIO): # type: ignore[misc]
def readlines(self, size: int | None = None, /) -> list[bytes]: ...
def seek(self, pos: int, whence: int = 0, /) -> int: ...

@type_check_only
class _BufferedReaderStream(Protocol):
def read(self, n: int = ..., /) -> bytes: ...
# Optional: def readall(self) -> bytes: ...
Expand Down
5 changes: 5 additions & 0 deletions stdlib/_msi.pyi
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import sys
from typing import type_check_only

if sys.platform == "win32":
class MSIError(Exception): ...
# Actual typename View, not exposed by the implementation
@type_check_only
class _View:
def Execute(self, params: _Record | None = ...) -> None: ...
def GetColumnInfo(self, kind: int) -> _Record: ...
Expand All @@ -14,6 +16,7 @@ if sys.platform == "win32":
__init__: None # type: ignore[assignment]

# Actual typename SummaryInformation, not exposed by the implementation
@type_check_only
class _SummaryInformation:
def GetProperty(self, field: int) -> int | bytes | None: ...
def GetPropertyCount(self) -> int: ...
Expand All @@ -24,6 +27,7 @@ if sys.platform == "win32":
__init__: None # type: ignore[assignment]

# Actual typename Database, not exposed by the implementation
@type_check_only
class _Database:
def OpenView(self, sql: str) -> _View: ...
def Commit(self) -> None: ...
Expand All @@ -34,6 +38,7 @@ if sys.platform == "win32":
__init__: None # type: ignore[assignment]

# Actual typename Record, not exposed by the implementation
@type_check_only
class _Record:
def GetFieldCount(self) -> int: ...
def GetInteger(self, field: int) -> int: ...
Expand Down
1 change: 1 addition & 0 deletions stdlib/_operator.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ class _SupportsDunderGT(Protocol):
class _SupportsDunderLE(Protocol):
def __le__(self, other: Any, /) -> Any: ...

@type_check_only
class _SupportsDunderGE(Protocol):
def __ge__(self, other: Any, /) -> Any: ...

Expand Down
1 change: 1 addition & 0 deletions stdlib/_pickle.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ from pickle import PickleBuffer as PickleBuffer
from typing import Any, Protocol, type_check_only
from typing_extensions import TypeAlias

@type_check_only
class _ReadableFileobj(Protocol):
def read(self, n: int, /) -> bytes: ...
def readline(self) -> bytes: ...
Expand Down
4 changes: 3 additions & 1 deletion stdlib/_ssl.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,15 @@ from ssl import (
SSLWantWriteError as SSLWantWriteError,
SSLZeroReturnError as SSLZeroReturnError,
)
from typing import Any, ClassVar, Literal, TypedDict, final, overload
from typing import Any, ClassVar, Literal, TypedDict, final, overload, type_check_only
from typing_extensions import NotRequired, Self, TypeAlias

_PasswordType: TypeAlias = Callable[[], str | bytes | bytearray] | str | bytes | bytearray
_PCTRTT: TypeAlias = tuple[tuple[str, str], ...]
_PCTRTTT: TypeAlias = tuple[_PCTRTT, ...]
_PeerCertRetDictType: TypeAlias = dict[str, str | _PCTRTTT | _PCTRTT]

@type_check_only
class _Cipher(TypedDict):
aead: bool
alg_bits: int
Expand All @@ -33,6 +34,7 @@ class _Cipher(TypedDict):
strength_bits: int
symmetric: str

@type_check_only
class _CertInfo(TypedDict):
subject: tuple[tuple[tuple[str, str], ...], ...]
issuer: tuple[tuple[tuple[str, str], ...], ...]
Expand Down
13 changes: 10 additions & 3 deletions stdlib/ast.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ from _ast import (
)
from _typeshed import ReadableBuffer, Unused
from collections.abc import Iterable, Iterator, Sequence
from typing import Any, ClassVar, Generic, Literal, TypedDict, TypeVar as _TypeVar, overload
from typing import Any, ClassVar, Generic, Literal, TypedDict, TypeVar as _TypeVar, overload, type_check_only
from typing_extensions import Self, Unpack, deprecated

if sys.version_info >= (3, 13):
Expand All @@ -20,6 +20,7 @@ if sys.version_info >= (3, 13):
_EndPositionT = typing_extensions.TypeVar("_EndPositionT", int, int | None, default=int | None)

# Corresponds to the names in the `_attributes` class variable which is non-empty in certain AST nodes
@type_check_only
class _Attributes(TypedDict, Generic[_EndPositionT], total=False):
lineno: int
col_offset: int
Expand Down Expand Up @@ -1698,8 +1699,14 @@ if sys.version_info >= (3, 12):
self, *, name: str = ..., default_value: expr | None = ..., **kwargs: Unpack[_Attributes[int]]
) -> Self: ...

class _ABC(type):
def __init__(cls, *args: Unused) -> None: ...
if sys.version_info >= (3, 14):
@type_check_only
class _ABC(type):
def __init__(cls, *args: Unused) -> None: ...

else:
class _ABC(type):
def __init__(cls, *args: Unused) -> None: ...

if sys.version_info < (3, 14):
@deprecated("Replaced by ast.Constant; removed in Python 3.14")
Expand Down
6 changes: 5 additions & 1 deletion stdlib/asyncio/events.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ from collections.abc import Callable, Sequence
from concurrent.futures import Executor
from contextvars import Context
from socket import AddressFamily, SocketKind, _Address, _RetAddress, socket
from typing import IO, Any, Literal, Protocol, TypeVar, overload
from typing import IO, Any, Literal, Protocol, TypeVar, overload, type_check_only
from typing_extensions import Self, TypeAlias, TypeVarTuple, Unpack, deprecated

from . import _AwaitableLike, _CoroutineLike
Expand Down Expand Up @@ -68,6 +68,7 @@ _ExceptionHandler: TypeAlias = Callable[[AbstractEventLoop, _Context], object]
_ProtocolFactory: TypeAlias = Callable[[], BaseProtocol]
_SSLContext: TypeAlias = bool | None | ssl.SSLContext

@type_check_only
class _TaskFactory(Protocol):
def __call__(self, loop: AbstractEventLoop, factory: _CoroutineLike[_T], /) -> Future[_T]: ...

Expand Down Expand Up @@ -599,6 +600,9 @@ class AbstractEventLoop:
@abstractmethod
async def shutdown_default_executor(self) -> None: ...

# This class does not exist at runtime, but stubtest complains if it's marked as
# @type_check_only because it has an alias that does exist at runtime. See mypy#19568.
# @type_check_only
class _AbstractEventLoopPolicy:
@abstractmethod
def get_event_loop(self) -> AbstractEventLoop: ...
Expand Down
3 changes: 2 additions & 1 deletion stdlib/asyncio/format_helpers.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ import sys
import traceback
from collections.abc import Iterable
from types import FrameType, FunctionType
from typing import Any, overload
from typing import Any, overload, type_check_only
from typing_extensions import TypeAlias

@type_check_only
class _HasWrapper:
__wrapper__: _HasWrapper | FunctionType

Expand Down
3 changes: 2 additions & 1 deletion stdlib/asyncio/streams.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import sys
from _typeshed import ReadableBuffer, StrPath
from collections.abc import Awaitable, Callable, Iterable, Sequence, Sized
from types import ModuleType
from typing import Any, Protocol, SupportsIndex
from typing import Any, Protocol, SupportsIndex, type_check_only
from typing_extensions import Self, TypeAlias

from . import events, protocols, transports
Expand All @@ -25,6 +25,7 @@ else:

_ClientConnectedCallback: TypeAlias = Callable[[StreamReader, StreamWriter], Awaitable[None] | None]

@type_check_only
class _ReaduntilBuffer(ReadableBuffer, Sized, Protocol): ...

if sys.version_info >= (3, 10):
Expand Down
5 changes: 4 additions & 1 deletion stdlib/asyncio/tasks.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ from _asyncio import (
_unregister_task as _unregister_task,
)
from collections.abc import AsyncIterator, Awaitable, Coroutine, Generator, Iterable, Iterator
from typing import Any, Literal, Protocol, TypeVar, overload
from typing import Any, Literal, Protocol, TypeVar, overload, type_check_only
from typing_extensions import TypeAlias

from . import _CoroutineLike
Expand Down Expand Up @@ -87,6 +87,7 @@ FIRST_EXCEPTION = concurrent.futures.FIRST_EXCEPTION
ALL_COMPLETED = concurrent.futures.ALL_COMPLETED

if sys.version_info >= (3, 13):
@type_check_only
class _SyncAndAsyncIterator(Iterator[_T_co], AsyncIterator[_T_co], Protocol[_T_co]): ...

def as_completed(fs: Iterable[_FutureLike[_T]], *, timeout: float | None = None) -> _SyncAndAsyncIterator[Future[_T]]: ...
Expand Down Expand Up @@ -445,6 +446,7 @@ elif sys.version_info >= (3, 12):
if sys.version_info >= (3, 12):
_TaskT_co = TypeVar("_TaskT_co", bound=Task[Any], covariant=True)

@type_check_only
class _CustomTaskConstructor(Protocol[_TaskT_co]):
def __call__(
self,
Expand All @@ -457,6 +459,7 @@ if sys.version_info >= (3, 12):
eager_start: bool,
) -> _TaskT_co: ...

@type_check_only
class _EagerTaskFactoryType(Protocol[_TaskT_co]):
def __call__(
self,
Expand Down
13 changes: 11 additions & 2 deletions stdlib/builtins.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -1340,6 +1340,9 @@ class property:
def __set__(self, instance: Any, value: Any, /) -> None: ...
def __delete__(self, instance: Any, /) -> None: ...

# This class does not exist at runtime, but stubtest complains if it's marked as
# @type_check_only because it has an alias that does exist at runtime. See mypy#19568.
# @type_check_only
@final
class _NotImplementedType(Any):
__call__: None
Expand Down Expand Up @@ -1513,7 +1516,7 @@ help: _sitebuiltins._Helper
def hex(number: int | SupportsIndex, /) -> str: ...
def id(obj: object, /) -> int: ...
def input(prompt: object = "", /) -> str: ...

@type_check_only
class _GetItemIterable(Protocol[_T_co]):
def __getitem__(self, i: int, /) -> _T_co: ...

Expand Down Expand Up @@ -1768,7 +1771,7 @@ def open(
opener: _Opener | None = None,
) -> IO[Any]: ...
def ord(c: str | bytes | bytearray, /) -> int: ...

@type_check_only
class _SupportsWriteAndFlush(SupportsWrite[_T_contra], SupportsFlush, Protocol[_T_contra]): ...

@overload
Expand All @@ -1787,12 +1790,15 @@ def print(
_E_contra = TypeVar("_E_contra", contravariant=True)
_M_contra = TypeVar("_M_contra", contravariant=True)

@type_check_only
class _SupportsPow2(Protocol[_E_contra, _T_co]):
def __pow__(self, other: _E_contra, /) -> _T_co: ...

@type_check_only
class _SupportsPow3NoneOnly(Protocol[_E_contra, _T_co]):
def __pow__(self, other: _E_contra, modulo: None = None, /) -> _T_co: ...

@type_check_only
class _SupportsPow3(Protocol[_E_contra, _M_contra, _T_co]):
def __pow__(self, other: _E_contra, modulo: _M_contra, /) -> _T_co: ...

Expand Down Expand Up @@ -1857,9 +1863,11 @@ def repr(obj: object, /) -> str: ...
# and https://github.com/python/typeshed/pull/9151
# on why we don't use `SupportsRound` from `typing.pyi`

@type_check_only
class _SupportsRound1(Protocol[_T_co]):
def __round__(self) -> _T_co: ...

@type_check_only
class _SupportsRound2(Protocol[_T_co]):
def __round__(self, ndigits: int, /) -> _T_co: ...

Expand All @@ -1881,6 +1889,7 @@ def sorted(iterable: Iterable[_T], /, *, key: Callable[[_T], SupportsRichCompari
_AddableT1 = TypeVar("_AddableT1", bound=SupportsAdd[Any, Any])
_AddableT2 = TypeVar("_AddableT2", bound=SupportsAdd[Any, Any])

@type_check_only
class _SupportsSumWithNoDefaultGiven(SupportsAdd[Any, Any], SupportsRAdd[int, Any], Protocol): ...

_SupportsSumNoDefaultT = TypeVar("_SupportsSumNoDefaultT", bound=_SupportsSumWithNoDefaultGiven)
Expand Down
4 changes: 3 additions & 1 deletion stdlib/bz2.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ from _bz2 import BZ2Compressor as BZ2Compressor, BZ2Decompressor as BZ2Decompres
from _typeshed import ReadableBuffer, StrOrBytesPath, WriteableBuffer
from collections.abc import Iterable
from io import TextIOWrapper
from typing import IO, Literal, Protocol, SupportsIndex, overload
from typing import IO, Literal, Protocol, SupportsIndex, overload, type_check_only
from typing_extensions import Self, TypeAlias

if sys.version_info >= (3, 14):
Expand All @@ -16,8 +16,10 @@ __all__ = ["BZ2File", "BZ2Compressor", "BZ2Decompressor", "open", "compress", "d
# The following attributes and methods are optional:
# def fileno(self) -> int: ...
# def close(self) -> object: ...
@type_check_only
class _ReadableFileobj(_Reader, Protocol): ...

@type_check_only
class _WritableFileobj(Protocol):
def write(self, b: bytes, /) -> object: ...
# The following attributes and methods are optional:
Expand Down
12 changes: 11 additions & 1 deletion stdlib/codecs.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ from _codecs import *
from _typeshed import ReadableBuffer
from abc import abstractmethod
from collections.abc import Callable, Generator, Iterable
from typing import Any, BinaryIO, ClassVar, Final, Literal, Protocol, TextIO, overload
from typing import Any, BinaryIO, ClassVar, Final, Literal, Protocol, TextIO, overload, type_check_only
from typing_extensions import Self, TypeAlias

__all__ = [
Expand Down Expand Up @@ -73,16 +73,19 @@ _BufferedEncoding: TypeAlias = Literal[
"utf-8-sig",
]

@type_check_only
class _WritableStream(Protocol):
def write(self, data: bytes, /) -> object: ...
def seek(self, offset: int, whence: int, /) -> object: ...
def close(self) -> object: ...

@type_check_only
class _ReadableStream(Protocol):
def read(self, size: int = ..., /) -> bytes: ...
def seek(self, offset: int, whence: int, /) -> object: ...
def close(self) -> object: ...

@type_check_only
class _Stream(_WritableStream, _ReadableStream, Protocol): ...

# TODO: this only satisfies the most common interface, where
Expand All @@ -91,24 +94,31 @@ class _Stream(_WritableStream, _ReadableStream, Protocol): ...
# There *are* bytes->bytes and str->str encodings in the standard library.
# They were much more common in Python 2 than in Python 3.

@type_check_only
class _Encoder(Protocol):
def __call__(self, input: str, errors: str = ..., /) -> tuple[bytes, int]: ... # signature of Codec().encode

@type_check_only
class _Decoder(Protocol):
def __call__(self, input: ReadableBuffer, errors: str = ..., /) -> tuple[str, int]: ... # signature of Codec().decode

@type_check_only
class _StreamReader(Protocol):
def __call__(self, stream: _ReadableStream, errors: str = ..., /) -> StreamReader: ...

@type_check_only
class _StreamWriter(Protocol):
def __call__(self, stream: _WritableStream, errors: str = ..., /) -> StreamWriter: ...

@type_check_only
class _IncrementalEncoder(Protocol):
def __call__(self, errors: str = ...) -> IncrementalEncoder: ...

@type_check_only
class _IncrementalDecoder(Protocol):
def __call__(self, errors: str = ...) -> IncrementalDecoder: ...

@type_check_only
class _BufferedIncrementalDecoder(Protocol):
def __call__(self, errors: str = ...) -> BufferedIncrementalDecoder: ...

Expand Down
Loading
Loading