1 /** 2 Helper functions to interact with the Python C API 3 */ 4 module python.cooked; 5 6 7 import python.raw; 8 import python.boilerplate: Module, CFunctions, Aggregates; 9 10 11 /** 12 Creates a Python3 extension module from the given C functions, which must 13 be of type `PyObject* (PyObject* args, PyObject* kwargs)`. `kwargs` is optional. 14 The aggregates are the D structs/classes/enums to be wrapped. 15 */ 16 auto createModule(Module module_, alias cfunctions, alias aggregates = Aggregates!())() 17 if(is(cfunctions == CFunctions!F, F...) && 18 is(aggregates == Aggregates!T, T...)) 19 { 20 static assert(isPython3, "Python2 no longer supported"); 21 22 static PyModuleDef moduleDef; 23 24 auto pyMethodDefs = cFunctionsToPyMethodDefs!(cfunctions); 25 moduleDef = pyModuleDef(module_.name.ptr, null /*doc*/, -1 /*size*/, pyMethodDefs); 26 27 auto module_ = pyModuleCreate(&moduleDef); 28 addModuleTypes!(aggregates.Types)(module_); 29 30 return module_; 31 } 32 33 34 /** 35 Takes a variadic sequence of D aggregate types and adds equivalent Python types 36 to the passed-in module. 37 */ 38 void addModuleTypes(aggregates...)(PyObject* module_) 39 { 40 import autowrap.common: AlwaysTry; 41 import python.type: PythonType; 42 import std.traits: fullyQualifiedName; 43 44 static foreach(T; aggregates) { 45 46 static if(AlwaysTry || __traits(compiles, PythonType!T.pyType)) { 47 if(PyType_Ready(PythonType!T.pyType) < 0) 48 throw new Exception("Could not get type ready for `" ~ __traits(identifier, T) ~ "`"); 49 50 pyIncRef(PythonType!T.pyObject); 51 PyModule_AddObject(module_, __traits(identifier, T), PythonType!T.pyObject); 52 } else 53 pragma(msg, "WARNING: could not wrap aggregate ", fullyQualifiedName!T); 54 } 55 } 56 57 /// Returns a PyMethodDef for each cfunction. 58 private PyMethodDef* cFunctionsToPyMethodDefs(alias cfunctions)() 59 if(is(cfunctions == CFunctions!(A), A...)) 60 { 61 // +1 due to the sentinel that Python uses to know when to 62 // stop incrementing through the pointer. 63 static PyMethodDef[cfunctions.length + 1] methods; 64 65 static foreach(i, function_; cfunctions.functions) {{ 66 alias cfunction = function_.symbol; 67 static assert(is(typeof(&cfunction): PyCFunction) || 68 is(typeof(&cfunction): PyCFunctionWithKeywords), 69 __traits(identifier, cfunction) ~ " is not a Python C function"); 70 71 methods[i] = pyMethodDef!(function_.identifier)(cast(PyCFunction) &cfunction); 72 }} 73 74 return &methods[0]; 75 } 76 77 78 /** 79 Helper function to get around the C syntax problem with 80 PyModuleDef_HEAD_INIT - it doesn't compile in D. 81 */ 82 private auto pyModuleDef(A...)(auto ref A args) { 83 import std.functional: forward; 84 85 return PyModuleDef( 86 // the line below is a manual D version expansion of PyModuleDef_HEAD_INIT 87 PyModuleDef_Base(PyObject(1 /*ref count*/, null /*type*/), null /*m_init*/, 0/*m_index*/, null/*m_copy*/), 88 forward!args 89 ); 90 } 91 92 /** 93 Helper function to create PyMethodDef structs. 94 The strings are compile-time parameters to avoid passing GC-allocated memory 95 to Python (by calling std.string.toStringz or manually appending the null 96 terminator). 97 */ 98 auto pyMethodDef(string name, int flags = defaultMethodFlags, string doc = "", F) 99 (F cfunction) pure 100 { 101 import std.traits: ReturnType, Parameters, isPointer; 102 import std.meta: allSatisfy; 103 104 static assert(isPointer!(ReturnType!F), 105 "C function method implementation must return a pointer"); 106 static assert(allSatisfy!(isPointer, Parameters!F), 107 "C function method implementation must take pointers"); 108 static assert(Parameters!F.length == 2 || Parameters!F.length == 3, 109 "C function method implementation must take 2 or 3 pointers"); 110 111 return PyMethodDef(name.ptr, cast(PyCFunction) cfunction, flags, doc.ptr); 112 } 113 114 115 enum defaultMethodFlags = MethodArgs.Var | MethodArgs.Keywords; 116 117 void addStringConstant(string key, string val)(PyObject* module_) { 118 import python.raw: PyModule_AddStringConstant; 119 120 static immutable char[1] emptyString = ['\0']; 121 122 // We can't pass a null pointer if the value of the string constant is empty 123 PyModule_AddStringConstant( 124 module_, 125 &key[0], 126 val.length ? val.ptr : &emptyString[0], 127 ); 128 } 129 130 void addIntConstant(string key, long val)(PyObject* module_) { 131 import python.raw: PyModule_AddIntConstant; 132 PyModule_AddIntConstant(module_, &key[0], val); 133 }