Skip to content

Commit ae0c9f9

Browse files
authored
Move _SimpleCData and Array from ctypes/__init__.pyi to _ctypes.pyi (#10118)
1 parent 2c34496 commit ae0c9f9

File tree

3 files changed

+99
-81
lines changed

3 files changed

+99
-81
lines changed

stdlib/_ctypes.pyi

Lines changed: 85 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,16 @@
11
import sys
2-
from ctypes import _CArgObject, _PointerLike
3-
from typing_extensions import TypeAlias
2+
from _typeshed import ReadableBuffer, WriteableBuffer
3+
from abc import abstractmethod
4+
from collections.abc import Iterable, Iterator, Mapping
5+
from ctypes import CDLL, _CArgObject, _PointerLike
6+
from typing import Any, Generic, TypeVar, overload
7+
from typing_extensions import Self, TypeAlias
8+
9+
if sys.version_info >= (3, 9):
10+
from types import GenericAlias
11+
12+
_T = TypeVar("_T")
13+
_CT = TypeVar("_CT", bound=_CData)
414

515
FUNCFLAG_CDECL: int
616
FUNCFLAG_PYTHONAPI: int
@@ -27,3 +37,76 @@ if sys.platform == "win32":
2737

2838
FUNCFLAG_HRESULT: int
2939
FUNCFLAG_STDCALL: int
40+
41+
class _CDataMeta(type):
42+
# By default mypy complains about the following two methods, because strictly speaking cls
43+
# might not be a Type[_CT]. However this can never actually happen, because the only class that
44+
# uses _CDataMeta as its metaclass is _CData. So it's safe to ignore the errors here.
45+
def __mul__(cls: type[_CT], other: int) -> type[Array[_CT]]: ... # type: ignore[misc] # pyright: ignore[reportGeneralTypeIssues]
46+
def __rmul__(cls: type[_CT], other: int) -> type[Array[_CT]]: ... # type: ignore[misc] # pyright: ignore[reportGeneralTypeIssues]
47+
48+
class _CData(metaclass=_CDataMeta):
49+
_b_base_: int
50+
_b_needsfree_: bool
51+
_objects: Mapping[Any, int] | None
52+
@classmethod
53+
def from_buffer(cls, source: WriteableBuffer, offset: int = ...) -> Self: ...
54+
@classmethod
55+
def from_buffer_copy(cls, source: ReadableBuffer, offset: int = ...) -> Self: ...
56+
@classmethod
57+
def from_address(cls, address: int) -> Self: ...
58+
@classmethod
59+
def from_param(cls, obj: Any) -> Self | _CArgObject: ...
60+
@classmethod
61+
def in_dll(cls, library: CDLL, name: str) -> Self: ...
62+
63+
class _SimpleCData(Generic[_T], _CData):
64+
value: _T
65+
# The TypeVar can be unsolved here,
66+
# but we can't use overloads without creating many, many mypy false-positive errors
67+
def __init__(self, value: _T = ...) -> None: ... # pyright: ignore[reportInvalidTypeVarUse]
68+
69+
class Array(Generic[_CT], _CData):
70+
@property
71+
@abstractmethod
72+
def _length_(self) -> int: ...
73+
@_length_.setter
74+
def _length_(self, value: int) -> None: ...
75+
@property
76+
@abstractmethod
77+
def _type_(self) -> type[_CT]: ...
78+
@_type_.setter
79+
def _type_(self, value: type[_CT]) -> None: ...
80+
# Note: only available if _CT == c_char
81+
@property
82+
def raw(self) -> bytes: ...
83+
@raw.setter
84+
def raw(self, value: ReadableBuffer) -> None: ...
85+
value: Any # Note: bytes if _CT == c_char, str if _CT == c_wchar, unavailable otherwise
86+
# TODO These methods cannot be annotated correctly at the moment.
87+
# All of these "Any"s stand for the array's element type, but it's not possible to use _CT
88+
# here, because of a special feature of ctypes.
89+
# By default, when accessing an element of an Array[_CT], the returned object has type _CT.
90+
# However, when _CT is a "simple type" like c_int, ctypes automatically "unboxes" the object
91+
# and converts it to the corresponding Python primitive. For example, when accessing an element
92+
# of an Array[c_int], a Python int object is returned, not a c_int.
93+
# This behavior does *not* apply to subclasses of "simple types".
94+
# If MyInt is a subclass of c_int, then accessing an element of an Array[MyInt] returns
95+
# a MyInt, not an int.
96+
# This special behavior is not easy to model in a stub, so for now all places where
97+
# the array element type would belong are annotated with Any instead.
98+
def __init__(self, *args: Any) -> None: ...
99+
@overload
100+
def __getitem__(self, __key: int) -> Any: ...
101+
@overload
102+
def __getitem__(self, __key: slice) -> list[Any]: ...
103+
@overload
104+
def __setitem__(self, __key: int, __value: Any) -> None: ...
105+
@overload
106+
def __setitem__(self, __key: slice, __value: Iterable[Any]) -> None: ...
107+
def __iter__(self) -> Iterator[Any]: ...
108+
# Can't inherit from Sized because the metaclass conflict between
109+
# Sized and _CData prevents using _CDataMeta.
110+
def __len__(self) -> int: ...
111+
if sys.version_info >= (3, 9):
112+
def __class_getitem__(cls, item: Any) -> GenericAlias: ...

stdlib/ctypes/__init__.pyi

Lines changed: 10 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
import sys
2-
from _ctypes import RTLD_GLOBAL as RTLD_GLOBAL, RTLD_LOCAL as RTLD_LOCAL
3-
from _typeshed import ReadableBuffer, WriteableBuffer
4-
from abc import abstractmethod
5-
from collections.abc import Callable, Iterable, Iterator, Mapping, Sequence
2+
from _ctypes import (
3+
RTLD_GLOBAL as RTLD_GLOBAL,
4+
RTLD_LOCAL as RTLD_LOCAL,
5+
Array as Array,
6+
_CData as _CData,
7+
_CDataMeta as _CDataMeta,
8+
_SimpleCData as _SimpleCData,
9+
)
10+
from collections.abc import Callable, Sequence
611
from typing import Any, ClassVar, Generic, TypeVar, overload
7-
from typing_extensions import Self, TypeAlias
12+
from typing_extensions import TypeAlias
813

914
if sys.version_info >= (3, 9):
1015
from types import GenericAlias
@@ -65,28 +70,6 @@ if sys.platform == "win32":
6570
pydll: LibraryLoader[PyDLL]
6671
pythonapi: PyDLL
6772

68-
class _CDataMeta(type):
69-
# By default mypy complains about the following two methods, because strictly speaking cls
70-
# might not be a Type[_CT]. However this can never actually happen, because the only class that
71-
# uses _CDataMeta as its metaclass is _CData. So it's safe to ignore the errors here.
72-
def __mul__(cls: type[_CT], other: int) -> type[Array[_CT]]: ... # type: ignore[misc] # pyright: ignore[reportGeneralTypeIssues]
73-
def __rmul__(cls: type[_CT], other: int) -> type[Array[_CT]]: ... # type: ignore[misc] # pyright: ignore[reportGeneralTypeIssues]
74-
75-
class _CData(metaclass=_CDataMeta):
76-
_b_base_: int
77-
_b_needsfree_: bool
78-
_objects: Mapping[Any, int] | None
79-
@classmethod
80-
def from_buffer(cls, source: WriteableBuffer, offset: int = ...) -> Self: ...
81-
@classmethod
82-
def from_buffer_copy(cls, source: ReadableBuffer, offset: int = ...) -> Self: ...
83-
@classmethod
84-
def from_address(cls, address: int) -> Self: ...
85-
@classmethod
86-
def from_param(cls, obj: Any) -> Self | _CArgObject: ...
87-
@classmethod
88-
def in_dll(cls, library: CDLL, name: str) -> Self: ...
89-
9073
class _CanCastTo(_CData): ...
9174
class _PointerLike(_CanCastTo): ...
9275

@@ -190,12 +173,6 @@ if sys.platform == "win32":
190173

191174
def wstring_at(address: _CVoidConstPLike, size: int = -1) -> str: ...
192175

193-
class _SimpleCData(Generic[_T], _CData):
194-
value: _T
195-
# The TypeVar can be unsolved here,
196-
# but we can't use overloads without creating many, many mypy false-positive errors
197-
def __init__(self, value: _T = ...) -> None: ... # pyright: ignore[reportInvalidTypeVarUse]
198-
199176
class c_byte(_SimpleCData[int]): ...
200177

201178
class c_char(_SimpleCData[bytes]):
@@ -259,48 +236,3 @@ class Union(_StructUnionBase): ...
259236
class Structure(_StructUnionBase): ...
260237
class BigEndianStructure(Structure): ...
261238
class LittleEndianStructure(Structure): ...
262-
263-
class Array(Generic[_CT], _CData):
264-
@property
265-
@abstractmethod
266-
def _length_(self) -> int: ...
267-
@_length_.setter
268-
def _length_(self, value: int) -> None: ...
269-
@property
270-
@abstractmethod
271-
def _type_(self) -> type[_CT]: ...
272-
@_type_.setter
273-
def _type_(self, value: type[_CT]) -> None: ...
274-
# Note: only available if _CT == c_char
275-
@property
276-
def raw(self) -> bytes: ...
277-
@raw.setter
278-
def raw(self, value: ReadableBuffer) -> None: ...
279-
value: Any # Note: bytes if _CT == c_char, str if _CT == c_wchar, unavailable otherwise
280-
# TODO These methods cannot be annotated correctly at the moment.
281-
# All of these "Any"s stand for the array's element type, but it's not possible to use _CT
282-
# here, because of a special feature of ctypes.
283-
# By default, when accessing an element of an Array[_CT], the returned object has type _CT.
284-
# However, when _CT is a "simple type" like c_int, ctypes automatically "unboxes" the object
285-
# and converts it to the corresponding Python primitive. For example, when accessing an element
286-
# of an Array[c_int], a Python int object is returned, not a c_int.
287-
# This behavior does *not* apply to subclasses of "simple types".
288-
# If MyInt is a subclass of c_int, then accessing an element of an Array[MyInt] returns
289-
# a MyInt, not an int.
290-
# This special behavior is not easy to model in a stub, so for now all places where
291-
# the array element type would belong are annotated with Any instead.
292-
def __init__(self, *args: Any) -> None: ...
293-
@overload
294-
def __getitem__(self, __key: int) -> Any: ...
295-
@overload
296-
def __getitem__(self, __key: slice) -> list[Any]: ...
297-
@overload
298-
def __setitem__(self, __key: int, __value: Any) -> None: ...
299-
@overload
300-
def __setitem__(self, __key: slice, __value: Iterable[Any]) -> None: ...
301-
def __iter__(self) -> Iterator[Any]: ...
302-
# Can't inherit from Sized because the metaclass conflict between
303-
# Sized and _CData prevents using _CDataMeta.
304-
def __len__(self) -> int: ...
305-
if sys.version_info >= (3, 9):
306-
def __class_getitem__(cls, item: Any) -> GenericAlias: ...

tests/stubtest_allowlists/py3_common.txt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,9 @@ csv.Dialect.skipinitialspace
7878
csv.DictReader.__init__ # runtime sig has *args but will error if more than 5 positional args are supplied
7979
csv.DictWriter.__init__ # runtime sig has *args but will error if more than 5 positional args are supplied
8080
ctypes.Array._type_ # _type_ and _length_ are abstract, https://github.com/python/typeshed/pull/6361
81+
_ctypes.Array._type_
8182
ctypes.Array._length_
83+
_ctypes.Array._length_
8284
ctypes.CDLL._FuncPtr # None at class level but initialized in __init__ to this value
8385
ctypes.memmove # CFunctionType
8486
ctypes.memset # CFunctionType
@@ -332,7 +334,6 @@ turtle.ScrolledCanvas.onResize
332334
wave.Wave_read.initfp
333335
wave.Wave_write.initfp
334336

335-
_ctypes.Array
336337
_ctypes.CFuncPtr
337338
_ctypes.POINTER
338339
_ctypes.PyObj_FromPtr
@@ -357,6 +358,7 @@ _ctypes.sizeof
357358
# ==========
358359

359360
ctypes.Array.raw # exists but stubtest can't see it; only available if _CT == c_char
361+
_ctypes.Array.raw
360362

361363
_collections_abc.AsyncGenerator.asend # async at runtime, deliberately not in the stub, see #7491. Pos-only differences also.
362364
_collections_abc.AsyncGenerator.__anext__ # async at runtime, deliberately not in the stub, see #7491
@@ -605,6 +607,7 @@ wsgiref.handlers.BaseHandler.status
605607

606608
# Iterable classes that don't define __iter__ at runtime (usually iterable via __getitem__)
607609
# These would ideally be special-cased by type checkers; see https://github.com/python/mypy/issues/2220
610+
_ctypes.Array.__iter__
608611
ctypes.Array.__iter__
609612
mmap.mmap.__iter__
610613
mmap.mmap.__contains__

0 commit comments

Comments
 (0)