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;