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 import std.traits: fullyQualifiedName; 48 49 static foreach(T; aggregates.Types) { 50 51 static if(__traits(compiles, PythonType!T.pyType)) { 52 if(PyType_Ready(PythonType!T.pyType) < 0) 53 throw new Exception("Could not get type ready for `" ~ __traits(identifier, T) ~ "`"); 54 55 pyIncRef(PythonType!T.pyObject); 56 PyModule_AddObject(module_, __traits(identifier, T), PythonType!T.pyObject); 57 } else 58 pragma(msg, "WARNING: could not wrap aggregate ", fullyQualifiedName!T); 59 } 60 } 61 62 /// Returns a PyMethodDef for each cfunction. 63 private PyMethodDef* cFunctionsToPyMethodDefs(alias cfunctions)() 64 if(is(cfunctions == CFunctions!(A), A...)) 65 { 66 // +1 due to the sentinel that Python uses to know when to 67 // stop incrementing through the pointer. 68 static PyMethodDef[cfunctions.length + 1] methods; 69 70 static foreach(i, function_; cfunctions.functions) {{ 71 alias cfunction = function_.symbol; 72 static assert(is(typeof(&cfunction): PyCFunction) || 73 is(typeof(&cfunction): PyCFunctionWithKeywords), 74 __traits(identifier, cfunction) ~ " is not a Python C function"); 75 76 methods[i] = pyMethodDef!(function_.identifier)(cast(PyCFunction) &cfunction); 77 }} 78 79 return &methods[0]; 80 } 81 82 83 /** 84 Helper function to get around the C syntax problem with 85 PyModuleDef_HEAD_INIT - it doesn't compile in D. 86 */ 87 private auto pyModuleDef(A...)(auto ref A args) if(isPython3) { 88 import std.functional: forward; 89 90 return PyModuleDef( 91 // the line below is a manual D version expansion of PyModuleDef_HEAD_INIT 92 PyModuleDef_Base(PyObject(1 /*ref count*/, null /*type*/), null /*m_init*/, 0/*m_index*/, null/*m_copy*/), 93 forward!args 94 ); 95 } 96 97 /** 98 Helper function to create PyMethodDef structs. 99 The strings are compile-time parameters to avoid passing GC-allocated memory 100 to Python (by calling std.string.toStringz or manually appending the null 101 terminator). 102 */ 103 auto pyMethodDef(string name, int flags = defaultMethodFlags, string doc = "", F) 104 (F cfunction) pure 105 { 106 import std.traits: ReturnType, Parameters, isPointer; 107 import std.meta: allSatisfy; 108 109 static assert(isPointer!(ReturnType!F), 110 "C function method implementation must return a pointer"); 111 static assert(allSatisfy!(isPointer, Parameters!F), 112 "C function method implementation must take pointers"); 113 static assert(Parameters!F.length == 2 || Parameters!F.length == 3, 114 "C function method implementation must take 2 or 3 pointers"); 115 116 return PyMethodDef(name.ptr, cast(PyCFunction) cfunction, flags, doc.ptr); 117 } 118 119 120 enum defaultMethodFlags = MethodArgs.Var | MethodArgs.Keywords;