1 /** 2 Wrapping functionality for D to Python. 3 */ 4 module autowrap.pynih.wrap; 5 6 7 public import autowrap.types: Modules, Module, isModule, 8 LibraryName, PreModuleInitCode, PostModuleInitCode, RootNamespace, Ignore; 9 public import std.typecons : Yes, No; 10 11 import python.raw: PyDateTime_CAPI, PyObject; 12 static import python.boilerplate; 13 import autowrap.common: toSnakeCase; 14 import mirror.meta.reflection : FunctionSymbol; 15 import std.meta: allSatisfy; 16 import std.traits: isInstanceOf, isSomeFunction; 17 18 // This is required to avoid linker errors. Before it was in the string mixin, 19 // but there's no need for it there, instead we declare it here in the library 20 // instead. 21 export __gshared extern(C) PyDateTime_CAPI* PyDateTimeAPI; 22 23 24 /** 25 Returns a string to mixin that implements the necessary boilerplate 26 to create a Python library containing one Python module 27 wrapping all relevant D code and data structures. 28 */ 29 string wrapDlang( 30 LibraryName libraryName, 31 Modules modules, 32 RootNamespace _ = RootNamespace(), // ignored in this backend 33 PreModuleInitCode preModuleInitCode = PreModuleInitCode(), // ignored in this backend 34 PostModuleInitCode postModuleInitCode = PostModuleInitCode(), // ignored in this backend 35 ) 36 () 37 { 38 assert(__ctfe); 39 return createPythonModuleMixin!(libraryName, modules); 40 } 41 42 43 /** 44 Returns a string with the module creation function for Python, i.e. 45 the Python extension module's entry point. Needs to be a string mixin 46 since Python knows which function is the entry point by name convention, 47 where an extension module called "foo" needs to export a function 48 `PyInit_foo`. "All" this function does is create a function with the 49 appropriate name and stringify the arguments to pass to the function 50 doing the heavy lifting: createPythonModule. 51 */ 52 string createPythonModuleMixin(LibraryName libraryName, Modules modules) 53 () 54 { 55 import std.format: format; 56 import std.algorithm: map; 57 import std.array: join; 58 59 assert(__ctfe); 60 61 return q{ 62 extern(C) export auto PyInit_%s() { // -> ModuleInitRet 63 import autowrap.pynih.wrap: createPythonModule, LibraryName; 64 import autowrap.types: Module; 65 return createPythonModule!( 66 LibraryName("%s"), 67 %s 68 ); 69 } 70 }.format( 71 libraryName.value, 72 libraryName.value, 73 modules.value.map!(a => a.toString).join(", ") 74 ); 75 } 76 77 78 /** 79 Reflects on the modules given and creates a Python module called `libraryName` 80 with wrappers for all of the functions and types in each of the modules. 81 */ 82 auto createPythonModule(LibraryName libraryName, modules...)() 83 if(allSatisfy!(isModule, modules)) 84 { 85 import python.boilerplate: Module; 86 87 auto ret = createDlangPythonModule!( 88 Module(libraryName.value), 89 cfunctions!modules, 90 aggregates!modules 91 ); 92 93 addConstants!modules(ret); 94 95 return ret; 96 } 97 98 void addConstants(modules...)(PyObject* pythonModule) { 99 import python.cooked : addStringConstant, addIntConstant; 100 import autowrap.reflection: AllConstants; 101 import std.meta: Filter; 102 103 alias constants = AllConstants!modules; 104 105 template isIntegral(alias var) { 106 import std.traits: _isIntegral = isIntegral; 107 enum isIntegral = _isIntegral!(var.Type); 108 } 109 static foreach(intConstant; Filter!(isIntegral, constants)) { 110 addIntConstant!(intConstant.identifier, intConstant.value)(pythonModule); 111 } 112 113 enum isString(alias var) = is(var.Type == string); 114 static foreach(strConstant; Filter!(isString, constants)) { 115 addStringConstant!(strConstant.identifier, strConstant.value)(pythonModule); 116 } 117 } 118 119 /** 120 The C functions that Python is actually going to call, of the type 121 PyObject* (PyObject* args, PyObject* kwargs). "Returned" as `python.boilerplate.CFunctions`, 122 obtained by reflecting on the passed-in modules and synthethising the necessary functions 123 by doing all conversions automatically. 124 */ 125 template cfunctions(modules...) { 126 import python.boilerplate: CFunctions; 127 import std.meta: staticMap; 128 alias cfunctions = CFunctions!(staticMap!(toCFunction, wrappableFunctions!modules)); 129 } 130 131 private template wrappableFunctions(modules...) { 132 import autowrap.common: AlwaysTry; 133 import python.type: PythonFunction; 134 import autowrap.reflection: AllFunctions; 135 import std.meta: Filter, templateNot; 136 import std.traits: fullyQualifiedName; 137 138 alias allFunctions = AllFunctions!modules; 139 enum isWrappableFunction(alias functionSymbol) = AlwaysTry || 140 __traits(compiles, &PythonFunction!(functionSymbol.symbol)._py_function_impl); 141 alias nonWrappableFunctions = Filter!(templateNot!isWrappableFunction, allFunctions); 142 143 static foreach(nonWrappableFunction; nonWrappableFunctions) { 144 pragma(msg, "autowrap WARNING: Could not wrap function ", 145 fullyQualifiedName!(nonWrappableFunction.symbol)); 146 // uncomment to see the compiler error 147 // &PythonFunction!(nonWrappableFunction.symbol)._py_function_impl; 148 } 149 150 alias wrappableFunctions = Filter!(isWrappableFunction, allFunctions); 151 } 152 153 /** 154 The D aggregates (structs/classes/enums) to be wrapped for Python. 155 "Returned" as `python.boilerplate.Aggregates` 156 */ 157 template aggregates(modules...) { 158 import python.boilerplate: Aggregates; 159 import autowrap.reflection: AllAggregates; 160 alias aggregates = Aggregates!(AllAggregates!modules); 161 } 162 163 /** 164 Converts from mirror.meta.reflection.FunctionSymbol to the template CFunction from python.boilerplate. 165 The reason identifier defaults to an empty string is because otherwise it doesn't compile 166 if a regular D function symbol is passed. 167 */ 168 template toCFunction(alias functionSymbol, string identifier = "") 169 if(isInstanceOf!(FunctionSymbol, functionSymbol)) 170 { 171 import python.type: PythonFunction; 172 import python.boilerplate: CFunction; 173 174 private enum id = identifier == "" ? functionSymbol.identifier.toSnakeCase : identifier; 175 176 alias toCFunction = CFunction!( 177 PythonFunction!(functionSymbol.symbol)._py_function_impl, 178 id, 179 ); 180 } 181 182 template toCFunction(alias F, string identifier = __traits(identifier, F).toSnakeCase) 183 if(isSomeFunction!F) 184 { 185 import python.type: PythonFunction; 186 import python.boilerplate: CFunction; 187 188 alias toCFunction = CFunction!( 189 PythonFunction!F._py_function_impl, 190 identifier, 191 ); 192 } 193 194 /** 195 Initialises the Python DateTime API, the druntime, and creates the Python extension module 196 */ 197 auto createDlangPythonModule(python.boilerplate.Module module_, alias cfunctions, alias aggregates)() { 198 import python.raw: pyDateTimeImport; 199 import python.cooked: createModule; 200 import core.runtime: rt_init; 201 202 rt_init; 203 pyDateTimeImport; 204 205 return createModule!(module_, cfunctions, aggregates); 206 }