From 30f1d893711af1e6d5e2945fdbacf3e1383c6836 Mon Sep 17 00:00:00 2001 From: jorenham Date: Mon, 14 Oct 2024 03:04:23 +0200 Subject: [PATCH 1/2] improved `scipy.LowLevelCallable` --- scipy-stubs/_lib/_ccallback.pyi | 127 ++++++++++++++++++++++++++++---- 1 file changed, 112 insertions(+), 15 deletions(-) diff --git a/scipy-stubs/_lib/_ccallback.pyi b/scipy-stubs/_lib/_ccallback.pyi index f135e99c..798a53af 100644 --- a/scipy-stubs/_lib/_ccallback.pyi +++ b/scipy-stubs/_lib/_ccallback.pyi @@ -1,44 +1,141 @@ import ctypes as ct from _ctypes import CFuncPtr as PyCFuncPtr from types import ModuleType -from typing import Generic, Literal, NoReturn, TypeAlias -from typing_extensions import CapsuleType, Never, Self, TypeVar, override +from typing import ClassVar, Generic, Literal, NoReturn, Protocol, TypeAlias, final, overload, type_check_only +from typing_extensions import CapsuleType as PyCapsule, Self, TypeVar, TypeVarTuple, Unpack, override -from cffi.model import FunctionPtrType as _CFFIFuncP, PointerType as _CFFIVoidP -from scipy._typing import Untyped +# some quick interfaces for the relevant `cffi` types -_Function: TypeAlias = CapsuleType | PyCFuncPtr | _CFFIFuncP | CData -_UserData: TypeAlias = CapsuleType | ct.c_void_p | _CFFIVoidP +@type_check_only +@final +class _CFFIBackendType(Protocol): + cname: str + kind: Literal[ + "primitive", + "bool", + "int", + "float", + "char", + "byte", + "pointer", + "charp", + "bytep", + "voidp", + "generic", + "struct", + "union", + "enum", + "anonymous", + "typedef", + "function", + ] + +_CTs = TypeVarTuple("_CTs", default=Unpack[tuple[_CFFIType, ...]]) +_CT_co = TypeVar("_CT_co", covariant=True, bound=_CFFIType, default=_CFFIType) + +@type_check_only +class _CFFIType(Protocol): + is_array_type: ClassVar[bool] + is_raw_function: ClassVar[bool] + + def is_integer_type(self, /) -> bool: ... + def has_c_name(self, /) -> bool: ... + def get_c_name(self, /, replace_with: str = "", context: str = "a C file", quals: int = 0) -> str: ... + def get_cached_btype(self, ffi: object, finishlist: list[object], can_delay: bool = False) -> _CFFIBackendType: ... + + # virtual + def build_backend_type(self, /, ffi: object, finishlist: list[object]) -> _CFFIBackendType: ... + @property + def c_name_with_marker(self, /) -> str: ... + +@type_check_only +@final +class _CFFIVoid(_CFFIType, Protocol): + is_array_type: ClassVar = False + is_raw_function: ClassVar = False + + def __init__(self, /) -> None: ... + +@type_check_only +class _CFFIFunc(_CFFIType, Protocol[_CT_co, Unpack[_CTs]]): + is_array_type: ClassVar = False + + @property + def args(self, /) -> tuple[Unpack[_CTs]]: ... + @property + def result(self, /) -> _CT_co: ... + @property + def ellipsis(self, /) -> bool: ... + @property + def abi(self, /) -> int | str | None: ... + def __init__(self, /, args: tuple[Unpack[_CTs]], result: _CT_co, ellipsis: bool, abi: int | None = None) -> None: ... + +@type_check_only +@final +class _CFFIFuncPtr(_CFFIFunc[_CT_co, Unpack[_CTs]], Protocol[_CT_co, Unpack[_CTs]]): + is_raw_function: ClassVar = False + + def as_raw_function(self, /) -> _CFFIFunc[_CT_co, Unpack[_CTs]]: ... + +@type_check_only +class _CFFIPointerType(_CFFIType, Protocol[_CT_co]): + is_array_type: ClassVar = False + is_raw_function: ClassVar = False + + @property + def totype(self, /) -> _CT_co: ... + @property + def quals(self, /) -> int: ... + def __init__(self, /, totype: _CT_co, quals: int = 0) -> None: ... + +_CFFIVoidP: TypeAlias = _CFFIPointerType[_CFFIVoid] + +# helper aliases + +_Function: TypeAlias = PyCapsule | PyCFuncPtr | _CFFIFuncPtr | CData +_UserData: TypeAlias = PyCapsule | ct.c_void_p | _CFFIVoidP _FuncT_co = TypeVar("_FuncT_co", bound=_Function, covariant=True, default=_Function) -_DataT_co = TypeVar("_DataT_co", bound=_UserData | None, covariant=True, default=_UserData) +_DataT = TypeVar("_DataT", bound=_UserData | None) +_DataT_co = TypeVar("_DataT_co", bound=_UserData | None, covariant=True, default=None) ffi: Literal[False] | None +# public api + +@final class CData: ... -class LowLevelCallable(tuple[CapsuleType, _FuncT_co, _DataT_co], Generic[_FuncT_co, _DataT_co]): +class LowLevelCallable(tuple[PyCapsule, _FuncT_co, _DataT_co], Generic[_FuncT_co, _DataT_co]): @property def function(self, /) -> _FuncT_co: ... @property def user_data(self, /) -> _DataT_co: ... @property def signature(self, /) -> str: ... - def __new__( + @overload + def __new__(cls, function: Self, user_data: _DataT_co | None = None, signature: str | None = None) -> Self: ... + @overload + def __new__(cls, function: _FuncT_co, user_data: _DataT_co = ..., signature: str | None = None) -> Self: ... + @classmethod + @overload + def from_cython( cls, - function: _FuncT_co | LowLevelCallable[_FuncT_co, _DataT_co], - user_data: Untyped | None = None, + module: ModuleType, + name: str, + user_data: None = None, signature: str | None = None, - ) -> Self: ... + ) -> LowLevelCallable[PyCapsule, None]: ... @classmethod + @overload def from_cython( cls, module: ModuleType, name: str, - user_data: _UserData | None = None, + user_data: _DataT, signature: str | None = None, - ) -> Self: ... + ) -> LowLevelCallable[PyCapsule, _DataT]: ... # NOTE: `__getitem__` will always raise a `ValueError` @override - def __getitem__(self, idx: Never, /) -> NoReturn: ... # type: ignore[override] # pyright: ignore[reportIncompatibleMethodOverride] + def __getitem__(self, idx: object, /) -> NoReturn: ... From 702b0e92076a702820084261b215e5501ae9237b Mon Sep 17 00:00:00 2001 From: jorenham Date: Mon, 14 Oct 2024 03:05:18 +0200 Subject: [PATCH 2/2] remove `types-cffi` dep --- poetry.lock | 27 +-------------------------- pyproject.toml | 1 - 2 files changed, 1 insertion(+), 27 deletions(-) diff --git a/poetry.lock b/poetry.lock index 3fa2b1a9..79946c62 100644 --- a/poetry.lock +++ b/poetry.lock @@ -459,31 +459,6 @@ files = [ {file = "tomli-2.0.2.tar.gz", hash = "sha256:d46d457a85337051c36524bc5349dd91b1877838e2979ac5ced3e710ed8a60ed"}, ] -[[package]] -name = "types-cffi" -version = "1.16.0.20240331" -description = "Typing stubs for cffi" -optional = false -python-versions = ">=3.8" -files = [ - {file = "types-cffi-1.16.0.20240331.tar.gz", hash = "sha256:b8b20d23a2b89cfed5f8c5bc53b0cb8677c3aac6d970dbc771e28b9c698f5dee"}, - {file = "types_cffi-1.16.0.20240331-py3-none-any.whl", hash = "sha256:a363e5ea54a4eb6a4a105d800685fde596bc318089b025b27dee09849fe41ff0"}, -] - -[package.dependencies] -types-setuptools = "*" - -[[package]] -name = "types-setuptools" -version = "75.1.0.20240917" -description = "Typing stubs for setuptools" -optional = false -python-versions = ">=3.8" -files = [ - {file = "types-setuptools-75.1.0.20240917.tar.gz", hash = "sha256:12f12a165e7ed383f31def705e5c0fa1c26215dd466b0af34bd042f7d5331f55"}, - {file = "types_setuptools-75.1.0.20240917-py3-none-any.whl", hash = "sha256:06f78307e68d1bbde6938072c57b81cf8a99bc84bd6dc7e4c5014730b097dc0c"}, -] - [[package]] name = "typing-extensions" version = "4.12.2" @@ -518,4 +493,4 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess [metadata] lock-version = "2.0" python-versions = "^3.10.1" -content-hash = "3a1d2c336059f7c70d5bfe3a8dc1a7a1431796d534468e156108bc7fed37bac0" +content-hash = "6dd822e187f3caee0d4062af6393e69f0f40d65169d7622c7f9287a9dd98b63b" diff --git a/pyproject.toml b/pyproject.toml index e1b15bed..294b99fa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,7 +33,6 @@ python = "^3.10.1" numpy = ">=1.25" scipy = ">=1.14.1" optype = "^0.6.1" -types-cffi = ">=1.16.0" [tool.poetry.group.lint.dependencies] basedmypy = "^2.6.0"