diff --git a/stdlib/_ctypes.pyi b/stdlib/_ctypes.pyi index 0ad2fcb571b8..41252bcbf89b 100644 --- a/stdlib/_ctypes.pyi +++ b/stdlib/_ctypes.pyi @@ -1,6 +1,16 @@ import sys -from ctypes import _CArgObject, _PointerLike -from typing_extensions import TypeAlias +from _typeshed import ReadableBuffer, WriteableBuffer +from abc import abstractmethod +from collections.abc import Iterable, Iterator, Mapping +from ctypes import CDLL, _CArgObject, _PointerLike +from typing import Any, Generic, TypeVar, overload +from typing_extensions import Self, TypeAlias + +if sys.version_info >= (3, 9): + from types import GenericAlias + +_T = TypeVar("_T") +_CT = TypeVar("_CT", bound=_CData) FUNCFLAG_CDECL: int FUNCFLAG_PYTHONAPI: int @@ -27,3 +37,76 @@ if sys.platform == "win32": FUNCFLAG_HRESULT: int FUNCFLAG_STDCALL: int + +class _CDataMeta(type): + # By default mypy complains about the following two methods, because strictly speaking cls + # might not be a Type[_CT]. However this can never actually happen, because the only class that + # uses _CDataMeta as its metaclass is _CData. So it's safe to ignore the errors here. + def __mul__(cls: type[_CT], other: int) -> type[Array[_CT]]: ... # type: ignore[misc] # pyright: ignore[reportGeneralTypeIssues] + def __rmul__(cls: type[_CT], other: int) -> type[Array[_CT]]: ... # type: ignore[misc] # pyright: ignore[reportGeneralTypeIssues] + +class _CData(metaclass=_CDataMeta): + _b_base_: int + _b_needsfree_: bool + _objects: Mapping[Any, int] | None + @classmethod + def from_buffer(cls, source: WriteableBuffer, offset: int = ...) -> Self: ... + @classmethod + def from_buffer_copy(cls, source: ReadableBuffer, offset: int = ...) -> Self: ... + @classmethod + def from_address(cls, address: int) -> Self: ... + @classmethod + def from_param(cls, obj: Any) -> Self | _CArgObject: ... + @classmethod + def in_dll(cls, library: CDLL, name: str) -> Self: ... + +class _SimpleCData(Generic[_T], _CData): + value: _T + # The TypeVar can be unsolved here, + # but we can't use overloads without creating many, many mypy false-positive errors + def __init__(self, value: _T = ...) -> None: ... # pyright: ignore[reportInvalidTypeVarUse] + +class Array(Generic[_CT], _CData): + @property + @abstractmethod + def _length_(self) -> int: ... + @_length_.setter + def _length_(self, value: int) -> None: ... + @property + @abstractmethod + def _type_(self) -> type[_CT]: ... + @_type_.setter + def _type_(self, value: type[_CT]) -> None: ... + # Note: only available if _CT == c_char + @property + def raw(self) -> bytes: ... + @raw.setter + def raw(self, value: ReadableBuffer) -> None: ... + value: Any # Note: bytes if _CT == c_char, str if _CT == c_wchar, unavailable otherwise + # TODO These methods cannot be annotated correctly at the moment. + # All of these "Any"s stand for the array's element type, but it's not possible to use _CT + # here, because of a special feature of ctypes. + # By default, when accessing an element of an Array[_CT], the returned object has type _CT. + # However, when _CT is a "simple type" like c_int, ctypes automatically "unboxes" the object + # and converts it to the corresponding Python primitive. For example, when accessing an element + # of an Array[c_int], a Python int object is returned, not a c_int. + # This behavior does *not* apply to subclasses of "simple types". + # If MyInt is a subclass of c_int, then accessing an element of an Array[MyInt] returns + # a MyInt, not an int. + # This special behavior is not easy to model in a stub, so for now all places where + # the array element type would belong are annotated with Any instead. + def __init__(self, *args: Any) -> None: ... + @overload + def __getitem__(self, __key: int) -> Any: ... + @overload + def __getitem__(self, __key: slice) -> list[Any]: ... + @overload + def __setitem__(self, __key: int, __value: Any) -> None: ... + @overload + def __setitem__(self, __key: slice, __value: Iterable[Any]) -> None: ... + def __iter__(self) -> Iterator[Any]: ... + # Can't inherit from Sized because the metaclass conflict between + # Sized and _CData prevents using _CDataMeta. + def __len__(self) -> int: ... + if sys.version_info >= (3, 9): + def __class_getitem__(cls, item: Any) -> GenericAlias: ... diff --git a/stdlib/ctypes/__init__.pyi b/stdlib/ctypes/__init__.pyi index f4f1dae140e6..117561826d40 100644 --- a/stdlib/ctypes/__init__.pyi +++ b/stdlib/ctypes/__init__.pyi @@ -1,10 +1,15 @@ import sys -from _ctypes import RTLD_GLOBAL as RTLD_GLOBAL, RTLD_LOCAL as RTLD_LOCAL -from _typeshed import ReadableBuffer, WriteableBuffer -from abc import abstractmethod -from collections.abc import Callable, Iterable, Iterator, Mapping, Sequence +from _ctypes import ( + RTLD_GLOBAL as RTLD_GLOBAL, + RTLD_LOCAL as RTLD_LOCAL, + Array as Array, + _CData as _CData, + _CDataMeta as _CDataMeta, + _SimpleCData as _SimpleCData, +) +from collections.abc import Callable, Sequence from typing import Any, ClassVar, Generic, TypeVar, overload -from typing_extensions import Self, TypeAlias +from typing_extensions import TypeAlias if sys.version_info >= (3, 9): from types import GenericAlias @@ -65,28 +70,6 @@ if sys.platform == "win32": pydll: LibraryLoader[PyDLL] pythonapi: PyDLL -class _CDataMeta(type): - # By default mypy complains about the following two methods, because strictly speaking cls - # might not be a Type[_CT]. However this can never actually happen, because the only class that - # uses _CDataMeta as its metaclass is _CData. So it's safe to ignore the errors here. - def __mul__(cls: type[_CT], other: int) -> type[Array[_CT]]: ... # type: ignore[misc] # pyright: ignore[reportGeneralTypeIssues] - def __rmul__(cls: type[_CT], other: int) -> type[Array[_CT]]: ... # type: ignore[misc] # pyright: ignore[reportGeneralTypeIssues] - -class _CData(metaclass=_CDataMeta): - _b_base_: int - _b_needsfree_: bool - _objects: Mapping[Any, int] | None - @classmethod - def from_buffer(cls, source: WriteableBuffer, offset: int = ...) -> Self: ... - @classmethod - def from_buffer_copy(cls, source: ReadableBuffer, offset: int = ...) -> Self: ... - @classmethod - def from_address(cls, address: int) -> Self: ... - @classmethod - def from_param(cls, obj: Any) -> Self | _CArgObject: ... - @classmethod - def in_dll(cls, library: CDLL, name: str) -> Self: ... - class _CanCastTo(_CData): ... class _PointerLike(_CanCastTo): ... @@ -190,12 +173,6 @@ if sys.platform == "win32": def wstring_at(address: _CVoidConstPLike, size: int = -1) -> str: ... -class _SimpleCData(Generic[_T], _CData): - value: _T - # The TypeVar can be unsolved here, - # but we can't use overloads without creating many, many mypy false-positive errors - def __init__(self, value: _T = ...) -> None: ... # pyright: ignore[reportInvalidTypeVarUse] - class c_byte(_SimpleCData[int]): ... class c_char(_SimpleCData[bytes]): @@ -259,48 +236,3 @@ class Union(_StructUnionBase): ... class Structure(_StructUnionBase): ... class BigEndianStructure(Structure): ... class LittleEndianStructure(Structure): ... - -class Array(Generic[_CT], _CData): - @property - @abstractmethod - def _length_(self) -> int: ... - @_length_.setter - def _length_(self, value: int) -> None: ... - @property - @abstractmethod - def _type_(self) -> type[_CT]: ... - @_type_.setter - def _type_(self, value: type[_CT]) -> None: ... - # Note: only available if _CT == c_char - @property - def raw(self) -> bytes: ... - @raw.setter - def raw(self, value: ReadableBuffer) -> None: ... - value: Any # Note: bytes if _CT == c_char, str if _CT == c_wchar, unavailable otherwise - # TODO These methods cannot be annotated correctly at the moment. - # All of these "Any"s stand for the array's element type, but it's not possible to use _CT - # here, because of a special feature of ctypes. - # By default, when accessing an element of an Array[_CT], the returned object has type _CT. - # However, when _CT is a "simple type" like c_int, ctypes automatically "unboxes" the object - # and converts it to the corresponding Python primitive. For example, when accessing an element - # of an Array[c_int], a Python int object is returned, not a c_int. - # This behavior does *not* apply to subclasses of "simple types". - # If MyInt is a subclass of c_int, then accessing an element of an Array[MyInt] returns - # a MyInt, not an int. - # This special behavior is not easy to model in a stub, so for now all places where - # the array element type would belong are annotated with Any instead. - def __init__(self, *args: Any) -> None: ... - @overload - def __getitem__(self, __key: int) -> Any: ... - @overload - def __getitem__(self, __key: slice) -> list[Any]: ... - @overload - def __setitem__(self, __key: int, __value: Any) -> None: ... - @overload - def __setitem__(self, __key: slice, __value: Iterable[Any]) -> None: ... - def __iter__(self) -> Iterator[Any]: ... - # Can't inherit from Sized because the metaclass conflict between - # Sized and _CData prevents using _CDataMeta. - def __len__(self) -> int: ... - if sys.version_info >= (3, 9): - def __class_getitem__(cls, item: Any) -> GenericAlias: ... diff --git a/tests/stubtest_allowlists/py3_common.txt b/tests/stubtest_allowlists/py3_common.txt index 5a41f9b9e265..fc50e2aec757 100644 --- a/tests/stubtest_allowlists/py3_common.txt +++ b/tests/stubtest_allowlists/py3_common.txt @@ -78,7 +78,9 @@ csv.Dialect.skipinitialspace csv.DictReader.__init__ # runtime sig has *args but will error if more than 5 positional args are supplied csv.DictWriter.__init__ # runtime sig has *args but will error if more than 5 positional args are supplied ctypes.Array._type_ # _type_ and _length_ are abstract, https://github.com/python/typeshed/pull/6361 +_ctypes.Array._type_ ctypes.Array._length_ +_ctypes.Array._length_ ctypes.CDLL._FuncPtr # None at class level but initialized in __init__ to this value ctypes.memmove # CFunctionType ctypes.memset # CFunctionType @@ -332,7 +334,6 @@ turtle.ScrolledCanvas.onResize wave.Wave_read.initfp wave.Wave_write.initfp -_ctypes.Array _ctypes.CFuncPtr _ctypes.POINTER _ctypes.PyObj_FromPtr @@ -357,6 +358,7 @@ _ctypes.sizeof # ========== ctypes.Array.raw # exists but stubtest can't see it; only available if _CT == c_char +_ctypes.Array.raw _collections_abc.AsyncGenerator.asend # async at runtime, deliberately not in the stub, see #7491. Pos-only differences also. _collections_abc.AsyncGenerator.__anext__ # async at runtime, deliberately not in the stub, see #7491 @@ -605,6 +607,7 @@ wsgiref.handlers.BaseHandler.status # Iterable classes that don't define __iter__ at runtime (usually iterable via __getitem__) # These would ideally be special-cased by type checkers; see https://github.com/python/mypy/issues/2220 +_ctypes.Array.__iter__ ctypes.Array.__iter__ mmap.mmap.__iter__ mmap.mmap.__contains__