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 extension module from the given C functions, which must
13    be of type `PyObject* (PyObject* args, PyObject* kwargs)`. `kwargs` is optional.
14    The aggregates are the D structs/classes/enums to be wrapped.
15  */
16 auto createModule(Module module_, alias cfunctions, alias aggregates = Aggregates!())()
17     if(is(cfunctions == CFunctions!F, F...) &&
18        is(aggregates == Aggregates!T, T...))
19 {
20     static assert(isPython3, "Python2 no longer supported");
21 
22     static PyModuleDef moduleDef;
23 
24     auto pyMethodDefs = cFunctionsToPyMethodDefs!(cfunctions);
25     moduleDef = pyModuleDef(module_.name.ptr, null /*doc*/, -1 /*size*/, pyMethodDefs);
26 
27     auto module_ = pyModuleCreate(&moduleDef);
28     addModuleTypes!(aggregates.Types)(module_);
29 
30     return module_;
31 }
32 
33 
34 /**
35    Takes a variadic sequence of D aggregate types and adds equivalent Python types
36    to the passed-in module.
37  */
38 void addModuleTypes(aggregates...)(PyObject* module_)
39 {
40     import autowrap.common: AlwaysTry;
41     import python.type: PythonType;
42     import std.traits: fullyQualifiedName;
43 
44     static foreach(T; aggregates) {
45 
46         static if(AlwaysTry || __traits(compiles, PythonType!T.pyType)) {
47             if(PyType_Ready(PythonType!T.pyType) < 0)
48                 throw new Exception("Could not get type ready for `" ~ __traits(identifier, T) ~ "`");
49 
50             pyIncRef(PythonType!T.pyObject);
51             PyModule_AddObject(module_, __traits(identifier, T), PythonType!T.pyObject);
52         } else
53             pragma(msg, "WARNING: could not wrap aggregate ", fullyQualifiedName!T);
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) {
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 = defaultMethodFlags, 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;
116 
117 void addStringConstant(string key, string val)(PyObject* module_) {
118     import python.raw: PyModule_AddStringConstant;
119 
120     static immutable char[1] emptyString = ['\0'];
121 
122     // We can't pass a null pointer if the value of the string constant is empty
123     PyModule_AddStringConstant(
124         module_,
125         &key[0],
126         val.length ? val.ptr : &emptyString[0],
127     );
128 }
129 
130 void addIntConstant(string key, long val)(PyObject* module_) {
131     import python.raw: PyModule_AddIntConstant;
132     PyModule_AddIntConstant(module_, &key[0], val);
133 }