1 /**
2    A D API for dealing with Python's PyTypeObject
3  */
4 module python.type;
5 
6 
7 import autowrap.reflection: isParameter, BinaryOperator;
8 import python.raw: PyObject;
9 import std.traits: Unqual, isArray, isIntegral, isBoolean, isFloatingPoint,
10     isAggregateType, isCallable, isAssociativeArray, isSomeFunction;
11 import std.datetime: DateTime, Date;
12 import std.typecons: Tuple;
13 import std.range.primitives: isInputRange;
14 import std.meta: allSatisfy;
15 
16 
17 package enum isPhobos(T) = isDateOrDateTime!T || isTuple!T;
18 package enum isDateOrDateTime(T) = is(Unqual!T == DateTime) || is(Unqual!T == Date);
19 package enum isTuple(T) = is(T: Tuple!A, A...);
20 package enum isUserAggregate(T) = isAggregateType!T && !isPhobos!(T);
21 package enum isNonRangeUDT(T) = isUserAggregate!T && !isInputRange!T;
22 
23 
24 /**
25    A wrapper for `PyTypeObject`.
26 
27    This struct does all of the necessary boilerplate to intialise
28    a `PyTypeObject` for a Python extension type that mimics the D
29    type `T`.
30  */
31 struct PythonType(T) {
32     import python.raw: PyTypeObject, PySequenceMethods, PyMappingMethods;
33     import std.traits: FieldNameTuple, Fields, Unqual, fullyQualifiedName, BaseClassesTuple;
34     import std.meta: Alias, AliasSeq, staticMap;
35 
36     static if(is(T == struct)) {
37         alias fieldNames = FieldNameTuple!T;
38         alias fieldTypes = Fields!T;
39     } else static if(is(T == class) || is(T == interface)) {
40         // recurse over base classes to get all fields
41         alias fieldNames = AliasSeq!(FieldNameTuple!T, staticMap!(FieldNameTuple, BaseClassesTuple!T));
42         private alias fieldType(string name) = typeof(__traits(getMember, T, name));
43         alias fieldTypes = staticMap!(fieldType, fieldNames);
44     }
45 
46     enum hasLength = is(typeof({ size_t len = T.init.length; }));
47 
48     static PyTypeObject _pyType;
49     static bool failedToReady;
50 
51     static PyObject* pyObject() {
52         initialise;
53         return failedToReady ? null : cast(PyObject*) &_pyType;
54     }
55 
56     static PyTypeObject* pyType() nothrow {
57         initialise;
58         return failedToReady ? null : &_pyType;
59     }
60 
61     private static void initialise() nothrow {
62         import python.raw: PyType_GenericNew, PyType_Ready, TypeFlags,
63             PyErr_SetString, PyExc_TypeError,
64             PyNumberMethods, PySequenceMethods;
65         import autowrap.reflection: UnaryOperators, BinaryOperators, AssignOperators, functionName;
66         import std.traits: arity, hasMember, TemplateOf;
67         import std.meta: Filter;
68         static import std.typecons;
69 
70         if(_pyType != _pyType.init) return;
71 
72         // This allows tp_name to do its usual Python job and allos us to
73         // construct a D class from its runtime Python type.
74         _pyType.tp_name = fullyQualifiedName!(Unqual!T).ptr;
75         _pyType.tp_flags = TypeFlags.Default;
76         static if(is(T == class) || is(T == interface))
77             _pyType.tp_flags |= TypeFlags.BaseType;
78 
79         // FIXME: types are that user aggregates *and* callables
80         static if(isUserAggregate!T) {
81             _pyType.tp_basicsize = PythonClass!T.sizeof;
82             _pyType.tp_getset = getsetDefs;
83             _pyType.tp_methods = methodDefs;
84             _pyType.tp_new = &_py_new;
85             _pyType.tp_repr = &_py_repr;
86             _pyType.tp_init = &_py_init;
87 
88             // special-case std.typecons.Typedef
89             // see: https://issues.dlang.org/show_bug.cgi?id=20117
90             static if(
91                 hasMember!(T, "opCmp")
92                 && !__traits(isSame, TemplateOf!T, std.typecons.Typedef)
93                 && &T.opCmp !is &Object.opCmp
94                 )
95             {
96                 _pyType.tp_richcompare = &PythonOpCmp!T._py_cmp;
97             }
98 
99             static if(hasMember!(T, "opSlice")) {
100                 _pyType.tp_iter = &PythonIter!T._py_iter;
101             }
102 
103             // In Python, both D's opIndex and opSlice are dealt with by one function,
104             // in opSlice's case when the type is indexed by a Python slice
105             static if(hasMember!(T, "opIndex") || hasMember!(T, "opSlice")) {
106                 if(_pyType.tp_as_mapping is null)
107                     _pyType.tp_as_mapping = new PyMappingMethods;
108                 _pyType.tp_as_mapping.mp_subscript = &PythonSubscript!T._py_index;
109             }
110 
111             static if(hasMember!(T, "opIndexAssign")) {
112                 if(_pyType.tp_as_mapping is null)
113                     _pyType.tp_as_mapping = new PyMappingMethods;
114 
115                 _pyType.tp_as_mapping.mp_ass_subscript = &PythonIndexAssign!T._py_index_assign;
116             }
117 
118             enum isPythonableUnary(string op) = op == "+" || op == "-" || op == "~";
119             enum unaryOperators = Filter!(isPythonableUnary, UnaryOperators!T);
120             alias binaryOperators = BinaryOperators!T;
121             alias assignOperators = AssignOperators!T;
122 
123             static if(unaryOperators.length > 0 || binaryOperators.length > 0 || assignOperators.length > 0) {
124                 _pyType.tp_as_number = new PyNumberMethods;
125                 _pyType.tp_as_sequence = new PySequenceMethods;
126             }
127 
128             static foreach(op; unaryOperators) {
129                 mixin(`_pyType.`, dlangUnOpToPythonSlot(op), ` = &PythonUnaryOperator!(T, op)._py_un_op;`);
130             }
131 
132             static foreach(binOp; binaryOperators) {{
133                 // first get the Python function pointer name
134                 enum slot = dlangBinOpToPythonSlot(binOp.op);
135                 // some of them differ in arity
136                 enum slotArity = arity!(mixin(`typeof(PyTypeObject.`, slot, `)`));
137 
138                 // get the function name in PythonBinaryOperator
139                 // `in` is special because the function signature is different
140                 static if(binOp.op == "in") {
141                     enum cFuncName = "_py_in_func";
142                 } else {
143                     static if(slotArity == 2)
144                         enum cFuncName = "_py_bin_func";
145                     else static if(slotArity == 3)
146                         enum cFuncName = "_py_ter_func";
147                     else
148                         static assert("Do not know how to deal with slot " ~ slot);
149                 }
150 
151                 // set the C function that implements this operator
152                 mixin(`_pyType.`, slot, ` = &PythonBinaryOperator!(T, binOp).`, cFuncName, `;`);
153             }}
154 
155             static foreach(assignOp; assignOperators) {{
156                 enum slot = dlangAssignOpToPythonSlot(assignOp);
157                                 // some of them differ in arity
158                 enum slotArity = arity!(mixin(`typeof(PyTypeObject.`, slot, `)`));
159 
160                 // get the function name in PythonAssignOperator
161                 static if(slotArity == 2)
162                     enum cFuncName = "_py_bin_func";
163                 else static if(slotArity == 3)
164                     enum cFuncName = "_py_ter_func";
165                 else
166                     static assert("Do not know how to deal with slot " ~ slot);
167 
168                 // set the C function that implements this operator
169                 mixin(`_pyType.`, slot, ` = &PythonAssignOperator!(T, assignOp).`, cFuncName, `;`);
170             }}
171 
172             static if(isCallable!T) {
173                 _pyType.tp_call = &PythonCallable!T._py_call;
174             }
175 
176             // inheritance
177             static if(is(T Bases == super)) {
178                 enum isSuperClass(U) = is(U == class) && !is(U == Object);
179                 alias supers = Filter!(isSuperClass, Bases);
180                 static if(supers.length == 1) {
181                     _pyType.tp_base = PythonType!(supers[0]).pyType;
182                 }
183             }
184 
185         } else static if(isCallable!T) {
186             _pyType.tp_basicsize = PythonCallable!T.sizeof;
187             _pyType.tp_call = &PythonCallable!T._py_call;
188         } else
189             static assert(false, "Don't know what to do for type " ~ T.stringof);
190 
191         static if(hasLength) {
192             if(_pyType.tp_as_sequence is null)
193                 _pyType.tp_as_sequence = new PySequenceMethods;
194             _pyType.tp_as_sequence.sq_length = &_py_length;
195         }
196 
197         if(PyType_Ready(&_pyType) < 0) {
198             PyErr_SetString(PyExc_TypeError, &"not ready"[0]);
199             failedToReady = true;
200         }
201     }
202 
203     static if(isUserAggregate!T)
204     private static auto getsetDefs() {
205         import autowrap.reflection: Properties;
206         import python.raw: PyGetSetDef;
207         import std.meta: staticMap, Filter, Alias;
208         import std.traits: isFunction, ReturnType;
209 
210         static if(is(T == struct)) {
211             enum isPublic(string memberName) =
212                 __traits(getProtection, __traits(getMember, T, memberName)) == "public";
213             alias publicMemberNames = Filter!(isPublic, __traits(allMembers, T));
214             alias AggMember(string memberName) = Alias!(__traits(getMember, T, memberName));
215             alias members = staticMap!(AggMember, publicMemberNames);
216             alias publicMemberFunctions = Filter!(isFunction, members);
217         } else {
218             import std.traits: MemberFunctionsTuple;
219             enum isFunctionName(string name) = isFunction!(__traits(getMember, T, name));
220             alias functionNames = Filter!(isFunctionName, __traits(allMembers, T));
221             alias memberFunctionsTuple(string name) = MemberFunctionsTuple!(T, name);
222             alias memberFunctions = staticMap!(memberFunctionsTuple, functionNames);
223             enum isPublic(alias T) = __traits(getProtection, T) == "public";
224             alias publicMemberFunctions = Filter!(isPublic, memberFunctions);
225         }
226 
227         alias properties = Properties!publicMemberFunctions;
228 
229 
230         // +1 due to the sentinel
231         static PyGetSetDef[fieldNames.length + properties.length + 1] getsets;
232 
233         // don't bother if already initialised
234         if(getsets != getsets.init) return &getsets[0];
235 
236         // first deal with the public fields
237         static foreach(i; 0 .. fieldNames.length) {
238             getsets[i].name = cast(typeof(PyGetSetDef.name)) fieldNames[i];
239             static if(__traits(getProtection, __traits(getMember, T, fieldNames[i])) == "public") {
240                 getsets[i].get = &PythonClass!T.get!i;
241                 getsets[i].set = &PythonClass!T.set!i;
242             }
243         }
244 
245         // then deal with the property functions
246         static foreach(j, property; properties) {{
247             enum i = fieldNames.length + j;
248 
249             getsets[i].name = cast(typeof(PyGetSetDef.name)) __traits(identifier, property);
250 
251             static foreach(overload; __traits(getOverloads, T, __traits(identifier, property))) {
252                 static if(is(ReturnType!overload == void))  // setter
253                     getsets[i].set = &PythonClass!T.propertySet!(overload);
254                 else  // getter
255                     getsets[i].get = &PythonClass!T.propertyGet!(overload);
256             }
257         }}
258 
259         return &getsets[0];
260     }
261 
262     private static auto methodDefs()() {
263         import autowrap.reflection: isProperty;
264         import python.raw: PyMethodDef, MethodArgs;
265         import python.cooked: pyMethodDef, defaultMethodFlags;
266         import std.meta: AliasSeq, Alias, staticMap, Filter, templateNot;
267         import std.traits: isSomeFunction;
268         import std.algorithm: startsWith;
269 
270         alias memberNames = AliasSeq!(__traits(allMembers, T));
271         enum ispublic(string name) = isPublic!(T, name);
272         alias publicMemberNames = Filter!(ispublic, memberNames);
273 
274         enum isRegular(string name) =
275             name != "this"
276             && name != "toHash"
277             && name != "factory"
278             && !name.startsWith("op")
279             && !name.startsWith("__")
280             ;
281         alias regularMemberNames = Filter!(isRegular, publicMemberNames);
282         alias overloads(string name) = AliasSeq!(__traits(getOverloads, T, name));
283         alias members = staticMap!(overloads, regularMemberNames);
284         alias memberFunctions = Filter!(templateNot!isProperty, Filter!(isSomeFunction, members));
285 
286         // +1 due to sentinel
287         static PyMethodDef[memberFunctions.length + 1] methods;
288 
289         if(methods != methods.init) return &methods[0];
290 
291         static foreach(i, memberFunction; memberFunctions) {{
292 
293             static if(__traits(isStaticFunction, memberFunction))
294                 enum flags = defaultMethodFlags | MethodArgs.Static;
295             else
296                 enum flags = defaultMethodFlags;
297 
298             methods[i] = pyMethodDef!(__traits(identifier, memberFunction), flags)
299                                      (&PythonMethod!(T, memberFunction)._py_method_impl);
300         }}
301 
302         return &methods[0];
303     }
304 
305     import python.raw: Py_ssize_t;
306     private static extern(C) Py_ssize_t _py_length(PyObject* self_) nothrow {
307 
308         return noThrowable!({
309             assert(self_ !is null);
310             static if(hasLength) {
311                 import python.conv: to;
312                 return self_.to!T.length;
313             } else
314                 return -1;
315         });
316     }
317 
318     private static extern(C) PyObject* _py_repr(PyObject* self_) nothrow {
319 
320         return noThrowable!({
321 
322             import python: pyUnicodeDecodeUTF8;
323             import python.conv: to;
324             import std.string: toStringz;
325             import std.conv: text;
326 
327             assert(self_ !is null);
328             auto ret = text(self_.to!T);
329             return pyUnicodeDecodeUTF8(ret.ptr, ret.length, null /*errors*/);
330         });
331     }
332 
333     private static extern(C) int _py_init(PyObject* self_, PyObject* args, PyObject* kwargs) nothrow {
334         // nothing to do
335         return 0;
336     }
337 
338     static if(isUserAggregate!T)
339     private static extern(C) PyObject* _py_new(PyTypeObject *type, PyObject* args, PyObject* kwargs) nothrow {
340         return noThrowable!({
341             import python.conv: toPython;
342             import python.raw: PyTuple_Size;
343             import std.traits: hasMember;
344 
345             if(PyTuple_Size(args) == 0) return toPython(userAggregateInit!T);
346 
347             static if(hasMember!(T, "__ctor"))
348                 return callDlangFunction!(T, __traits(getMember, T, "__ctor"))(null /*self*/, args, kwargs);
349             else { // allow implicit constructors to work in Python
350                 T impl(fieldTypes fields = fieldTypes.init) {
351                     static if(is(T == class)) {
352                         if(PyTuple_Size(args) != 0)
353                             throw new Exception(T.stringof ~ " has no constructor therefore can't construct one from arguments");
354                         return T.init;
355                     } else
356                         return T(fields);
357                 }
358 
359                 return callDlangFunction!(typeof(impl), impl)(null /*self*/, args, kwargs);
360             }
361         });
362     }
363 }
364 
365 
366 // From a D operator (e.g. `+`) to a Python function pointer member name
367 private string dlangUnOpToPythonSlot(string op) {
368     enum opToSlot = [
369         "+": "tp_as_number.nb_positive",
370         "-": "tp_as_number.nb_negative",
371         "~": "tp_as_number.nb_invert",
372     ];
373     if(op !in opToSlot) throw new Exception("Unknown unary operator " ~ op);
374     return opToSlot[op];
375 }
376 
377 
378 // From a D operator (e.g. `+`) to a Python function pointer member name
379 private string dlangBinOpToPythonSlot(string op) {
380     enum opToSlot = [
381         "+":   "tp_as_number.nb_add",
382         "+=":  "tp_as_number.nb_inplace_add",
383         "-":   "tp_as_number.nb_subtract",
384         "-=":  "tp_as_number.nb_inplace_subtract",
385         "*":   "tp_as_number.nb_multiply",
386         "*=":  "tp_as_number.nb_inplace_multiply",
387         "/":   "tp_as_number.nb_divide",
388         "/=":  "tp_as_number.nb_inplace_divide",
389         "%":   "tp_as_number.nb_remainder",
390         "%=":  "tp_as_number.nb_inplace_remainder",
391         "^^":  "tp_as_number.nb_power",
392         "^^=": "tp_as_number.nb_inplace_power",
393         "&":   "tp_as_number.nb_and",
394         "&=":  "tp_as_number.nb_inplace_and",
395         "|":   "tp_as_number.nb_or",
396         "|=":  "tp_as_number.nb_inplace_or",
397         "^":   "tp_as_number.nb_xor",
398         "^=":  "tp_as_number.nb_inplace_xor",
399         "<<":  "tp_as_number.nb_lshift",
400         "<<=": "tp_as_number.nb_inplace_lshift",
401         ">>":  "tp_as_number.nb_rshift",
402         ">>=": "tp_as_number.nb_inplace_rshift",
403         "~":   "tp_as_sequence.sq_concat",
404         "~=":  "tp_as_sequence.sq_concat",
405         "in":  "tp_as_sequence.sq_contains",
406     ];
407     if(op !in opToSlot) throw new Exception("Unknown binary operator " ~ op);
408     return opToSlot[op];
409 }
410 
411 
412 // From a D operator (e.g. `+`) to a Python function pointer member name
413 private string dlangAssignOpToPythonSlot(string op) {
414     enum opToSlot = [
415         "+":  "tp_as_number.nb_inplace_add",
416         "-":  "tp_as_number.nb_inplace_subtract",
417         "*":  "tp_as_number.nb_inplace_multiply",
418         "/":  "tp_as_number.nb_inplace_divide",
419         "%":  "tp_as_number.nb_inplace_remainder",
420         "^^": "tp_as_number.nb_inplace_power",
421         "&":  "tp_as_number.nb_inplace_and",
422         "|":  "tp_as_number.nb_inplace_or",
423         "^":  "tp_as_number.nb_inplace_xor",
424         "<<": "tp_as_number.nb_inplace_lshift",
425         ">>": "tp_as_number.nb_inplace_rshift",
426         "~":  "tp_as_sequence.sq_concat",
427     ];
428     if(op !in opToSlot) throw new Exception("Unknown assignment operator " ~ op);
429     return opToSlot[op];
430 }
431 
432 
433 private auto pythonArgsToDArgs(bool isVariadic, P...)(PyObject* args, PyObject* kwargs)
434     if(allSatisfy!(isParameter, P))
435 {
436     import python.raw: PyTuple_Size, PyTuple_GetItem, PyTuple_GetSlice, pyUnicodeDecodeUTF8, PyDict_GetItem;
437     import python.conv: to;
438     import std.typecons: Tuple;
439     import std.meta: staticMap;
440     import std.traits: Unqual;
441     import std.conv: text;
442     import std.exception: enforce;
443 
444     const argsLength = args is null ? 0 : PyTuple_Size(args);
445 
446     alias Type(alias Param) = Param.Type;
447     alias Types = staticMap!(Type, P);
448 
449     // If one or more of the parameters is const/immutable,
450     // it'll be hard to construct it as such, so we Unqual
451     // the types for construction and cast to the appropriate
452     // type when returning.
453     alias MutableTuple = Tuple!(staticMap!(Unqual, Types));
454     alias RetTuple = Tuple!(Types);
455 
456     MutableTuple dArgs;
457 
458     void positional(size_t i, T)() {
459         auto item = PyTuple_GetItem(args, i);
460         if(!checkPythonType!T(item)) {
461             import python.raw: PyErr_Clear;
462             PyErr_Clear;
463             throw new ArgumentConversionException("Can't convert to " ~ T.stringof);
464         }
465         dArgs[i] = item.to!T;
466     }
467 
468     int pythonArgIndex = 0;
469     static foreach(i; 0 .. P.length) {
470 
471         static if(i == P.length - 1 && isVariadic) {  // last parameter and it's a typesafe variadic one
472             // slice the remaining arguments
473             auto remainingArgs = PyTuple_GetSlice(args, i, PyTuple_Size(args));
474             dArgs[i] = remainingArgs.to!(P[i].Type);
475         } else static if(is(P[i].Default == void)) {
476             // ith parameter is required
477             enforce(i < argsLength,
478                     text(__FUNCTION__, ": not enough Python arguments"));
479             positional!(i, typeof(dArgs[i]));
480         } else {
481 
482             if(i < argsLength) {  // regular case
483                 positional!(i, P[i].Type);
484             } else {
485                 // Here it gets tricky. The user could have supplied it in
486                 // args positionally or via kwargs
487                 auto key = pyUnicodeDecodeUTF8(&P[i].identifier[0],
488                                                P[i].identifier.length,
489                                                null /*errors*/);
490                 enforce(key, "Errors converting '" ~ P[i].identifier ~ "' to Python object");
491                 auto val = kwargs ? PyDict_GetItem(kwargs, key) : null;
492                 dArgs[i] = val
493                     ? val.to!(P[i].Type) // use kwargs
494                     : P[i].Default; // use default value
495             }
496         }
497     }
498 
499     return cast(RetTuple) dArgs;
500 }
501 
502 
503 private alias Type(alias A) = typeof(A);
504 
505 
506 /**
507    The C API implementation of a Python method F of aggregate type T
508  */
509 struct PythonMethod(T, alias F) {
510     static extern(C) PyObject* _py_method_impl(PyObject* self,
511                                                PyObject* args,
512                                                PyObject* kwargs)
513         nothrow
514     {
515         return noThrowable!(callDlangFunction!(T, F))(self, args, kwargs);
516     }
517 }
518 
519 
520 private void mutateSelf(T)(PyObject* self, auto ref T dAggregate) {
521 
522     import python.conv.d_to_python: toPython;
523     import python.raw: pyDecRef;
524 
525     auto newSelf = {
526         return self is null ? self : toPython(dAggregate);
527     }();
528     scope(exit) {
529         if(self !is null) pyDecRef(newSelf);
530     }
531     auto pyClassSelf = cast(PythonClass!T*) self;
532     auto pyClassNewSelf = cast(PythonClass!T*) newSelf;
533 
534     static foreach(i; 0 .. PythonClass!T.fieldNames.length) {
535         if(self !is null)
536             pyClassSelf.set!i(self, pyClassNewSelf.get!i(newSelf));
537     }
538 
539 }
540 
541 
542 /**
543    The C API implementation that calls a D function F.
544  */
545 struct PythonFunction(alias F) {
546     static extern(C) PyObject* _py_function_impl(PyObject* self, PyObject* args, PyObject* kwargs) nothrow {
547         return noThrowable!(callDlangFunction!(void, F))(self, args, kwargs);
548     }
549 }
550 
551 
552 private auto noThrowable(alias F, A...)(auto ref A args) {
553     import python.raw: PyErr_SetString, PyExc_RuntimeError;
554     import std.string: toStringz;
555     import std.traits: ReturnType;
556 
557     try {
558         return F(args);
559     } catch(Exception e) {
560         PyErr_SetString(PyExc_RuntimeError, e.msg.toStringz);
561         return ReturnType!F.init;
562     } catch(Error e) {
563         import std.conv: text;
564         try
565             PyErr_SetString(PyExc_RuntimeError, ("FATAL ERROR: " ~ e.text).toStringz);
566         catch(Exception _)
567             PyErr_SetString(PyExc_RuntimeError, ("FATAL ERROR: " ~ e.msg).toStringz);
568 
569         return ReturnType!F.init;
570     }
571 }
572 
573 
574 class ArgsException: Exception {
575     import std.exception: basicExceptionCtors;
576     mixin basicExceptionCtors;
577 }
578 
579 private PyObject* callDlangFunction(T, alias F)(PyObject* self, PyObject* args, PyObject *kwargs) {
580 
581     import autowrap.reflection: FunctionParameters, NumDefaultParameters, NumRequiredParameters;
582     import python.raw: PyTuple_Size;
583     import python.conv: toPython, to;
584     import std.traits: Parameters, variadicFunctionStyle, Variadic, ReturnType,
585         functionAttributes, FunctionAttribute, moduleName, isCallable;
586     import std.conv: text;
587     import std.exception: enforce;
588     import std.meta: AliasSeq;
589 
590     enum identifier = __traits(identifier, F);
591     enum isCtor = isUserAggregate!T && identifier == "__ctor";
592     enum isMethod = isUserAggregate!T && identifier != "__ctor";
593 
594     static if(is(T == void)) { // regular function
595         enum parent = moduleName!F;
596         mixin(`static import `, parent, `;`);
597         mixin(`alias Parent = `, parent, `;`);
598     } else static if(isMethod) {
599         enum parent =  "dAggregate";
600         alias Parent = T;
601     } else static if(isCallable!T) {
602         // nothing to do here
603     } else static if(isCtor) {
604         alias Parent = T;
605     } else
606         static assert(false, __FUNCTION__ ~ " does not know how to handle " ~ T.stringof);
607 
608     static if(is(T == void))
609         enum callMixin = `auto ret = callDlangFunction!F(dArgs);`;
610     else static if(isMethod)
611         enum callMixin = `auto ret = callDlangFunction!((Parameters!overload dArgs) => ` ~ parent ~ `.` ~ identifier ~ `(dArgs))(dArgs);`;
612     else static if(isCtor) {
613         static if(is(T == class))
614             enum callMixin = `auto ret = callDlangFunction!((Parameters!overload dArgs) => new T(dArgs))(dArgs);`;
615         else
616             enum callMixin = `auto ret = callDlangFunction!((Parameters!overload dArgs) => T(dArgs))(dArgs);`;
617     } else static if(isCallable!T && !isUserAggregate!T)
618         enum callMixin = `auto ret = callDlangFunction!F(dArgs);`;
619     else
620         static assert(false);
621 
622     static if(__traits(compiles, __traits(getOverloads, Parent, identifier))) {
623         alias candidates = __traits(getOverloads, Parent, identifier);
624         // Deal with possible template instantiation functions.
625         // If it's a free function (T is void), then there must be at least
626         // one overload. The only reason for there to not be one is because
627         // it's a function template.
628         static if(is(T == void) && candidates.length == 0)
629             alias overloads = AliasSeq!F;
630         else
631             alias overloads = candidates;
632     } else
633         alias overloads = AliasSeq!F;
634 
635     static foreach(overload; overloads) {{
636         enum numDefaults = NumDefaultParameters!overload;
637         enum numRequired = NumRequiredParameters!overload;
638         enum isVariadic = variadicFunctionStyle!overload == Variadic.typesafe;
639         enum isMemberFunction = !__traits(isStaticFunction, overload) && !is(T == void);
640 
641         static if(isUserAggregate!T && isMemberFunction && !isCtor)
642             assert(self !is null,
643                    "Cannot call PythonMethod!" ~ identifier ~ " on null self");
644 
645         static if(functionAttributes!overload & FunctionAttribute.const_)
646             alias Aggregate = const T;
647         else static if(functionAttributes!overload & FunctionAttribute.immutable_)
648             alias Aggregate = immutable T;
649         else
650             alias Aggregate = Unqual!T;
651 
652         static if(isUserAggregate!T) { // member function, static or not
653             // self could be null for static member functions
654             auto dAggregate = self is null ? Aggregate.init : self.to!Aggregate;
655         }
656 
657         try {
658             const numArgs = args is null ? 0 : PyTuple_Size(args);
659             if(!isVariadic)
660                 enforce!ArgumentConversionException(
661                     numArgs >= numRequired
662                     && numArgs <= Parameters!overload.length,
663                     text("Received ", numArgs, " parameters but ",
664                          identifier, " takes ", Parameters!overload.length));
665 
666             auto dArgs = pythonArgsToDArgs!(isVariadic, FunctionParameters!overload)(args, kwargs);
667             mixin(callMixin);
668 
669             static if(isUserAggregate!T && isMemberFunction && !isConstMemberFunction!overload) {
670                 mutateSelf(self, dAggregate);
671             }
672 
673             return ret;
674         } catch(ArgumentConversionException _) {
675             // only using this to weed out incompatible overloads
676         }
677     }}
678 
679     throw new Exception("Could not find suitable overload for `" ~ identifier ~ "`");
680 }
681 
682 
683 class ArgumentConversionException: Exception {
684     import std.exception: basicExceptionCtors;
685     mixin basicExceptionCtors;
686 }
687 
688 
689 private PyObject* callDlangFunction(alias F, A)(auto ref A argTuple) {
690     import python.raw: pyIncRef, pyNone;
691     import python.conv: toPython;
692     import std.traits: ReturnType;
693 
694     // TODO - side-effects on parameters?
695     static if(is(ReturnType!F == void)) {
696         F(argTuple.expand);
697         pyIncRef(pyNone);
698         return pyNone;
699     } else {
700         auto dret = F(argTuple.expand);
701         return dret.toPython;
702     }
703 }
704 
705 
706 /**
707    Creates an instance of a Python class that is equivalent to the D type `T`.
708    Return PyObject*.
709  */
710 PyObject* pythonClass(T)(auto ref T dobj) {
711 
712     import python.conv: toPython;
713     import python.raw: pyObjectNew;
714     import std.traits: isPointer, PointerTarget;
715 
716     static if(is(T == class) || isPointer!T) {
717         if(dobj is null)
718             throw new Exception("Cannot create Python class from null D object");
719     }
720 
721     static if(isPointer!T)
722         alias Type = PointerTarget!T;
723     else
724         alias Type = T;
725 
726     auto _type = PythonType!Type.pyType;
727 
728     auto ret = pyObjectNew!(PythonClass!Type)(PythonType!Type.pyType);
729 
730     static foreach(fieldName; PythonType!Type.fieldNames) {
731         static if(isPublic!(T, fieldName))
732             mixin(`ret.`, fieldName, ` = dobj.`, fieldName, `.toPython;`);
733     }
734 
735     return cast(PyObject*) ret;
736 }
737 
738 
739 private template isPublic(T, string memberName) {
740 
741     static if(__traits(compiles, __traits(getProtection, __traits(getMember, T, memberName)))) {
742         enum protection = __traits(getProtection, __traits(getMember, T, memberName));
743         enum isPublic = protection == "public" || protection == "export";
744     } else
745         enum isPublic = false;
746 }
747 
748 /**
749    OOP types register factory functions here, indexed by the fully qualified
750    name of the type. This allows us to construct D class types from the
751    runtime types of Python values.
752  */
753 Object delegate(PyObject*)[string] gFactory;
754 
755 /**
756    A Python class that mirrors the D type `T`.
757    For instance, this struct:
758    ----------
759    struct Foo {
760        int i;
761        string s;
762    }
763    ----------
764 
765    Will generate a Python class called `Foo` with two members, and trying to
766    assign anything but an integer to `Foo.i` or a string to `Foo.s` in Python
767    will raise `TypeError`.
768  */
769 struct PythonClass(T) if(isUserAggregate!T) {
770     import python.raw: PyObjectHead, PyGetSetDef;
771     import std.traits: Unqual;
772 
773     alias fieldNames = PythonType!(Unqual!T).fieldNames;
774     alias fieldTypes = PythonType!(Unqual!T).fieldTypes;
775 
776     // Every python object must have this
777     mixin PyObjectHead;
778 
779     // Field members
780     // Generate a python object field for every field in T
781     static foreach(fieldName; fieldNames) {
782         mixin(`PyObject* `, fieldName, `;`);
783     }
784 
785     static if(is(T == class)) {
786         static this() {
787             import std.traits: fullyQualifiedName;
788 
789             gFactory[fullyQualifiedName!(Unqual!T)] = (PyObject* value) {
790                 import python.conv.python_to_d: to;
791 
792                 auto pyclass = cast(PythonClass!T*) value;
793                 auto ret = userAggregateInit!(Unqual!T);
794 
795                 static foreach(fieldName; fieldNames) {{
796                     alias Field = typeof(__traits(getMember, ret, fieldName));
797                     __traits(getMember, ret, fieldName) = __traits(getMember, pyclass, fieldName).to!Field;
798                 }}
799 
800                 return cast(Object) ret;
801             };
802         }
803     }
804 
805     // The function pointer for PyGetSetDef.get
806     private static extern(C) PyObject* get(int FieldIndex)
807                                           (PyObject* self_, void* closure = null)
808         nothrow
809         in(self_ !is null)
810     {
811         import python.raw: pyIncRef;
812 
813         auto self = cast(PythonClass*) self_;
814 
815         auto field = self.getField!FieldIndex;
816         assert(field !is null, "Cannot increase reference count on null field");
817         pyIncRef(field);
818 
819         return field;
820     }
821 
822     // The function pointer for PyGetSetDef.set
823     static extern(C) int set(int FieldIndex)
824                             (PyObject* self_, PyObject* value, void* closure = null)
825         nothrow
826         in(self_ !is null)
827     {
828         import python.raw: pyIncRef, pyDecRef, PyErr_SetString, PyExc_TypeError;
829 
830         if(value is null) {
831             enum deleteErrStr = "Cannot delete " ~ fieldNames[FieldIndex];
832             PyErr_SetString(PyExc_TypeError, deleteErrStr);
833             return -1;
834         }
835 
836         if(!checkPythonType!(fieldTypes[FieldIndex])(value)) {
837             return -1;
838         }
839 
840         auto self = cast(PythonClass!T*) self_;
841         auto tmp = self.getField!FieldIndex;
842 
843         pyIncRef(value);
844         mixin(`self.`, fieldNames[FieldIndex], ` = value;`);
845         pyDecRef(tmp);
846 
847         return 0;
848     }
849 
850     PyObject* getField(int FieldIndex)() {
851         mixin(`return this.`, fieldNames[FieldIndex], `;`);
852     }
853 
854     static extern(C) PyObject* propertyGet(alias F)
855                                           (PyObject* self_, void* closure = null)
856         nothrow
857         in(self_ !is null)
858     {
859         return PythonMethod!(T, F)._py_method_impl(self_, null /*args*/, null /*kwargs*/);
860     }
861 
862     static extern(C) int propertySet(alias F)
863                                     (PyObject* self_, PyObject* value, void* closure = null)
864         nothrow
865         in(self_ !is null)
866     {
867         import python.raw: PyTuple_New, PyTuple_SetItem, pyDecRef;
868 
869         auto args = PyTuple_New(1);
870         PyTuple_SetItem(args, 0, value);
871         scope(exit) pyDecRef(args);
872 
873         PythonMethod!(T, F)._py_method_impl(self_, args, null /*kwargs*/);
874 
875         return 0;
876     }
877 }
878 
879 
880 PyObject* pythonCallable(T)(T callable) {
881     import python.raw: pyObjectNew;
882 
883     auto ret = pyObjectNew!(PythonCallable!T)(PythonType!T.pyType);
884     ret._callable = callable;
885 
886     return cast(PyObject*) ret;
887 }
888 
889 
890 private struct PythonCallable(T) if(isCallable!T) {
891 
892     import std.traits: hasMember;
893 
894     static if(hasMember!(T, "opCall")) {
895         private static extern(C) PyObject* _py_call(PyObject* self, PyObject* args, PyObject* kwargs) nothrow {
896             return PythonMethod!(T, T.opCall)._py_method_impl(self, args, kwargs);
897         }
898     } else {
899         /**
900            Reserves space for a callable to be stored in a PyObject struct so that it
901            can later be called.
902         */
903 
904         import python.raw: PyObjectHead;
905 
906         // Every python object must have this
907         mixin PyObjectHead;
908 
909         private T _callable;
910 
911         private static extern(C) PyObject* _py_call(PyObject* self_, PyObject* args, PyObject* kwargs)
912             nothrow
913             in(self_ !is null)
914             do
915         {
916             import std.traits: Parameters, ReturnType;
917             auto self = cast(PythonCallable!T*) self_;
918             assert(self._callable !is null, "Cannot have null callable");
919             return noThrowable!(callDlangFunction!(T, (Parameters!T args) => self._callable(args)))(self_, args, kwargs);
920         }
921     }
922 }
923 
924 private bool isConstMemberFunction(alias F)() {
925     import std.traits: functionAttributes, FunctionAttribute;
926     return cast(bool) (functionAttributes!F & FunctionAttribute.const_);
927 }
928 
929 
930 private template PythonUnaryOperator(T, string op) {
931     static extern(C) PyObject* _py_un_op(PyObject* self) nothrow {
932         return noThrowable!({
933             import python.conv.python_to_d: to;
934             import python.conv.d_to_python: toPython;
935             import std.traits: Parameters;
936 
937             static assert(Parameters!(T.opUnary!op).length == 0, "opUnary can't take any parameters");
938 
939             return self.to!T.opUnary!op.toPython;
940         });
941     }
942 }
943 
944 
945 private template PythonBinaryOperator(T, BinaryOperator operator) {
946 
947     static extern(C) int _py_in_func(PyObject* lhs, PyObject* rhs)
948         nothrow
949         in(operator.op == "in")
950     {
951         import python.conv.python_to_d: to;
952         import python.conv.d_to_python: toPython;
953         import std.traits: Parameters, hasMember;
954 
955         alias inParams(U) = Parameters!(U.opBinaryRight!(operator.op));
956 
957         static if(__traits(compiles, inParams!T))
958             alias parameters = inParams!T;
959         else
960             alias parameters = void;
961 
962         static if(is(typeof(T.init.opBinaryRight!(operator.op)(parameters.init)): bool)) {
963             return noThrowable!({
964 
965                 static assert(parameters.length == 1, "opBinaryRight!in must have one parameter");
966                 alias Arg = parameters[0];
967 
968                 auto this_ = lhs.to!T;
969                 auto dArg  = rhs.to!Arg;
970 
971                 const ret = this_.opBinaryRight!(operator.op)(dArg);
972                 // See https://docs.python.org/3/c-api/sequence.html#c.PySequence_Contains
973                 return ret ? 1 : 0;
974             });
975         } else {
976             // Error. See https://docs.python.org/3/c-api/sequence.html#c.PySequence_Contains
977             return -1;
978         }
979     }
980 
981     static extern(C) PyObject* _py_bin_func(PyObject* lhs, PyObject* rhs) nothrow {
982         return _py_ter_func(lhs, rhs, null);
983     }
984 
985     // Should only be for `^^` because in Python the function is ternary
986     static extern(C) PyObject* _py_ter_func(PyObject* lhs_, PyObject* rhs_, PyObject* extra) nothrow {
987         import python.conv.python_to_d: to;
988         import python.conv.d_to_python: toPython;
989         import autowrap.reflection: BinOpDir, functionName;
990         import std.traits: Parameters;
991         import std.exception: enforce;
992         import std.conv: text;
993 
994         return noThrowable!({
995 
996             PyObject* self, pArg;
997             if(lhs_.isInstanceOf!T) {
998                 self = lhs_;
999                 pArg = rhs_;
1000             } else if(rhs_.isInstanceOf!T) {
1001                 self = rhs_;
1002                 pArg = lhs_;
1003             } else
1004                 throw new Exception("Neither lhs or rhs were of type " ~ T.stringof);
1005 
1006             PyObject* impl(BinOpDir dir)() {
1007                 enum funcName = functionName(dir);
1008                 static if(operator.dirs & dir) {
1009                     mixin(`alias parameters = Parameters!(T.`, funcName, `!(operator.op));`);
1010                     static assert(parameters.length == 1, "Binary operators must take one parameter");
1011                     alias Arg = parameters[0];
1012 
1013                     auto this_ = self.to!T;
1014                     auto dArg  = pArg.to!Arg;
1015                     mixin(`return this_.`, funcName, `!(operator.op)(dArg).toPython;`);
1016                 } else {
1017                     throw new Exception(text(T.stringof, " does not support ", funcName, " with self on ", dir));
1018                 }
1019             }
1020 
1021             if(lhs_.isInstanceOf!T) {  // self is on the left hand side
1022                 return impl!(BinOpDir.left);
1023             } else if(rhs_.isInstanceOf!T) {  // self is on the right hand side
1024                 return impl!(BinOpDir.right);
1025             } else {
1026                 throw new Exception("Neither lhs or rhs were of type " ~ T.stringof);
1027             }
1028         });
1029     }
1030 }
1031 
1032 private template PythonAssignOperator(T, string op) {
1033 
1034     static extern(C) PyObject* _py_bin_func(PyObject* lhs, PyObject* rhs) nothrow {
1035         return _py_ter_func(lhs, rhs, null);
1036     }
1037 
1038     // Should only be for `^^` because in Python the function is ternary
1039     static extern(C) PyObject* _py_ter_func(PyObject* lhs, PyObject* rhs, PyObject* extra) nothrow {
1040         import python.conv.python_to_d: to;
1041         import python.conv.d_to_python: toPython;
1042         import std.traits: Parameters;
1043 
1044         PyObject* impl() {
1045             alias parameters = Parameters!(T.opOpAssign!op);
1046             static assert(parameters.length == 1, "Assignment operators must take one parameter");
1047 
1048             auto dObj = lhs.to!T;
1049             dObj.opOpAssign!op(rhs.to!(parameters[0]));
1050             return dObj.toPython;
1051         }
1052 
1053         return noThrowable!impl;
1054     }
1055 }
1056 
1057 
1058 private template PythonOpCmp(T) {
1059     static extern(C) PyObject* _py_cmp(PyObject* lhs, PyObject* rhs, int opId) nothrow {
1060         import python.raw: Py_LT, Py_LE, Py_EQ, Py_NE, Py_GT, Py_GE;
1061         import python.conv.python_to_d: to;
1062         import python.conv.d_to_python: toPython;
1063         import std.conv: text;
1064         import std.traits: Unqual, Parameters;
1065 
1066         return noThrowable!({
1067 
1068             alias parameters = Parameters!(T.opCmp);
1069             static assert(parameters.length == 1, T.stringof ~ ".opCmp must have exactly one parameter");
1070 
1071             const cmp = lhs.to!(Unqual!T).opCmp(rhs.to!(Unqual!(parameters[0])));
1072 
1073             const dRes = {
1074                 switch(opId) {
1075                     default: throw new Exception(text("Unknown opId for opCmp: ", opId));
1076                     case Py_LT: return cmp < 0;
1077                     case Py_LE: return cmp <= 0;
1078                     case Py_EQ: return cmp == 0;
1079                     case Py_NE: return cmp !=0;
1080                     case Py_GT: return cmp > 0;
1081                     case Py_GE: return cmp >=0;
1082                 }
1083             }();
1084 
1085             return dRes.toPython;
1086        });
1087     }
1088 }
1089 
1090 
1091 private template PythonSubscript(T) {
1092 
1093     static extern(C) PyObject* _py_index(PyObject* self, PyObject* key) nothrow {
1094         import python.raw: pyIndexCheck, pySliceCheck, PyObject_Repr, PyObject_Length,
1095             Py_ssize_t, PySlice_GetIndices;
1096         import python.conv.python_to_d: to;
1097         import python.conv.d_to_python: toPython;
1098         import std.traits: Parameters, Unqual, hasMember;
1099         import std.meta: Filter;
1100 
1101         PyObject* impl() {
1102             static if(hasMember!(T, "opIndex")) {
1103                 if(pyIndexCheck(key)) {
1104                     static if(__traits(compiles, Parameters!(T.opIndex))) {
1105                         alias parameters = Parameters!(T.opIndex);
1106                         static if(parameters.length == 1)
1107                             return self.to!(Unqual!T).opIndex(key.to!(parameters[0])).toPython;
1108                         else
1109                             throw new Exception("Don't know how to handle opIndex with more than one parameter");
1110                     }
1111                 } else if(pySliceCheck(key)) {
1112 
1113                     enum hasTwoParams(alias F) = Parameters!F.length == 2;
1114                     alias twoParamOpSlices = Filter!(hasTwoParams, __traits(getOverloads, T, "opSlice"));
1115 
1116                     static if(twoParamOpSlices.length > 0) {
1117 
1118                         static assert(twoParamOpSlices.length == 1);
1119                         alias opSlice = twoParamOpSlices[0];
1120 
1121                         const len = PyObject_Length(self);
1122                         Py_ssize_t start, stop, step;
1123                         const indicesRet = PySlice_GetIndices(key, len, &start, &stop, &step);
1124 
1125                         if(indicesRet < 0)
1126                             throw new Exception("Could not get slice indices for key '" ~ PyObject_Repr(key).to!string ~ "'");
1127 
1128                         if(step != 1)
1129                             throw new Exception("Slice steps other than 1 not supported in D: " ~ PyObject_Repr(key).to!string);
1130 
1131                         auto dObj = self.to!T;
1132                         return dObj[start .. stop].toPython;
1133 
1134                     } else {
1135                         throw new Exception(T.stringof ~ " cannot be sliced by " ~ PyObject_Repr(key).to!string);
1136                     }
1137 
1138                     assert(0, "Error in slicing " ~ T.stringof ~ " with " ~ PyObject_Repr(key).to!string);
1139                 } else
1140                     throw new Exception(T.stringof ~ " failed pyIndexCheck and pySliceCheck for key '" ~ PyObject_Repr(key).to!string ~ "'");
1141                 assert(0);
1142             } else
1143                 throw new Exception(T.stringof ~ " has no opIndex");
1144         }
1145 
1146         return noThrowable!impl;
1147     }
1148 }
1149 
1150 
1151 /**
1152    Implement a Python iterator for D type T.
1153    We get a D slice from it, convert it to a Python list,
1154    then return its iterator.
1155  */
1156 private template PythonIter(T) {
1157 
1158     static extern(C) PyObject* _py_iter(PyObject* self) nothrow {
1159         import python.raw: PyObject_GetIter;
1160         import python.conv.d_to_python: toPython;
1161         import python.conv.python_to_d: to;
1162         import std.array;
1163 
1164         PyObject* impl() {
1165             static if(__traits(compiles, T.init[].array[0])) {
1166                 auto dObj = self.to!T;
1167                 auto list = dObj[].array.toPython;
1168                 return PyObject_GetIter(list);
1169             } else {
1170                 throw new Exception("Cannot get an array from " ~ T.stringof ~ "[]");
1171             }
1172         }
1173 
1174         return noThrowable!impl;
1175     }
1176 }
1177 
1178 
1179 private template PythonIndexAssign(T) {
1180 
1181     static extern(C) int _py_index_assign(PyObject* self, PyObject* key, PyObject* val) nothrow {
1182 
1183         import python.conv.python_to_d: to;
1184         import python.conv.d_to_python: toPython;
1185         import python.raw: pyIndexCheck, pySliceCheck, PyObject_Repr, PyObject_Length, PySlice_GetIndices, Py_ssize_t;
1186         import std.traits: Parameters, Unqual;
1187         import std.conv: to;
1188         import std.meta: Filter, AliasSeq;
1189 
1190         int impl() {
1191             if(pyIndexCheck(key)) {
1192                 static if(__traits(compiles, Parameters!(T.opIndexAssign))) {
1193                     alias parameters = Parameters!(T.opIndexAssign);
1194                     static if(parameters.length == 2) {
1195                         auto dObj = self.to!(Unqual!T);
1196                         dObj.opIndexAssign(val.to!(parameters[0]), key.to!(parameters[1]));
1197                         mutateSelf(self, dObj);
1198                         return 0;
1199                     } else
1200                         //throw new Exception("Don't know how to handle opIndex with more than one parameter");
1201                         return -1;
1202                 } else
1203                     return -1;
1204             } else if(pySliceCheck(key)) {
1205 
1206                 enum hasThreeParams(alias F) = Parameters!F.length == 3;
1207                 alias threeParamOps = Filter!(hasThreeParams,
1208                                               AliasSeq!(
1209                                                   __traits(getOverloads, T, "opIndexAssign"),
1210                                                   __traits(getOverloads, T, "opSliceAssign"),
1211                                               )
1212                 );
1213 
1214                 static if(threeParamOps.length > 0) {
1215 
1216                     static assert(threeParamOps.length == 1);
1217                     alias opIndexAssign = threeParamOps[0];
1218                     alias parameters = Parameters!opIndexAssign;
1219 
1220                     const len = PyObject_Length(self);
1221                     Py_ssize_t start, stop, step;
1222                     const indicesRet = PySlice_GetIndices(key, len, &start, &stop, &step);
1223 
1224                     if(indicesRet < 0)
1225                         return -1;
1226 
1227                     if(step != 1)
1228                         return -1;
1229 
1230                     auto dObj = self.to!(Unqual!T);
1231                     mixin(`dObj.`, __traits(identifier, opIndexAssign), `(val.to!(parameters[0]), start, stop);`);
1232                     mutateSelf(self, dObj);
1233                     return 0;
1234                 } else {
1235                     return -1;
1236                 }
1237             } else
1238                 return -1;
1239         }
1240 
1241         return noThrowable!impl;
1242     }
1243 }
1244 
1245 private bool isInstanceOf(T)(PyObject* obj) {
1246     import python.raw: PyObject_IsInstance;
1247     return cast(bool) PyObject_IsInstance(obj, cast(PyObject*) PythonType!T.pyType);
1248 }
1249 
1250 
1251 private bool checkPythonType(T)(PyObject* value) if(isArray!T) {
1252     import python.raw: pySequenceCheck;
1253     const ret = pySequenceCheck(value);
1254     if(!ret) setPyErrTypeString!"sequence";
1255     return ret;
1256 }
1257 
1258 
1259 private bool checkPythonType(T)(PyObject* value) if(isIntegral!T) {
1260     import python.raw: pyIntCheck, pyLongCheck;
1261     const ret = pyLongCheck(value) || pyIntCheck(value);
1262     if(!ret) setPyErrTypeString!"long";
1263     return ret;
1264 }
1265 
1266 
1267 private bool checkPythonType(T)(PyObject* value) if(isFloatingPoint!T) {
1268     import python.raw: pyFloatCheck;
1269     const ret = pyFloatCheck(value);
1270     if(!ret) setPyErrTypeString!"float";
1271     return ret;
1272 }
1273 
1274 
1275 private bool checkPythonType(T)(PyObject* value) if(is(T == DateTime)) {
1276     import python.raw: pyDateTimeCheck;
1277     const ret = pyDateTimeCheck(value);
1278     if(!ret) setPyErrTypeString!"DateTime";
1279     return ret;
1280 }
1281 
1282 
1283 private bool checkPythonType(T)(PyObject* value) if(is(T == Date)) {
1284     import python.raw: pyDateCheck;
1285     const ret = pyDateCheck(value);
1286     if(!ret) setPyErrTypeString!"Date";
1287     return ret;
1288 }
1289 
1290 
1291 private bool checkPythonType(T)(PyObject* value) if(isAssociativeArray!T) {
1292     import python.raw: pyMappingCheck;
1293     const ret = pyMappingCheck(value);
1294     if(!ret) setPyErrTypeString!"dict";
1295     return ret;
1296 }
1297 
1298 
1299 private bool checkPythonType(T)(PyObject* value) if(isUserAggregate!T) {
1300     return true;  // FIXMME
1301 }
1302 
1303 
1304 private bool checkPythonType(T)(PyObject* value) if(isSomeFunction!T) {
1305     import python.raw: pyCallableCheck;
1306     const ret = pyCallableCheck(value);
1307     if(!ret) setPyErrTypeString!"callable";
1308     return ret;
1309 }
1310 
1311 
1312 private void setPyErrTypeString(string type)() @trusted @nogc nothrow {
1313     import python.raw: PyErr_SetString, PyExc_TypeError;
1314     enum str = "must be a " ~ type;
1315     PyErr_SetString(PyExc_TypeError, &str[0]);
1316 }
1317 
1318 // Generalises T.init for classes since null isn't a value we want to use
1319 T userAggregateInit(T)() {
1320     static if(is(T == class)) {
1321         auto buffer = new void[__traits(classInstanceSize, T)];
1322         // this is needed for the vtable to work
1323         buffer[] = typeid(T).initializer[];
1324         return cast(T) buffer.ptr;
1325     } else
1326         return T.init;
1327 }