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;