1 /** 2 Helper functions to interact with the Python C API 3 */ 4 module python.cooked; 5 6 import python.raw; 7 import python.boilerplate: Module, CFunctions, Aggregates; 8 9 /** 10 Creates a Python3 module from the given C functions. 11 Each function has the same name in Python. 12 */ 13 auto createModule(Module module_, alias cfunctions, alias aggregates)() 14 if(isPython3 && 15 is(cfunctions == CFunctions!F, F...) && 16 is(aggregates == Aggregates!T, T...)) 17 { 18 static PyModuleDef moduleDef; 19 20 auto pyMethodDefs = cFunctionsToPyMethodDefs!(cfunctions); 21 moduleDef = pyModuleDef(module_.name.ptr, null /*doc*/, -1 /*size*/, pyMethodDefs); 22 23 auto module_ = pyModuleCreate(&moduleDef); 24 addModuleTypes!aggregates(module_); 25 26 return module_; 27 } 28 29 30 31 /** 32 Calls Py_InitModule. It's the Python2 way of creating a new Python module. 33 Each function has the same name in Python. 34 */ 35 void initModule(Module module_, alias cfunctions, alias aggregates)() 36 if(isPython2 && 37 is(cfunctions == CFunctions!F, F...) && 38 is(aggregates == Aggregates!T, T...)) 39 { 40 auto module_ = pyInitModule(&module_.name[0], cFunctionsToPyMethodDefs!(cfunctions)); 41 addModuleTypes!aggregates(module_); 42 } 43 44 private void addModuleTypes(alias aggregates)(PyObject* module_) { 45 import python.type: PythonType; 46 47 static foreach(T; aggregates.Types) { 48 49 if(PyType_Ready(PythonType!T.pyType) < 0) 50 throw new Exception("Could not get type ready for `" ~ __traits(identifier, T) ~ "`"); 51 52 pyIncRef(PythonType!T.pyObject); 53 PyModule_AddObject(module_, __traits(identifier, T), PythonType!T.pyObject); 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) if(isPython3) { 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 = MethodArgs.Var | MethodArgs.Keywords, 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;