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

1from . import _ccallback_c 

2 

3import ctypes 

4 

5PyCFuncPtr = ctypes.CFUNCTYPE(ctypes.c_void_p).__bases__[0] 

6 

7ffi = None 

8 

9class CData: 

10 pass 

11 

12def _import_cffi(): 

13 global ffi, CData 

14 

15 if ffi is not None: 

16 return 

17 

18 try: 

19 import cffi 

20 ffi = cffi.FFI() 

21 CData = ffi.CData 

22 except ImportError: 

23 ffi = False 

24 

25 

26class LowLevelCallable(tuple): 

27 """ 

28 Low-level callback function. 

29 

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. 

39 

40 Attributes 

41 ---------- 

42 function 

43 Callback function given. 

44 user_data 

45 User data given. 

46 signature 

47 Signature of the function. 

48 

49 Methods 

50 ------- 

51 from_cython 

52 Class method for constructing callables from Cython C-exported 

53 functions. 

54 

55 Notes 

56 ----- 

57 The argument ``function`` can be one of: 

58 

59 - PyCapsule, whose name contains the C function signature 

60 - ctypes function pointer 

61 - cffi function pointer 

62 

63 The signature of the low-level callback must match one of those expected 

64 by the routine it is passed to. 

65 

66 If constructing low-level functions from a PyCapsule, the name of the 

67 capsule must be the corresponding signature, in the format:: 

68 

69 return_type (arg1_type, arg2_type, ...) 

70 

71 For example:: 

72 

73 "void (double)" 

74 "double (double, int *, void *)" 

75 

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. 

78 

79 """ 

80 

81 # Make the class immutable 

82 __slots__ = () 

83 

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)) 

89 

90 def __repr__(self): 

91 return "LowLevelCallable({!r}, {!r})".format(self.function, self.user_data) 

92 

93 @property 

94 def function(self): 

95 return tuple.__getitem__(self, 1) 

96 

97 @property 

98 def user_data(self): 

99 return tuple.__getitem__(self, 2) 

100 

101 @property 

102 def signature(self): 

103 return _ccallback_c.get_capsule_signature(tuple.__getitem__(self, 0)) 

104 

105 def __getitem__(self, idx): 

106 raise ValueError() 

107 

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. 

112 

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*. 

123 

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) 

132 

133 @classmethod 

134 def _parse_callback(cls, obj, user_data=None, signature=None): 

135 _import_cffi() 

136 

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)") 

147 

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)") 

158 

159 return _ccallback_c.get_raw_capsule(func, signature, context) 

160 

161 

162# 

163# ctypes helpers 

164# 

165 

166def _get_ctypes_func(func, signature=None): 

167 # Get function pointer 

168 func_ptr = ctypes.cast(func, ctypes.c_void_p).value 

169 

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 += ")" 

179 

180 return func_ptr, signature 

181 

182 

183def _typename_from_ctypes(item): 

184 if item is None: 

185 return "void" 

186 elif item is ctypes.c_void_p: 

187 return "void *" 

188 

189 name = item.__name__ 

190 

191 pointer_level = 0 

192 while name.startswith("LP_"): 

193 pointer_level += 1 

194 name = name[3:] 

195 

196 if name.startswith('c_'): 

197 name = name[2:] 

198 

199 if pointer_level > 0: 

200 name += " " + "*"*pointer_level 

201 

202 return name 

203 

204 

205def _get_ctypes_data(data): 

206 # Get voidp pointer 

207 return ctypes.cast(data, ctypes.c_void_p).value 

208 

209 

210# 

211# CFFI helpers 

212# 

213 

214def _get_cffi_func(func, signature=None): 

215 # Get function pointer 

216 func_ptr = ffi.cast('uintptr_t', func) 

217 

218 # Get signature 

219 if signature is None: 

220 signature = ffi.getctype(ffi.typeof(func)).replace('(*)', ' ') 

221 

222 return func_ptr, signature 

223 

224 

225def _get_cffi_data(data): 

226 # Get pointer 

227 return ffi.cast('uintptr_t', data)