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