From 10c9d8cfced4057bc818f11ba3a713b257862435 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Mon, 15 Nov 2021 13:45:24 +0000 Subject: [PATCH] Add `@final` to many unsubclassable stdlib classes (#6299) --- stdlib/_thread.pyi | 3 +++ stdlib/_tkinter.pyi | 4 +++- stdlib/_weakref.pyi | 3 +++ stdlib/_winapi.pyi | 4 ++-- stdlib/bz2.pyi | 4 +++- stdlib/collections/__init__.pyi | 4 ++++ stdlib/datetime.pyi | 2 ++ stdlib/functools.pyi | 3 ++- stdlib/lzma.pyi | 4 +++- stdlib/operator.pyi | 5 ++++- stdlib/os/__init__.pyi | 6 +++++- stdlib/pickle.pyi | 2 ++ stdlib/posix.pyi | 3 +++ stdlib/pyexpat/__init__.pyi | 2 ++ stdlib/time.pyi | 2 ++ stdlib/typing.pyi | 4 +++- stdlib/winreg.pyi | 2 ++ 17 files changed, 48 insertions(+), 9 deletions(-) diff --git a/stdlib/_thread.pyi b/stdlib/_thread.pyi index 242570312..2f4252981 100644 --- a/stdlib/_thread.pyi +++ b/stdlib/_thread.pyi @@ -2,6 +2,7 @@ import sys from threading import Thread from types import TracebackType from typing import Any, Callable, NoReturn, Optional, Tuple, Type +from typing_extensions import final error = RuntimeError @@ -9,6 +10,7 @@ def _count() -> int: ... _dangling: Any +@final class LockType: def acquire(self, blocking: bool = ..., timeout: float = ...) -> bool: ... def release(self) -> None: ... @@ -29,6 +31,7 @@ TIMEOUT_MAX: float if sys.version_info >= (3, 8): def get_native_id() -> int: ... # only available on some platforms + @final class _ExceptHookArgs(Tuple[Type[BaseException], Optional[BaseException], Optional[TracebackType], Optional[Thread]]): @property def exc_type(self) -> Type[BaseException]: ... diff --git a/stdlib/_tkinter.pyi b/stdlib/_tkinter.pyi index 378b04202..e97edf5b4 100644 --- a/stdlib/_tkinter.pyi +++ b/stdlib/_tkinter.pyi @@ -1,5 +1,5 @@ from typing import Any -from typing_extensions import Literal +from typing_extensions import Literal, final # _tkinter is meant to be only used internally by tkinter, but some tkinter # functions e.g. return _tkinter.Tcl_Obj objects. Tcl_Obj represents a Tcl @@ -14,6 +14,7 @@ from typing_extensions import Literal # >>> text.tag_add('foo', '1.0', 'end') # >>> text.tag_ranges('foo') # (, ) +@final class Tcl_Obj: string: str # str(tclobj) returns this typename: str @@ -37,6 +38,7 @@ class TclError(Exception): ... # # eval always returns str because _tkinter_tkapp_eval_impl in _tkinter.c calls # Tkapp_UnicodeResult, and it returns a string when it succeeds. +@final class TkappType: # Please keep in sync with tkinter.Tk def call(self, __command: Any, *args: Any) -> Any: ... diff --git a/stdlib/_weakref.pyi b/stdlib/_weakref.pyi index 006836f85..dcaef25b3 100644 --- a/stdlib/_weakref.pyi +++ b/stdlib/_weakref.pyi @@ -1,5 +1,6 @@ import sys from typing import Any, Callable, Generic, TypeVar, overload +from typing_extensions import final if sys.version_info >= (3, 9): from types import GenericAlias @@ -7,9 +8,11 @@ if sys.version_info >= (3, 9): _C = TypeVar("_C", bound=Callable[..., Any]) _T = TypeVar("_T") +@final class CallableProxyType(Generic[_C]): # "weakcallableproxy" def __getattr__(self, attr: str) -> Any: ... +@final class ProxyType(Generic[_T]): # "weakproxy" def __getattr__(self, attr: str) -> Any: ... diff --git a/stdlib/_winapi.pyi b/stdlib/_winapi.pyi index f1a12be18..83089d485 100644 --- a/stdlib/_winapi.pyi +++ b/stdlib/_winapi.pyi @@ -1,6 +1,6 @@ import sys from typing import Any, NoReturn, Sequence, overload -from typing_extensions import Literal +from typing_extensions import Literal, final CREATE_NEW_CONSOLE: int CREATE_NEW_PROCESS_GROUP: int @@ -126,7 +126,7 @@ def WriteFile(handle: int, buffer: bytes, overlapped: Literal[True]) -> tuple[Ov def WriteFile(handle: int, buffer: bytes, overlapped: Literal[False] = ...) -> tuple[int, int]: ... @overload def WriteFile(handle: int, buffer: bytes, overlapped: int | bool) -> tuple[Any, int]: ... - +@final class Overlapped: event: int def GetOverlappedResult(self, __wait: bool) -> tuple[int, int]: ... diff --git a/stdlib/bz2.pyi b/stdlib/bz2.pyi index e7b57ec54..c49832759 100644 --- a/stdlib/bz2.pyi +++ b/stdlib/bz2.pyi @@ -3,7 +3,7 @@ import sys from _compression import BaseStream from _typeshed import ReadableBuffer, Self, StrOrBytesPath, WriteableBuffer from typing import IO, Any, Iterable, Protocol, TextIO, TypeVar, overload -from typing_extensions import Literal, SupportsIndex +from typing_extensions import Literal, SupportsIndex, final # The following attributes and methods are optional: # def fileno(self) -> int: ... @@ -118,11 +118,13 @@ class BZ2File(BaseStream, IO[bytes]): def write(self, data: ReadableBuffer) -> int: ... def writelines(self, seq: Iterable[ReadableBuffer]) -> None: ... +@final class BZ2Compressor(object): def __init__(self, compresslevel: int = ...) -> None: ... def compress(self, __data: bytes) -> bytes: ... def flush(self) -> bytes: ... +@final class BZ2Decompressor(object): def decompress(self, data: bytes, max_length: int = ...) -> bytes: ... @property diff --git a/stdlib/collections/__init__.pyi b/stdlib/collections/__init__.pyi index 777752b9a..0dad79cb1 100644 --- a/stdlib/collections/__init__.pyi +++ b/stdlib/collections/__init__.pyi @@ -2,6 +2,7 @@ import sys from _typeshed import Self from builtins import _dict_items, _dict_keys, _dict_values from typing import Any, Dict, Generic, NoReturn, Tuple, Type, TypeVar, overload +from typing_extensions import final if sys.version_info >= (3, 10): from typing import Callable, Iterable, Iterator, Mapping, MutableMapping, MutableSequence, Reversible, Sequence @@ -240,12 +241,15 @@ class Counter(Dict[_T, int], Generic[_T]): def __iand__(self, other: Counter[_T]) -> Counter[_T]: ... def __ior__(self, other: Counter[_T]) -> Counter[_T]: ... # type: ignore +@final class _OrderedDictKeysView(_dict_keys[_KT_co, _VT_co], Reversible[_KT_co]): def __reversed__(self) -> Iterator[_KT_co]: ... +@final class _OrderedDictItemsView(_dict_items[_KT_co, _VT_co], Reversible[Tuple[_KT_co, _VT_co]]): def __reversed__(self) -> Iterator[tuple[_KT_co, _VT_co]]: ... +@final class _OrderedDictValuesView(_dict_values[_KT_co, _VT_co], Reversible[_VT_co], Generic[_KT_co, _VT_co]): def __reversed__(self) -> Iterator[_VT_co]: ... diff --git a/stdlib/datetime.pyi b/stdlib/datetime.pyi index 746643d84..12e53921e 100644 --- a/stdlib/datetime.pyi +++ b/stdlib/datetime.pyi @@ -1,6 +1,7 @@ import sys from time import struct_time from typing import ClassVar, NamedTuple, SupportsAbs, Type, TypeVar, overload +from typing_extensions import final _S = TypeVar("_S") @@ -16,6 +17,7 @@ class tzinfo: # Alias required to avoid name conflicts with date(time).tzinfo. _tzinfo = tzinfo +@final class timezone(tzinfo): utc: ClassVar[timezone] min: ClassVar[timezone] diff --git a/stdlib/functools.pyi b/stdlib/functools.pyi index dff8d4d06..8b3ac58ac 100644 --- a/stdlib/functools.pyi +++ b/stdlib/functools.pyi @@ -2,7 +2,7 @@ import sys import types from _typeshed import SupportsItems, SupportsLessThan from typing import Any, Callable, Generic, Hashable, Iterable, NamedTuple, Sequence, Set, Sized, Tuple, Type, TypeVar, overload -from typing_extensions import ParamSpec +from typing_extensions import ParamSpec, final if sys.version_info >= (3, 9): from types import GenericAlias @@ -24,6 +24,7 @@ class _CacheInfo(NamedTuple): maxsize: int currsize: int +@final class _lru_cache_wrapper(Generic[_P, _T]): # type: ignore __wrapped__: Callable[_P, _T] # type: ignore def __call__(self, *args: _P.args, **kwargs: _P.kwargs) -> _T: ... # type: ignore diff --git a/stdlib/lzma.pyi b/stdlib/lzma.pyi index 7a26d1529..e1da3024c 100644 --- a/stdlib/lzma.pyi +++ b/stdlib/lzma.pyi @@ -1,7 +1,7 @@ import io from _typeshed import ReadableBuffer, Self, StrOrBytesPath from typing import IO, Any, Mapping, Sequence, TextIO, Union, overload -from typing_extensions import Literal +from typing_extensions import Literal, final _OpenBinaryWritingMode = Literal["w", "wb", "x", "xb", "a", "ab"] _OpenTextWritingMode = Literal["wt", "xt", "at"] @@ -40,6 +40,7 @@ PRESET_DEFAULT: int PRESET_EXTREME: int # from _lzma.c +@final class LZMADecompressor(object): def __init__(self, format: int | None = ..., memlimit: int | None = ..., filters: _FilterChain | None = ...) -> None: ... def decompress(self, data: bytes, max_length: int = ...) -> bytes: ... @@ -53,6 +54,7 @@ class LZMADecompressor(object): def needs_input(self) -> bool: ... # from _lzma.c +@final class LZMACompressor(object): def __init__( self, format: int | None = ..., check: int = ..., preset: int | None = ..., filters: _FilterChain | None = ... diff --git a/stdlib/operator.pyi b/stdlib/operator.pyi index 5cbfda7a8..bb8e23733 100644 --- a/stdlib/operator.pyi +++ b/stdlib/operator.pyi @@ -11,6 +11,7 @@ from typing import ( TypeVar, overload, ) +from typing_extensions import final _T = TypeVar("_T") _T_co = TypeVar("_T_co", covariant=True) @@ -115,7 +116,7 @@ def __setitem__(a: MutableSequence[_T], b: slice, c: Sequence[_T]) -> None: ... @overload def __setitem__(a: MutableMapping[_K, _V], b: _K, c: _V) -> None: ... def length_hint(__obj: Any, __default: int = ...) -> int: ... - +@final class attrgetter(Generic[_T_co]): @overload def __new__(cls, attr: str) -> attrgetter[Any]: ... @@ -129,6 +130,7 @@ class attrgetter(Generic[_T_co]): def __new__(cls, attr: str, *attrs: str) -> attrgetter[Tuple[Any, ...]]: ... def __call__(self, obj: Any) -> _T_co: ... +@final class itemgetter(Generic[_T_co]): @overload def __new__(cls, item: Any) -> itemgetter[Any]: ... @@ -142,6 +144,7 @@ class itemgetter(Generic[_T_co]): def __new__(cls, item: Any, *items: Any) -> itemgetter[Tuple[Any, ...]]: ... def __call__(self, obj: Any) -> _T_co: ... +@final class methodcaller: def __init__(self, __name: str, *args: Any, **kwargs: Any) -> None: ... def __call__(self, obj: Any) -> Any: ... diff --git a/stdlib/os/__init__.pyi b/stdlib/os/__init__.pyi index 07d609804..7a4588a97 100644 --- a/stdlib/os/__init__.pyi +++ b/stdlib/os/__init__.pyi @@ -37,7 +37,7 @@ from typing import ( overload, runtime_checkable, ) -from typing_extensions import Literal +from typing_extensions import Literal, final from . import path as _path @@ -262,6 +262,7 @@ if sys.platform != "win32": TMP_MAX: int # Undocumented, but used by tempfile # ----- os classes (structures) ----- +@final class stat_result: # For backward compatibility, the return value of stat() is also # accessible as a tuple of at least 10 integers giving the most important @@ -314,6 +315,7 @@ class PathLike(Protocol[_AnyStr_co]): _FdOrAnyPath = Union[int, StrOrBytesPath] +@final class DirEntry(Generic[AnyStr]): # This is what the scandir iterator yields # The constructor is hidden @@ -334,6 +336,7 @@ if sys.platform != "win32": _Tuple11Int = Tuple[int, int, int, int, int, int, int, int, int, int, int] if sys.version_info >= (3, 7): # f_fsid was added in https://github.com/python/cpython/pull/4571 + @final class statvfs_result(_Tuple10Int): # Unix only def __new__(cls, seq: _Tuple10Int | _Tuple11Int, dict: dict[str, int] = ...) -> statvfs_result: ... n_fields: int @@ -566,6 +569,7 @@ if sys.platform != "win32": def readv(__fd: int, __buffers: Sequence[bytearray]) -> int: ... def writev(__fd: int, __buffers: Sequence[bytes]) -> int: ... +@final class terminal_size(Tuple[int, int]): columns: int lines: int diff --git a/stdlib/pickle.pyi b/stdlib/pickle.pyi index da6c5d117..cef1ffe9e 100644 --- a/stdlib/pickle.pyi +++ b/stdlib/pickle.pyi @@ -1,5 +1,6 @@ import sys from typing import Any, Callable, ClassVar, Iterable, Iterator, Mapping, Optional, Protocol, Tuple, Type, Union +from typing_extensions import final HIGHEST_PROTOCOL: int DEFAULT_PROTOCOL: int @@ -15,6 +16,7 @@ class _WritableFileobj(Protocol): if sys.version_info >= (3, 8): # TODO: holistic design for buffer interface (typing.Buffer?) + @final class PickleBuffer: # buffer must be a buffer-providing object def __init__(self, buffer: Any) -> None: ... diff --git a/stdlib/posix.pyi b/stdlib/posix.pyi index 4993371b5..14cea87cd 100644 --- a/stdlib/posix.pyi +++ b/stdlib/posix.pyi @@ -2,7 +2,9 @@ import sys from _typeshed import StrOrBytesPath from os import PathLike, _ExecEnv, _ExecVArgs, stat_result as stat_result from typing import Any, Iterable, NamedTuple, Sequence, Tuple, overload +from typing_extensions import final +@final class uname_result(NamedTuple): sysname: str nodename: str @@ -10,6 +12,7 @@ class uname_result(NamedTuple): version: str machine: str +@final class times_result(NamedTuple): user: float system: float diff --git a/stdlib/pyexpat/__init__.pyi b/stdlib/pyexpat/__init__.pyi index d1993496a..6a3d6cd56 100644 --- a/stdlib/pyexpat/__init__.pyi +++ b/stdlib/pyexpat/__init__.pyi @@ -2,6 +2,7 @@ import pyexpat.errors as errors import pyexpat.model as model from _typeshed import SupportsRead from typing import Any, Callable, Optional, Tuple +from typing_extensions import final EXPAT_VERSION: str # undocumented version_info: tuple[int, int, int] # undocumented @@ -21,6 +22,7 @@ XML_PARAM_ENTITY_PARSING_ALWAYS: int _Model = Tuple[int, int, Optional[str], Tuple[Any, ...]] +@final class XMLParserType(object): def Parse(self, __data: str | bytes, __isfinal: bool = ...) -> int: ... def ParseFile(self, __file: SupportsRead[bytes]) -> int: ... diff --git a/stdlib/time.pyi b/stdlib/time.pyi index 29bfebed6..bf370d68e 100644 --- a/stdlib/time.pyi +++ b/stdlib/time.pyi @@ -1,6 +1,7 @@ import sys from types import SimpleNamespace from typing import Any, NamedTuple, Tuple +from typing_extensions import final _TimeTuple = Tuple[int, int, int, int, int, int, int, int, int] @@ -48,6 +49,7 @@ class _struct_time(NamedTuple): @property def n_unnamed_fields(self) -> int: ... +@final class struct_time(_struct_time): def __init__( self, diff --git a/stdlib/typing.pyi b/stdlib/typing.pyi index 148ac021c..bd9c9d873 100644 --- a/stdlib/typing.pyi +++ b/stdlib/typing.pyi @@ -2,7 +2,7 @@ import collections # Needed by aliases like DefaultDict, see mypy issue 2986 import sys from abc import ABCMeta, abstractmethod from types import BuiltinFunctionType, CodeType, FrameType, FunctionType, MethodType, ModuleType, TracebackType -from typing_extensions import Literal as _Literal, ParamSpec as _ParamSpec +from typing_extensions import Literal as _Literal, ParamSpec as _ParamSpec, final as _final if sys.version_info >= (3, 7): from types import MethodDescriptorType, MethodWrapperType, WrapperDescriptorType @@ -545,6 +545,7 @@ class TextIO(IO[str]): class ByteString(Sequence[int], metaclass=ABCMeta): ... +@_final class Match(Generic[AnyStr]): pos: int endpos: int @@ -588,6 +589,7 @@ class Match(Generic[AnyStr]): if sys.version_info >= (3, 9): def __class_getitem__(cls, item: Any) -> GenericAlias: ... +@_final class Pattern(Generic[AnyStr]): flags: int groupindex: Mapping[str, int] diff --git a/stdlib/winreg.pyi b/stdlib/winreg.pyi index aeba48630..5fff1104e 100644 --- a/stdlib/winreg.pyi +++ b/stdlib/winreg.pyi @@ -1,6 +1,7 @@ from _typeshed import Self from types import TracebackType from typing import Any, Type, Union +from typing_extensions import final _KeyType = Union[HKEYType, int] @@ -88,6 +89,7 @@ REG_WHOLE_HIVE_VOLATILE: int # undocumented error = OSError # Though this class has a __name__ of PyHKEY, it's exposed as HKEYType for some reason +@final class HKEYType: def __bool__(self) -> bool: ... def __int__(self) -> int: ...