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 module from the given C functions. 13 Each function has the same name in Python. 14 */ 15 auto createModule(Module module_, alias cfunctions, alias aggregates)() 16 if(isPython3 && 17 is(cfunctions == CFunctions!F, F...) && 18 is(aggregates == Aggregates!T, T...)) 19 { 20 static PyModuleDef moduleDef; 21 22 auto pyMethodDefs = cFunctionsToPyMethodDefs!(cfunctions); 23 moduleDef = pyModuleDef(module_.name.ptr, null /*doc*/, -1 /*size*/, pyMethodDefs); 24 25 auto module_ = pyModuleCreate(&moduleDef); 26 addModuleTypes!aggregates(module_); 27 28 return module_; 29 } 30 31 32 /** 33 Calls Py_InitModule. It's the Python2 way of creating a new Python module. 34 Each function has the same name in Python. 35 */ 36 void initModule(Module module_, alias cfunctions, alias aggregates)() 37 if(isPython2 && 38 is(cfunctions == CFunctions!F, F...) && 39 is(aggregates == Aggregates!T, T...)) 40 { 41 auto module_ = pyInitModule(&module_.name[0], cFunctionsToPyMethodDefs!(cfunctions)); 42 addModuleTypes!aggregates(module_); 43 } 44 45 private void addModuleTypes(alias aggregates)(PyObject* module_) { 46 import python.type: PythonType; 47 48 static foreach(T; aggregates.Types) { 49 50 if(PyType_Ready(PythonType!T.pyType) < 0) 51 throw new Exception("Could not get type ready for `" ~ __traits(identifier, T) ~ "`"); 52 53 pyIncRef(PythonType!T.pyObject); 54 PyModule_AddObject(module_, __traits(identifier, T), PythonType!T.pyObject); 55 } 56 } 57 58 /// Returns a PyMethodDef for each cfunction. 59 private PyMethodDef* cFunctionsToPyMethodDefs(alias cfunctions)() 60 if(is(cfunctions == CFunctions!(A), A...)) 61 { 62 // +1 due to the sentinel that Python uses to know when to 63 // stop incrementing through the pointer. 64 static PyMethodDef[cfunctions.length + 1] methods; 65 66 static foreach(i, function_; cfunctions.functions) {{ 67 alias cfunction = function_.symbol; 68 static assert(is(typeof(&cfunction): PyCFunction) || 69 is(typeof(&cfunction): PyCFunctionWithKeywords), 70 __traits(identifier, cfunction) ~ " is not a Python C function"); 71 72 methods[i] = pyMethodDef!(function_.identifier)(cast(PyCFunction) &cfunction); 73 }} 74 75 return &methods[0]; 76 } 77 78 79 /** 80 Helper function to get around the C syntax problem with 81 PyModuleDef_HEAD_INIT - it doesn't compile in D. 82 */ 83 private auto pyModuleDef(A...)(auto ref A args) if(isPython3) { 84 import std.functional: forward; 85 86 return PyModuleDef( 87 // the line below is a manual D version expansion of PyModuleDef_HEAD_INIT 88 PyModuleDef_Base(PyObject(1 /*ref count*/, null /*type*/), null /*m_init*/, 0/*m_index*/, null/*m_copy*/), 89 forward!args 90 ); 91 } 92 93 /** 94 Helper function to create PyMethodDef structs. 95 The strings are compile-time parameters to avoid passing GC-allocated memory 96 to Python (by calling std.string.toStringz or manually appending the null 97 terminator). 98 */ 99 auto pyMethodDef(string name, int flags = MethodArgs.Var | MethodArgs.Keywords, string doc = "", F) 100 (F cfunction) pure 101 { 102 import std.traits: ReturnType, Parameters, isPointer; 103 import std.meta: allSatisfy; 104 105 static assert(isPointer!(ReturnType!F), 106 "C function method implementation must return a pointer"); 107 static assert(allSatisfy!(isPointer, Parameters!F), 108 "C function method implementation must take pointers"); 109 static assert(Parameters!F.length == 2 || Parameters!F.length == 3, 110 "C function method implementation must take 2 or 3 pointers"); 111 112 return PyMethodDef(name.ptr, cast(PyCFunction) cfunction, flags, doc.ptr); 113 } 114 115 116 enum defaultMethodFlags = MethodArgs.Var | MethodArgs.Keywords;