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