Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/scipy/_lib/_ccallback.py: 28%
98 statements
« prev ^ index » next coverage.py v7.3.2, created at 2023-12-12 06:31 +0000
« prev ^ index » next coverage.py v7.3.2, created at 2023-12-12 06:31 +0000
1from . import _ccallback_c
3import ctypes
5PyCFuncPtr = ctypes.CFUNCTYPE(ctypes.c_void_p).__bases__[0]
7ffi = None
9class CData:
10 pass
12def _import_cffi():
13 global ffi, CData
15 if ffi is not None:
16 return
18 try:
19 import cffi
20 ffi = cffi.FFI()
21 CData = ffi.CData
22 except ImportError:
23 ffi = False
26class LowLevelCallable(tuple):
27 """
28 Low-level callback function.
30 Parameters
31 ----------
32 function : {PyCapsule, ctypes function pointer, cffi function pointer}
33 Low-level callback function.
34 user_data : {PyCapsule, ctypes void pointer, cffi void pointer}
35 User data to pass on to the callback function.
36 signature : str, optional
37 Signature of the function. If omitted, determined from *function*,
38 if possible.
40 Attributes
41 ----------
42 function
43 Callback function given.
44 user_data
45 User data given.
46 signature
47 Signature of the function.
49 Methods
50 -------
51 from_cython
52 Class method for constructing callables from Cython C-exported
53 functions.
55 Notes
56 -----
57 The argument ``function`` can be one of:
59 - PyCapsule, whose name contains the C function signature
60 - ctypes function pointer
61 - cffi function pointer
63 The signature of the low-level callback must match one of those expected
64 by the routine it is passed to.
66 If constructing low-level functions from a PyCapsule, the name of the
67 capsule must be the corresponding signature, in the format::
69 return_type (arg1_type, arg2_type, ...)
71 For example::
73 "void (double)"
74 "double (double, int *, void *)"
76 The context of a PyCapsule passed in as ``function`` is used as ``user_data``,
77 if an explicit value for ``user_data`` was not given.
79 """
81 # Make the class immutable
82 __slots__ = ()
84 def __new__(cls, function, user_data=None, signature=None):
85 # We need to hold a reference to the function & user data,
86 # to prevent them going out of scope
87 item = cls._parse_callback(function, user_data, signature)
88 return tuple.__new__(cls, (item, function, user_data))
90 def __repr__(self):
91 return "LowLevelCallable({!r}, {!r})".format(self.function, self.user_data)
93 @property
94 def function(self):
95 return tuple.__getitem__(self, 1)
97 @property
98 def user_data(self):
99 return tuple.__getitem__(self, 2)
101 @property
102 def signature(self):
103 return _ccallback_c.get_capsule_signature(tuple.__getitem__(self, 0))
105 def __getitem__(self, idx):
106 raise ValueError()
108 @classmethod
109 def from_cython(cls, module, name, user_data=None, signature=None):
110 """
111 Create a low-level callback function from an exported Cython function.
113 Parameters
114 ----------
115 module : module
116 Cython module where the exported function resides
117 name : str
118 Name of the exported function
119 user_data : {PyCapsule, ctypes void pointer, cffi void pointer}, optional
120 User data to pass on to the callback function.
121 signature : str, optional
122 Signature of the function. If omitted, determined from *function*.
124 """
125 try:
126 function = module.__pyx_capi__[name]
127 except AttributeError as e:
128 raise ValueError("Given module is not a Cython module with __pyx_capi__ attribute") from e
129 except KeyError as e:
130 raise ValueError("No function {!r} found in __pyx_capi__ of the module".format(name)) from e
131 return cls(function, user_data, signature)
133 @classmethod
134 def _parse_callback(cls, obj, user_data=None, signature=None):
135 _import_cffi()
137 if isinstance(obj, LowLevelCallable):
138 func = tuple.__getitem__(obj, 0)
139 elif isinstance(obj, PyCFuncPtr):
140 func, signature = _get_ctypes_func(obj, signature)
141 elif isinstance(obj, CData):
142 func, signature = _get_cffi_func(obj, signature)
143 elif _ccallback_c.check_capsule(obj):
144 func = obj
145 else:
146 raise ValueError("Given input is not a callable or a low-level callable (pycapsule/ctypes/cffi)")
148 if isinstance(user_data, ctypes.c_void_p):
149 context = _get_ctypes_data(user_data)
150 elif isinstance(user_data, CData):
151 context = _get_cffi_data(user_data)
152 elif user_data is None:
153 context = 0
154 elif _ccallback_c.check_capsule(user_data):
155 context = user_data
156 else:
157 raise ValueError("Given user data is not a valid low-level void* pointer (pycapsule/ctypes/cffi)")
159 return _ccallback_c.get_raw_capsule(func, signature, context)
162#
163# ctypes helpers
164#
166def _get_ctypes_func(func, signature=None):
167 # Get function pointer
168 func_ptr = ctypes.cast(func, ctypes.c_void_p).value
170 # Construct function signature
171 if signature is None:
172 signature = _typename_from_ctypes(func.restype) + " ("
173 for j, arg in enumerate(func.argtypes):
174 if j == 0:
175 signature += _typename_from_ctypes(arg)
176 else:
177 signature += ", " + _typename_from_ctypes(arg)
178 signature += ")"
180 return func_ptr, signature
183def _typename_from_ctypes(item):
184 if item is None:
185 return "void"
186 elif item is ctypes.c_void_p:
187 return "void *"
189 name = item.__name__
191 pointer_level = 0
192 while name.startswith("LP_"):
193 pointer_level += 1
194 name = name[3:]
196 if name.startswith('c_'):
197 name = name[2:]
199 if pointer_level > 0:
200 name += " " + "*"*pointer_level
202 return name
205def _get_ctypes_data(data):
206 # Get voidp pointer
207 return ctypes.cast(data, ctypes.c_void_p).value
210#
211# CFFI helpers
212#
214def _get_cffi_func(func, signature=None):
215 # Get function pointer
216 func_ptr = ffi.cast('uintptr_t', func)
218 # Get signature
219 if signature is None:
220 signature = ffi.getctype(ffi.typeof(func)).replace('(*)', ' ')
222 return func_ptr, signature
225def _get_cffi_data(data):
226 # Get pointer
227 return ffi.cast('uintptr_t', data)