"""
Type annotations for :mod:`ctypes` behavior that is documented but missing from its type stubs.
Most of these definitions fall back to standard :mod:`ctypes` types at runtime;
this allows them to be used as drop-in replacements for the standard types in function signatures.
"""
# pyright: reportPrivateUsage=false
# The types are private but the interfaces are documented, so this is a false positive.
# pyright: reportNoOverloadImplementation=false
# The overloads are only for type checking, so they don't need implementations.
from __future__ import annotations
from collections.abc import Buffer, Callable, Iterable, Iterator
from ctypes import (
CFUNCTYPE,
POINTER,
Array,
Structure,
c_bool,
c_byte,
c_char,
c_char_p,
c_double,
c_float,
c_int,
c_int8,
c_int16,
c_int32,
c_int64,
c_long,
c_longdouble,
c_longlong,
c_short,
c_size_t,
c_ssize_t,
c_ubyte,
c_uint,
c_uint8,
c_uint16,
c_uint32,
c_uint64,
c_ulong,
c_ulonglong,
c_ushort,
c_void_p,
)
from typing import (
TYPE_CHECKING,
Protocol,
Self,
SupportsBytes,
SupportsFloat,
SupportsInt,
overload,
override,
)
if TYPE_CHECKING:
from _ctypes import _CDataType
from _ctypes import CFuncPtr, _Pointer, _SimpleCData
type CInt = (
c_int
| c_byte
| c_short
| c_long
| c_longlong
| c_int8
| c_int16
| c_int32
| c_int64
| c_ssize_t
)
type CUint = (
c_uint
| c_ubyte
| c_ushort
| c_ulong
| c_ulonglong
| c_uint8
| c_uint16
| c_uint32
| c_uint64
| c_size_t
)
CReal = c_float | c_double | c_longdouble
CNumber = CInt | CUint | CReal
[docs]
class AsParameter[T: _CDataType](Protocol):
"""
A type that can be converted to a ctypes object
with the _as_parameter_ property.
"""
@property
def _as_parameter_(self) -> T: ...
type ConvertibleTo[T: _CDataType] = T | AsParameter[T]
type ConvertibleToPrimitive[T: _CDataType, U: (int, float, bytes, bool)] = (
ConvertibleTo[T] | U | _SimpleCData[U]
)
class _FunctionPointerDeclaration:
@classmethod
def __class_getitem__(cls, args: tuple[type[_CDataType] | None, list[type[_CDataType]]]):
"""Allow subscripted CoreFunctionPointer types to be used as type annotations."""
# CFUNCTYPE doesn't support returning pointers to structs,
# so function pointers will have to return c_void_p instead
# and be cast back to the correct type in the implementation.
# https://github.com/python/cpython/issues/49960
restype = args[0]
if (
restype
and issubclass(restype, _Pointer)
and issubclass(restype._type_, (Structure, CFuncPtr)) # type: ignore
):
return CFUNCTYPE(c_void_ptr, *args[1])
else:
return CFUNCTYPE(restype, *args[1])
class _PointerDeclaration:
@classmethod
def __class_getitem__(cls, arg: type[_CDataType]):
"""Allow subscripted Pointer types to be used as type annotations."""
return POINTER(arg)
class _CTypeDeclaration:
@classmethod
def __class_getitem__(cls, ctype: type[_CDataType]):
return ctype
[docs]
class c_void_ptr(c_void_p):
"""
A trivial subclass of ``c_void_p`` that solely exists to
prevent ``ctypes`` from implicitly converting
``void*`` parameters or struct fields to an ``int``.
Use this in function signatures and struct definitions instead of ``c_void_p``.
"""
@override
def __repr__(self) -> str:
return f"c_void_ptr({self.value:#x})"
if TYPE_CHECKING:
type CBoolArg = ConvertibleToPrimitive[c_bool, bool]
"""A type that can be used as an argument for a C :c:type:`c_bool` parameter."""
type CIntArg[T: CUint | CInt] = ConvertibleToPrimitive[T, int] | SupportsInt
"""A type that can be used as an argument for a C integer parameter."""
type CFloatArg[T: CReal] = ConvertibleToPrimitive[T, float] | SupportsFloat
"""A type that can be used as an argument for a C floating-point parameter."""
type CStringArg = ConvertibleToPrimitive[c_char_p, bytes] | SupportsBytes
"""A type that can be used as an argument for a C ``char *`` parameter."""
type Pointer[T: _CDataType] = _Pointer[T]
[docs]
class TypedFunctionPointer[R: _CDataType | None, **P](CFuncPtr):
"""
Typing-only refinement of :func:`ctypes.CFUNCTYPE` parameterized by return and parameter types.
Defined inside a :attr:`typing.TYPE_CHECKING` block;
at runtime this name resolves to a plain
:func:`ctypes.CFUNCTYPE` factory (see the :keyword:`else` branch below).
Exists so static analyzers can infer concrete return types
(:class:`bool`, :class:`int``, :class:`float`, :class:`bytes`, etc.)
from the ctypes return-type parameter ``R``.
"""
@overload
def __call__(self, func: Callable[P, R]) -> Self: ...
@overload
def __call__(
self: TypedFunctionPointer[c_bool, P], *args: P.args, **kwargs: P.kwargs
) -> bool: ...
@overload
def __call__[I: CInt | CUint](
self: TypedFunctionPointer[I, P], *args: P.args, **kwargs: P.kwargs
) -> int: ...
@overload
def __call__[F: CReal](
self: TypedFunctionPointer[F, P], *args: P.args, **kwargs: P.kwargs
) -> float: ...
@overload
def __call__[S: Structure](
self: TypedFunctionPointer[TypedPointer[S] | Pointer[S], P],
*args: P.args,
**kwargs: P.kwargs,
) -> c_void_ptr | None: ...
@overload
def __call__(
self: TypedFunctionPointer[None, []], *args: P.args, **kwargs: P.kwargs
) -> TypedFunctionPointer[None, []] | None: ...
@overload
def __call__[T: _CDataType, **Q](
self: TypedFunctionPointer[TypedFunctionPointer[T, Q], P],
*args: P.args,
**kwargs: P.kwargs,
) -> c_void_ptr | None: ...
# See https://github.com/python/cpython/issues/49960
@overload
def __call__(
self: TypedFunctionPointer[c_char_p, P], *args: P.args, **kwargs: P.kwargs
) -> bytes | None: ...
@overload
def __call__(self, *args: P.args, **kwargs: P.kwargs) -> R: ...
class TypedArray[T: _CDataType](Array[T]):
"""
Typing-only refinement of :class:`ctypes.Array` parameterized by element type.
Defined inside ``if TYPE_CHECKING:``;
at runtime this name resolves to plain :class:`ctypes.Array`
(see the :keyword:`else` branch below).
Exists so static analyzers can refine the element type
returned by indexing and iteration based on the :mod:`ctypes` element type ``T``.
"""
@property
@override
def raw(self: TypedArray[c_char]) -> bytes: ...
@raw.setter
def raw(self: TypedArray[c_char], value: Buffer) -> None: ...
@overload
def __init__(self, *args: ConvertibleTo[T]) -> None: ...
@overload
def __init__[I: CInt | CUint](self: TypedArray[I], *args: ConvertibleTo[I]) -> None: ...
@overload
def __init__[F: CReal](self: TypedArray[F], *args: ConvertibleTo[F]) -> None: ...
@overload
def __init__[S: Structure](self: TypedArray[S], *args: ConvertibleTo[S]) -> None: ...
@overload
def __getitem__(self, key: int, /) -> T: ...
@overload
def __getitem__[I: CInt | CUint](self: TypedArray[I], key: int, /) -> int: ...
@overload
def __getitem__[F: CReal](self: TypedArray[F], key: int, /) -> float: ...
@overload
def __getitem__[S: Structure](self: TypedArray[S], key: int, /) -> S: ...
@overload
def __getitem__[P: _CDataType](
self: TypedArray[TypedArray[P]] | TypedArray[_Pointer[P]], key: int, /
) -> TypedArray[P]: ...
@overload
def __getitem__(self, key: slice[T], /) -> list[T]: ...
@overload
def __getitem__[I: CInt | CUint](self: TypedArray[I], key: slice, /) -> list[int]: ...
@overload
def __getitem__[F: CReal](self: TypedArray[F], key: slice, /) -> list[float]: ...
@overload
def __getitem__[S: Structure](self: TypedArray[S], key: slice, /) -> list[S]: ...
@overload
def __getitem__[P: _CDataType]( # pyright: ignore[reportIncompatibleMethodOverride]
self: TypedArray[TypedArray[P]] | TypedArray[_Pointer[P]], key: slice, /
) -> list[TypedArray[P]]: ...
# pyright triggers reportIncompatibleMethodOverride here because
# it claims that not all overloads of the base type are covered;
# not sure if it's a false positive or if the overloads are actually incompatible.
# The error occurred in 1.1.409, but not 1.1.408; no idea why.
# Same goes for __setitem__ below.
@override
@overload
def __setitem__(self, key: int, value: ConvertibleTo[T], /) -> None: ...
@overload
def __setitem__[I: CInt | CUint](
self: TypedArray[I], key: int, value: ConvertibleTo[I], /
) -> None: ...
@overload
def __setitem__[F: CReal](
self: TypedArray[F], key: int, value: ConvertibleTo[F], /
) -> None: ...
@overload
def __setitem__[S: Structure](
self: TypedArray[S], key: int, value: ConvertibleTo[S], /
) -> None: ...
@overload
def __setitem__[P: _CDataType](
self: TypedArray[TypedArray[P]] | TypedArray[_Pointer[P]],
key: int,
value: ConvertibleTo[TypedArray[P]] | ConvertibleTo[_Pointer[P]],
/,
) -> None: ...
@overload
def __setitem__(
self, key: slice[ConvertibleTo[T]], value: Iterable[ConvertibleTo[T]], /
) -> None: ...
@overload
def __setitem__[I: CInt | CUint](
self: TypedArray[I], key: slice, value: Iterable[ConvertibleTo[I]], /
) -> None: ...
@overload
def __setitem__[F: CReal](
self: TypedArray[F], key: slice, value: Iterable[ConvertibleTo[F]], /
) -> None: ...
@overload
def __setitem__[S: Structure](
self: TypedArray[S], key: slice, value: Iterable[ConvertibleTo[S]], /
) -> None: ...
@overload
def __setitem__[P: _CDataType]( # pyright: ignore[reportIncompatibleMethodOverride]
self: TypedArray[TypedArray[P]] | TypedArray[_Pointer[P]],
key: slice,
value: Iterable[ConvertibleTo[TypedArray[P]] | ConvertibleTo[_Pointer[P]]],
/,
) -> None: ...
@overload
def __iter__[I: CInt | CUint](self: TypedArray[I]) -> Iterator[int]: ...
@overload
def __iter__[F: CReal](self: TypedArray[F]) -> Iterator[float]: ...
@overload
def __iter__[S: Structure](self: TypedArray[S]) -> Iterator[S]: ...
@overload
def __iter__(self: TypedArray[T]) -> Iterator[T]: ...
[docs]
class TypedPointer[T: _CDataType](_Pointer[T]):
"""
Typing-only refinement of :class:`ctypes.POINTER` parameterized by referent type.
Defined inside ``if TYPE_CHECKING:``;
at runtime this name resolves to a plain
:func:`ctypes.POINTER` factory (see the :keyword:`else` branch below).
Exists so static analyzers can refine the value type
returned by indexing based on the :mod:`ctypes` referent type ``T``.
"""
@overload
def __getitem__(self: TypedPointer[c_bool], key: int, /) -> bool: ...
@overload
def __getitem__[I: CInt | CUint](self: TypedPointer[I], key: int, /) -> int: ...
@overload
def __getitem__[F: CReal](self: TypedPointer[F], key: int, /) -> float: ...
@overload
def __getitem__(self: TypedPointer[c_char_p], key: int, /) -> bytes: ...
@overload
def __getitem__[S: Structure](self: TypedPointer[S], key: int, /) -> S: ...
@overload
def __getitem__[P: _CDataType](
self: TypedPointer[TypedPointer[P]] | TypedPointer[_Pointer[P]], key: int, /
) -> TypedPointer[P]: ...
@overload
def __getitem__(self: TypedPointer[c_bool], key: slice, /) -> list[bool]: ...
@overload
def __getitem__[I: CInt | CUint](self: TypedPointer[I], key: slice, /) -> list[int]: ...
@overload
def __getitem__[F: CReal](self: TypedPointer[F], key: slice, /) -> list[float]: ...
@overload
def __getitem__(self: TypedPointer[c_char_p], key: slice, /) -> list[bytes]: ...
@overload
def __getitem__[S: Structure](self: TypedPointer[S], key: slice, /) -> list[S]: ...
@overload
def __getitem__[P: _CDataType](
self: TypedPointer[TypedPointer[P]] | TypedPointer[_Pointer[P]], key: slice, /
) -> list[TypedPointer[P]]: ...
@overload
def __setitem__(self: TypedPointer[c_bool], key: int, value: CBoolArg, /) -> None: ...
@overload
def __setitem__[I: CInt | CUint](
self: TypedPointer[I], key: int, value: CIntArg[I], /
) -> None: ...
@overload
def __setitem__[F: CReal](
self: TypedPointer[F], key: int, value: CFloatArg[F], /
) -> None: ...
@overload
def __setitem__(self: TypedPointer[c_char_p], key: int, value: CStringArg, /) -> None: ...
@overload
def __setitem__[S: Structure](
self: TypedPointer[S], key: int, value: ConvertibleTo[S], /
) -> None: ...
@overload
def __setitem__[P: _CDataType](
self: TypedPointer[TypedPointer[P]] | TypedPointer[_Pointer[P]],
key: int,
value: ConvertibleTo[TypedPointer[P]] | ConvertibleTo[_Pointer[P]],
/,
) -> None: ...
def __bool__(self) -> bool:
"""Return :obj:`True` if this pointer is non-null."""
...
else:
CBoolArg = c_bool
CIntArg = _CTypeDeclaration
CFloatArg = _CTypeDeclaration
CStringArg = c_char_p
Pointer = _PointerDeclaration
TypedFunctionPointer = _FunctionPointerDeclaration
TypedPointer = _PointerDeclaration
TypedArray = Array
__all__ = (
"AsParameter",
"ConvertibleTo",
"CBoolArg",
"CFloatArg",
"CIntArg",
"CStringArg",
"TypedFunctionPointer",
"Pointer",
"TypedPointer",
"TypedArray",
"c_void_ptr",
)