Skip to content
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

move some stuffs related to _SimpleCData from ctypes/__init__.pyi to _ctypes.pyi, as at runtime. #10118

Merged
merged 3 commits into from
Apr 30, 2023
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
87 changes: 85 additions & 2 deletions stdlib/_ctypes.pyi
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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: ...
88 changes: 10 additions & 78 deletions stdlib/ctypes/__init__.pyi
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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): ...

Expand Down Expand Up @@ -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]):
Expand Down Expand Up @@ -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: ...
5 changes: 4 additions & 1 deletion tests/stubtest_allowlists/py3_common.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -332,7 +334,6 @@ turtle.ScrolledCanvas.onResize
wave.Wave_read.initfp
wave.Wave_write.initfp

_ctypes.Array
_ctypes.CFuncPtr
_ctypes.POINTER
_ctypes.PyObj_FromPtr
Expand All @@ -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
Expand Down Expand Up @@ -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__
Expand Down