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, isAggregateType, isCallable;
10 import std.datetime: DateTime, Date;
11 import std.typecons: Tuple;
12 import std.range.primitives: isInputRange;
13 import std.meta: allSatisfy;
14 
15 
16 package enum isPhobos(T) = isDateOrDateTime!T || isTuple!T;
17 package enum isDateOrDateTime(T) = is(Unqual!T == DateTime) || is(Unqual!T == Date);
18 package enum isTuple(T) = is(T: Tuple!A, A...);
19 package enum isUserAggregate(T) = isAggregateType!T && !isPhobos!(T);
20 package enum isNonRangeUDT(T) = isUserAggregate!T && !isInputRange!T;
21 
22 
23 /**
24    A wrapper for `PyTypeObject`.
25 
26    This struct does all of the necessary boilerplate to intialise
27    a `PyTypeObject` for a Python extension type that mimics the D
28    type `T`.
29  */
30 struct PythonType(T) {
31     import python.raw: PyTypeObject, PySequenceMethods;
32     import std.traits: FieldNameTuple, Fields;
33     import std.meta: Alias, staticMap;
34 
35     alias fieldNames = FieldNameTuple!T;
36     alias fieldTypes = Fields!T;
37     enum hasLength = is(typeof({ size_t len = T.init.length; }));
38 
39     static PyTypeObject _pyType;
40     static bool failedToReady;
41 
42     static PyObject* pyObject() {
43         initialise;
44         return failedToReady ? null : cast(PyObject*) &_pyType;
45     }
46 
47     static PyTypeObject* pyType() nothrow {
48         initialise;
49         return failedToReady ? null : &_pyType;
50     }
51 
52     private static void initialise() nothrow {
53         import python.raw: PyType_GenericNew, PyType_Ready, TypeFlags,
54             PyErr_SetString, PyExc_TypeError,
55             PyNumberMethods, PySequenceMethods;
56         import autowrap.reflection: UnaryOperators, BinaryOperators, functionName;
57         import std.traits: arity, hasMember, TemplateOf;
58         import std.meta: Filter;
59         static import std.typecons;
60 
61         if(_pyType != _pyType.init) return;
62 
63         _pyType.tp_name = T.stringof;
64         _pyType.tp_flags = TypeFlags.Default;
65 
66         // FIXME: types are that user aggregates *and* callables
67         static if(isUserAggregate!T) {
68             _pyType.tp_basicsize = PythonClass!T.sizeof;
69             _pyType.tp_getset = getsetDefs;
70             _pyType.tp_methods = methodDefs;
71             _pyType.tp_new = &_py_new;
72             _pyType.tp_repr = &_py_repr;
73             _pyType.tp_init = &_py_init;
74 
75             // special-case std.typecons.Typedef
76             // see: https://issues.dlang.org/show_bug.cgi?id=20117
77             static if(
78                 hasMember!(T, "opCmp")
79                 && !__traits(isSame, TemplateOf!T, std.typecons.Typedef)
80                 && &T.opCmp !is &Object.opCmp
81                 )
82             {
83                 _pyType.tp_richcompare = &PythonOpCmp!T._py_cmp;
84             }
85 
86             enum isPythonableUnary(string op) = op == "+" || op == "-" || op == "~";
87             enum unaryOperators = Filter!(isPythonableUnary, UnaryOperators!T);
88             alias binaryOperators = BinaryOperators!T;
89 
90             static if(unaryOperators.length > 0 || binaryOperators.length > 0) {
91                 _pyType.tp_as_number = new PyNumberMethods;
92                 _pyType.tp_as_sequence = new PySequenceMethods;
93             }
94 
95             static foreach(op; unaryOperators) {
96                 mixin(`_pyType.`, dlangUnOpToPythonSlot(op), ` = &PythonUnaryOperator!(T, op)._py_un_op;`);
97             }
98 
99             static foreach(binOp; binaryOperators) {{
100                 // first get the Python function pointer name
101                 enum slot = dlangBinOpToPythonSlot(binOp.op);
102                 // some of them differ in arity
103                 enum slotArity = arity!(mixin(`typeof(PyTypeObject.`, slot, `)`));
104 
105                 // get the function name in PythonBinaryOperator
106                 // `in` is special because the function signature is different
107                 static if(binOp.op == "in") {
108                     enum cFuncName = "_py_in_func";
109                 } else {
110                     static if(slotArity == 2)
111                         enum cFuncName = "_py_bin_func";
112                     else static if(slotArity == 3)
113                         enum cFuncName = "_py_ter_func";
114                     else
115                         static assert("Do not know how to deal with slot " ~ slot);
116                 }
117 
118                 // set the C function that implements this operator
119                 mixin(`_pyType.`, slot, ` = &PythonBinaryOperator!(T, binOp).`, cFuncName, `;`);
120             }}
121 
122 
123         } else static if(isCallable!T) {
124             _pyType.tp_basicsize = PythonCallable!T.sizeof;
125             _pyType.tp_call = &PythonCallable!T._py_call;
126         } else
127             static assert(false, "Don't know what to do for type " ~ T.stringof);
128 
129         static if(hasLength) {
130             if(_pyType.tp_as_sequence is null)
131                 _pyType.tp_as_sequence = new PySequenceMethods;
132             _pyType.tp_as_sequence.sq_length = &_py_length;
133         }
134 
135         if(PyType_Ready(&_pyType) < 0) {
136             PyErr_SetString(PyExc_TypeError, &"not ready"[0]);
137             failedToReady = true;
138         }
139     }
140 
141     static if(isUserAggregate!T)
142     private static auto getsetDefs() {
143         import autowrap.reflection: Properties;
144         import python.raw: PyGetSetDef;
145         import std.meta: staticMap, Filter, Alias;
146         import std.traits: isFunction, ReturnType;
147 
148         alias AggMember(string memberName) = Alias!(__traits(getMember, T, memberName));
149         alias memberNames = __traits(allMembers, T);
150         enum isPublic(string memberName) = (__traits(getProtection, __traits(getMember, T, memberName)) == "public");
151         alias publicMemberNames = Filter!(isPublic, memberNames);
152         alias members = staticMap!(AggMember, publicMemberNames);
153         alias memberFunctions = Filter!(isFunction, members);
154         alias properties = Properties!memberFunctions;
155 
156         // +1 due to the sentinel
157         static PyGetSetDef[fieldNames.length + properties.length + 1] getsets;
158 
159         // don't bother if already initialised
160         if(getsets != getsets.init) return &getsets[0];
161 
162         // first deal with the public fields
163         static foreach(i; 0 .. fieldNames.length) {
164             getsets[i].name = cast(typeof(PyGetSetDef.name)) fieldNames[i];
165             static if(__traits(getProtection, __traits(getMember, T, fieldNames[i])) == "public") {
166                 getsets[i].get = &PythonClass!T.get!i;
167                 getsets[i].set = &PythonClass!T.set!i;
168             }
169         }
170 
171         // then deal with the property functions
172         static foreach(j, property; properties) {{
173             enum i = fieldNames.length + j;
174 
175             getsets[i].name = cast(typeof(PyGetSetDef.name)) __traits(identifier, property);
176 
177             static foreach(overload; __traits(getOverloads, T, __traits(identifier, property))) {
178                 static if(is(ReturnType!overload == void))
179                     getsets[i].set = &PythonClass!T.propertySet!(overload);
180                 else
181                     getsets[i].get = &PythonClass!T.propertyGet!(overload);
182             }
183         }}
184 
185         return &getsets[0];
186     }
187 
188     private static auto methodDefs()() {
189         import autowrap.reflection: isProperty;
190         import python.raw: PyMethodDef, MethodArgs;
191         import python.cooked: pyMethodDef, defaultMethodFlags;
192         import std.meta: AliasSeq, Alias, staticMap, Filter, templateNot;
193         import std.traits: isSomeFunction;
194         import std.algorithm: startsWith;
195 
196         alias memberNames = AliasSeq!(__traits(allMembers, T));
197         enum ispublic(string name) = isPublic!(T, name);
198         alias publicMemberNames = Filter!(ispublic, memberNames);
199         enum isRegular(string name) =
200             name != "this"
201             && name != "toHash"
202             && name != "factory"
203             && !name.startsWith("op")
204             && name != "__ctor"
205             ;
206         alias regularMemberNames = Filter!(isRegular, publicMemberNames);
207         alias Member(string name) = Alias!(__traits(getMember, T, name));
208         alias members = staticMap!(Member, regularMemberNames);
209         alias memberFunctions = Filter!(templateNot!isProperty, Filter!(isSomeFunction, members));
210 
211         // +1 due to sentinel
212         static PyMethodDef[memberFunctions.length + 1] methods;
213 
214         if(methods != methods.init) return &methods[0];
215 
216         static foreach(i, memberFunction; memberFunctions) {{
217 
218             static if(__traits(isStaticFunction, memberFunction))
219                 enum flags = defaultMethodFlags | MethodArgs.Static;
220             else
221                 enum flags = defaultMethodFlags;
222 
223             methods[i] = pyMethodDef!(__traits(identifier, memberFunction), flags)
224                                      (&PythonMethod!(T, memberFunction)._py_method_impl);
225         }}
226 
227         return &methods[0];
228     }
229 
230     import python.raw: Py_ssize_t;
231     private static extern(C) Py_ssize_t _py_length(PyObject* self_) nothrow {
232 
233         return noThrowable!({
234             assert(self_ !is null);
235             static if(hasLength) {
236                 import python.conv: to;
237                 return self_.to!T.length;
238             } else
239                 return -1;
240         });
241     }
242 
243     private static extern(C) PyObject* _py_repr(PyObject* self_) nothrow {
244 
245         return noThrowable!({
246 
247             import python: pyUnicodeDecodeUTF8;
248             import python.conv: to;
249             import std.string: toStringz;
250             import std.conv: text;
251 
252             assert(self_ !is null);
253             auto ret = text(self_.to!T);
254             return pyUnicodeDecodeUTF8(ret.ptr, ret.length, null /*errors*/);
255         });
256     }
257 
258     private static extern(C) int _py_init(PyObject* self_, PyObject* args, PyObject* kwargs) nothrow {
259         // nothing to do
260         return 0;
261     }
262 
263     static if(isUserAggregate!T)
264     private static extern(C) PyObject* _py_new(PyTypeObject *type, PyObject* args, PyObject* kwargs) nothrow {
265         return noThrowable!({
266             import autowrap.reflection: FunctionParameters, Parameter;
267             import python.conv: toPython, to;
268             import python.raw: PyTuple_Size, PyTuple_GetItem;
269             import std.traits: hasMember, Unqual;
270             import std.meta: AliasSeq;
271 
272             const numArgs = PyTuple_Size(args);
273 
274             if(numArgs == 0) {
275                 return toPython(userAggregateInit!T);
276             }
277 
278             static if(hasMember!(T, "__ctor"))
279                 alias constructors = AliasSeq!(__traits(getOverloads, T, "__ctor"));
280             else
281                 alias constructors = AliasSeq!();
282 
283             static if(constructors.length == 0) {
284                 alias parameter(FieldType) = Parameter!(
285                     FieldType,
286                     "",
287                     void,
288                 );
289                 alias parameters = staticMap!(parameter, fieldTypes);
290                 return pythonConstructor!(T, false /*is variadic*/, parameters)(args, kwargs);
291             } else {
292                 import autowrap.reflection: NumRequiredParameters;
293                 import python.raw: PyErr_SetString, PyExc_TypeError;
294                 import std.traits: Parameters, variadicFunctionStyle, Variadic;
295 
296                 static foreach(constructor; constructors) {{
297 
298                     enum isVariadic = variadicFunctionStyle!constructor == Variadic.typesafe;
299 
300                     if(Parameters!constructor.length == numArgs) {
301                         return pythonConstructor!(T, isVariadic, FunctionParameters!constructor)(args, kwargs);
302                     } else if(numArgs >= NumRequiredParameters!constructor
303                               && numArgs <= Parameters!constructor.length)
304                     {
305                         return pythonConstructor!(T, isVariadic, FunctionParameters!constructor)(args, kwargs);
306                     }
307                 }}
308 
309                 PyErr_SetString(PyExc_TypeError, "Could not find a suitable constructor");
310                 return null;
311             }
312 
313         });
314     }
315 
316     // Creates a python object from the given arguments by converting them to D
317     // types, calling the D constructor and converting the result to a Python
318     // object.
319     static if(isUserAggregate!T)
320     private static auto pythonConstructor(T, bool isVariadic, P...)(PyObject* args, PyObject* kwargs) {
321         import python.conv: toPython;
322         import std.traits: hasMember;
323 
324         auto dArgs = pythonArgsToDArgs!(isVariadic, P)(args, kwargs);
325 
326         static if(is(T == class)) {
327             static if(hasMember!(T, "__ctor")) {
328                 // When immutable dmd prints an odd error message about not being
329                 // able to modify dobj
330                 static if(is(T == immutable))
331                     auto dobj = new T(dArgs.expand);
332                 else
333                     scope dobj = new T(dArgs.expand);
334             } else
335                 T dobj;
336         } else
337             auto dobj = T(dArgs.expand);
338 
339         return toPython(dobj);
340     }
341 }
342 
343 
344 // From a D operator (e.g. `+`) to a Python function pointer member name
345 private string dlangUnOpToPythonSlot(string op) {
346     enum opToSlot = [
347         "+": "tp_as_number.nb_positive",
348         "-": "tp_as_number.nb_negative",
349         "~": "tp_as_number.nb_invert",
350     ];
351     if(op !in opToSlot) throw new Exception("Unknown unary operator " ~ op);
352     return opToSlot[op];
353 }
354 
355 // From a D operator (e.g. `+`) to a Python function pointer member name
356 private string dlangBinOpToPythonSlot(string op) {
357     enum opToSlot = [
358         "+": "tp_as_number.nb_add",
359         "-": "tp_as_number.nb_subtract",
360         "*": "tp_as_number.nb_multiply",
361         "/": "tp_as_number.nb_divide",
362         "%": "tp_as_number.nb_remainder",
363         "^^": "tp_as_number.nb_power",
364         "&": "tp_as_number.nb_and",
365         "|": "tp_as_number.nb_or",
366         "^": "tp_as_number.nb_xor",
367         "<<": "tp_as_number.nb_lshift",
368         ">>": "tp_as_number.nb_rshift",
369         "~": "tp_as_sequence.sq_concat",
370         "in": "tp_as_sequence.sq_contains",
371     ];
372     if(op !in opToSlot) throw new Exception("Unknown binary operator " ~ op);
373     return opToSlot[op];
374 }
375 
376 private auto pythonArgsToDArgs(bool isVariadic, P...)(PyObject* args, PyObject* kwargs)
377     if(allSatisfy!(isParameter, P))
378 {
379     import python.raw: PyTuple_Size, PyTuple_GetItem, PyTuple_GetSlice, pyUnicodeDecodeUTF8, PyDict_GetItem;
380     import python.conv: to;
381     import std.typecons: Tuple;
382     import std.meta: staticMap;
383     import std.traits: Unqual;
384     import std.conv: text;
385     import std.exception: enforce;
386 
387     const argsLength = args is null ? 0 : PyTuple_Size(args);
388 
389     alias Type(alias Param) = Param.Type;
390     alias Types = staticMap!(Type, P);
391 
392     // If one or more of the parameters is const/immutable,
393     // it'll be hard to construct it as such, so we Unqual
394     // the types for construction and cast to the appropriate
395     // type when returning.
396     alias MutableTuple = Tuple!(staticMap!(Unqual, Types));
397     alias RetTuple = Tuple!(Types);
398 
399     MutableTuple dArgs;
400 
401     int pythonArgIndex = 0;
402     static foreach(i; 0 .. P.length) {
403 
404         static if(i == P.length - 1 && isVariadic) {  // last parameter and it's a typesafe variadic one
405             // slice the remaining arguments
406             auto remainingArgs = PyTuple_GetSlice(args, i, PyTuple_Size(args));
407             dArgs[i] = remainingArgs.to!(P[i].Type);
408         } else static if(is(P[i].Default == void)) {
409             // ith parameter is required
410             enforce(i < argsLength,
411                     text(__FUNCTION__, ": not enough Python arguments"));
412             dArgs[i] = PyTuple_GetItem(args, i).to!(typeof(dArgs[i]));
413         } else {
414 
415             if(i < argsLength) {  // regular case
416                 dArgs[i] = PyTuple_GetItem(args, i).to!(P[i].Type);
417             } else {
418                 // Here it gets tricky. The user could have supplied it in
419                 // args positionally or via kwargs
420                 auto key = pyUnicodeDecodeUTF8(&P[i].identifier[0],
421                                                P[i].identifier.length,
422                                                null /*errors*/);
423                 enforce(key, "Errors converting '" ~ P[i].identifier ~ "' to Python object");
424                 auto val = kwargs ? PyDict_GetItem(kwargs, key) : null;
425                 dArgs[i] = val
426                     ? val.to!(P[i].Type) // use kwargs
427                     : P[i].Default; // use default value
428             }
429         }
430     }
431 
432     return cast(RetTuple) dArgs;
433 }
434 
435 
436 private alias Type(alias A) = typeof(A);
437 
438 
439 /**
440    The C API implementation of a Python method F of aggregate type T
441  */
442 struct PythonMethod(T, alias F) {
443     static extern(C) PyObject* _py_method_impl(PyObject* self,
444                                                PyObject* args,
445                                                PyObject* kwargs)
446         nothrow
447     {
448         return noThrowable!({
449             import python.raw: pyDecRef;
450             import python.conv: toPython, to;
451             import std.traits: Parameters, FunctionAttribute, functionAttributes, Unqual, hasFunctionAttributes;
452 
453             static if(!__traits(isStaticFunction, F))
454                 assert(self !is null,
455                        "Cannot call PythonMethod!" ~ __traits(identifier, F) ~ " on null self");
456 
457             static if(functionAttributes!F & FunctionAttribute.const_)
458                 alias Aggregate = const T;
459             else static if(functionAttributes!F & FunctionAttribute.immutable_)
460                 alias Aggregate = immutable T;
461             else
462                 alias Aggregate = Unqual!T;
463 
464             auto dAggregate = {
465                 // could be null for static member functions
466                 return self is null ? Aggregate.init : self.to!Aggregate;
467             }();
468 
469             // Not sure how else to take `dAggregate` and `F` and call the member
470             // function other than a mixin
471             mixin(`auto ret = callDlangFunction!((Parameters!F args) => dAggregate.`,
472                   __traits(identifier, F), `(args), F)(self, args, kwargs);`);
473 
474             // The member function could have side-effects, we need to copy the changes
475             // back to the Python object.
476             static if(!(functionAttributes!F & FunctionAttribute.const_)) {
477                 auto newSelf = {
478                     return self is null ? self : toPython(dAggregate);
479                 }();
480                 scope(exit) {
481                     if(self !is null) pyDecRef(newSelf);
482                 }
483                 auto pyClassSelf = cast(PythonClass!T*) self;
484                 auto pyClassNewSelf = cast(PythonClass!T*) newSelf;
485 
486                 static foreach(i; 0 .. PythonClass!T.fieldNames.length) {
487                     if(self !is null)
488                         pyClassSelf.set!i(self, pyClassNewSelf.get!i(newSelf));
489                 }
490             }
491 
492             return ret;
493         });
494     }
495 }
496 
497 
498 /**
499    The C API implementation that calls a D function F.
500  */
501 struct PythonFunction(alias F) {
502     static extern(C) PyObject* _py_function_impl(PyObject* self, PyObject* args, PyObject* kwargs) nothrow {
503         return noThrowable!(() => callDlangFunction!F(self, args, kwargs));
504     }
505 }
506 
507 
508 private auto noThrowable(alias F, A...)(auto ref A args) {
509     import python.raw: PyErr_SetString, PyExc_RuntimeError;
510     import std.string: toStringz;
511     import std.traits: ReturnType;
512 
513     try {
514         return F(args);
515     } catch(Exception e) {
516         PyErr_SetString(PyExc_RuntimeError, e.msg.toStringz);
517         return ReturnType!F.init;
518     } catch(Error e) {
519         import std.conv: text;
520         try
521             PyErr_SetString(PyExc_RuntimeError, ("FATAL ERROR: " ~ e.text).toStringz);
522         catch(Exception _)
523             PyErr_SetString(PyExc_RuntimeError, ("FATAL ERROR: " ~ e.msg).toStringz);
524 
525         return ReturnType!F.init;
526     }
527 }
528 
529 // simple, regular version for functions
530 private auto callDlangFunction(alias F)
531                               (PyObject* self, PyObject* args, PyObject* kwargs)
532 {
533     return callDlangFunction!(F, F)(self, args, kwargs);
534 }
535 
536 // Takes two functions due to how we're calling methods.
537 // One is the original function to reflect on, the other
538 // is the closure that's actually going to be called.
539 private auto callDlangFunction(alias callable, A...)
540                               (PyObject* self, PyObject* args, PyObject* kwargs)
541     if(A.length == 1 && isCallable!(A[0]))
542 {
543     import autowrap.reflection: FunctionParameters, NumDefaultParameters, NumRequiredParameters;
544     import python.raw: PyTuple_Size;
545     import python.conv: toPython;
546     import std.traits: Parameters, variadicFunctionStyle, Variadic;
547     import std.conv: text;
548     import std.string: toStringz;
549     import std.exception: enforce;
550 
551     alias originalFunction = A[0];
552 
553     enum numDefaults = NumDefaultParameters!originalFunction;
554     enum numRequired = NumRequiredParameters!originalFunction;
555     enum isVariadic = variadicFunctionStyle!originalFunction == Variadic.typesafe;
556 
557     static if(__traits(compiles, __traits(identifier, originalFunction)))
558         enum identifier = __traits(identifier, originalFunction);
559     else
560         enum identifier = "anonymous";
561 
562     const numArgs = args is null ? 0 : PyTuple_Size(args);
563     if(!isVariadic)
564         enforce(numArgs >= numRequired
565                 && numArgs <= Parameters!originalFunction.length,
566                 text("Received ", numArgs, " parameters but ",
567                      identifier, " takes ", Parameters!originalFunction.length));
568 
569     auto dArgs = pythonArgsToDArgs!(isVariadic, FunctionParameters!originalFunction)(args, kwargs);
570     return callDlangFunction!callable(dArgs);
571 }
572 
573 
574 private auto callDlangFunction(alias F, A)(auto ref A argTuple) {
575     import python.raw: pyIncRef, pyNone;
576     import python.conv: toPython;
577     import std.traits: ReturnType;
578 
579     // TODO - side-effects on parameters?
580     static if(is(ReturnType!F == void)) {
581         F(argTuple.expand);
582         pyIncRef(pyNone);
583         return pyNone;
584     } else {
585         auto dret = F(argTuple.expand);
586         return dret.toPython;
587     }
588 }
589 
590 
591 /**
592    Creates an instance of a Python class that is equivalent to the D type `T`.
593    Return PyObject*.
594  */
595 PyObject* pythonClass(T)(auto ref T dobj) {
596 
597     import python.conv: toPython;
598     import python.raw: pyObjectNew;
599 
600     static if(is(T == class)) {
601         if(dobj is null)
602             throw new Exception("Cannot create Python class from null D class");
603     }
604 
605     auto ret = pyObjectNew!(PythonClass!T)(PythonType!T.pyType);
606 
607     static foreach(fieldName; PythonType!T.fieldNames) {
608         static if(isPublic!(T, fieldName))
609             mixin(`ret.`, fieldName, ` = dobj.`, fieldName, `.toPython;`);
610     }
611 
612     return cast(PyObject*) ret;
613 }
614 
615 
616 private template isPublic(T, string memberName) {
617 
618     static if(__traits(compiles, __traits(getProtection, __traits(getMember, T, memberName)))) {
619         enum protection = __traits(getProtection, __traits(getMember, T, memberName));
620         enum isPublic = protection == "public" || protection == "export";
621     } else
622         enum isPublic = false;
623 }
624 
625 
626 
627 /**
628    A Python class that mirrors the D type `T`.
629    For instance, this struct:
630    ----------
631    struct Foo {
632        int i;
633        string s;
634    }
635    ----------
636 
637    Will generate a Python class called `Foo` with two members, and trying to
638    assign anything but an integer to `Foo.i` or a string to `Foo.s` in Python
639    will raise `TypeError`.
640  */
641 struct PythonClass(T) if(isUserAggregate!T) {
642     import python.raw: PyObjectHead, PyGetSetDef;
643     import std.traits: Unqual;
644 
645     alias fieldNames = PythonType!(Unqual!T).fieldNames;
646     alias fieldTypes = PythonType!(Unqual!T).fieldTypes;
647 
648     // Every python object must have this
649     mixin PyObjectHead;
650 
651     // Field members
652     // Generate a python object field for every field in T
653     static foreach(fieldName; fieldNames) {
654         mixin(`PyObject* `, fieldName, `;`);
655     }
656 
657     // The function pointer for PyGetSetDef.get
658     private static extern(C) PyObject* get(int FieldIndex)
659                                           (PyObject* self_, void* closure = null)
660         nothrow
661         in(self_ !is null)
662     {
663         import python.raw: pyIncRef;
664 
665         auto self = cast(PythonClass*) self_;
666 
667         auto field = self.getField!FieldIndex;
668         assert(field !is null, "Cannot increase reference count on null field");
669         pyIncRef(field);
670 
671         return field;
672     }
673 
674     // The function pointer for PyGetSetDef.set
675     static extern(C) int set(int FieldIndex)
676                             (PyObject* self_, PyObject* value, void* closure = null)
677         nothrow
678         in(self_ !is null)
679     {
680         import python.raw: pyIncRef, pyDecRef, PyErr_SetString, PyExc_TypeError;
681 
682         if(value is null) {
683             enum deleteErrStr = "Cannot delete " ~ fieldNames[FieldIndex];
684             PyErr_SetString(PyExc_TypeError, deleteErrStr);
685             return -1;
686         }
687 
688         // FIXME
689         // if(!checkPythonType!(fieldTypes[FieldIndex])(value)) {
690         //     return -1;
691         // }
692 
693         auto self = cast(PythonClass!T*) self_;
694         auto tmp = self.getField!FieldIndex;
695 
696         pyIncRef(value);
697         mixin(`self.`, fieldNames[FieldIndex], ` = value;`);
698         pyDecRef(tmp);
699 
700         return 0;
701     }
702 
703     PyObject* getField(int FieldIndex)() {
704         mixin(`return this.`, fieldNames[FieldIndex], `;`);
705     }
706 
707     static extern(C) PyObject* propertyGet(alias F)
708                                           (PyObject* self_, void* closure = null)
709         nothrow
710         in(self_ !is null)
711     {
712         return PythonMethod!(T, F)._py_method_impl(self_, null /*args*/, null /*kwargs*/);
713     }
714 
715     static extern(C) int propertySet(alias F)
716                                     (PyObject* self_, PyObject* value, void* closure = null)
717         nothrow
718         in(self_ !is null)
719     {
720         import python.raw: PyTuple_New, PyTuple_SetItem, pyDecRef;
721 
722         auto args = PyTuple_New(1);
723         PyTuple_SetItem(args, 0, value);
724         scope(exit) pyDecRef(args);
725 
726         PythonMethod!(T, F)._py_method_impl(self_, args, null /*kwargs*/);
727 
728         return 0;
729     }
730 }
731 
732 
733 PyObject* pythonCallable(T)(T callable) {
734     import python.raw: pyObjectNew;
735 
736     auto ret = pyObjectNew!(PythonCallable!T)(PythonType!T.pyType);
737     ret._callable = callable;
738 
739     return cast(PyObject*) ret;
740 }
741 
742 
743 /**
744    Reserves space for a callable to be stored in a PyObject struct so that it
745    can later be called.
746  */
747 private struct PythonCallable(T) if(isCallable!T) {
748     import python.raw: PyObjectHead;
749 
750     // Every python object must have this
751     mixin PyObjectHead;
752 
753     private T _callable;
754 
755     private static extern(C) PyObject* _py_call(PyObject* self_, PyObject* args, PyObject* kwargs)
756         nothrow
757         in(self_ !is null)
758     {
759         import std.traits: Parameters, ReturnType;
760         auto self = cast(PythonCallable!T*) self_;
761         assert(self._callable !is null, "Cannot have null callable");
762         return noThrowable!(() => callDlangFunction!((Parameters!T args) => self._callable(args), T)(self_, args, kwargs));
763     }
764 }
765 
766 
767 private template PythonUnaryOperator(T, string op) {
768     static extern(C) PyObject* _py_un_op(PyObject* self) nothrow {
769         return noThrowable!({
770             import python.conv.python_to_d: to;
771             import python.conv.d_to_python: toPython;
772             import std.traits: Parameters;
773 
774             static assert(Parameters!(T.opUnary!op).length == 0, "opUnary can't take any parameters");
775 
776             return self.to!T.opUnary!op.toPython;
777         });
778     }
779 }
780 
781 
782 private template PythonBinaryOperator(T, BinaryOperator operator) {
783 
784     static extern(C) int _py_in_func(PyObject* lhs, PyObject* rhs)
785         nothrow
786         in(operator.op == "in")
787     {
788         import python.conv.python_to_d: to;
789         import python.conv.d_to_python: toPython;
790         import std.traits: Parameters, hasMember;
791 
792         alias inParams(U) = Parameters!(U.opBinaryRight!(operator.op));
793 
794         static if(__traits(compiles, inParams!T))
795             alias parameters = inParams!T;
796         else
797             alias parameters = void;
798 
799         static if(is(typeof(T.init.opBinaryRight!(operator.op)(parameters.init)): bool)) {
800             return noThrowable!({
801 
802                 static assert(parameters.length == 1, "opBinaryRight!in must have one parameter");
803                 alias Arg = parameters[0];
804 
805                 auto this_ = lhs.to!T;
806                 auto dArg  = rhs.to!Arg;
807 
808                 const ret = this_.opBinaryRight!(operator.op)(dArg);
809                 // See https://docs.python.org/3/c-api/sequence.html#c.PySequence_Contains
810                 return ret ? 1 : 0;
811             });
812         } else {
813             // Error. See https://docs.python.org/3/c-api/sequence.html#c.PySequence_Contains
814             return -1;
815         }
816     }
817 
818     static extern(C) PyObject* _py_bin_func(PyObject* lhs, PyObject* rhs) nothrow {
819         return _py_ter_func(lhs, rhs, null);
820     }
821 
822     // Should only be for `^^` because in Python the function is ternary
823     static extern(C) PyObject* _py_ter_func(PyObject* lhs_, PyObject* rhs_, PyObject* extra) nothrow {
824         import python.conv.python_to_d: to;
825         import python.conv.d_to_python: toPython;
826         import autowrap.reflection: BinOpDir, functionName;
827         import std.traits: Parameters;
828         import std.exception: enforce;
829         import std.conv: text;
830 
831         return noThrowable!({
832 
833             PyObject* self, pArg;
834             if(lhs_.isInstanceOf!T) {
835                 self = lhs_;
836                 pArg = rhs_;
837             } else if(rhs_.isInstanceOf!T) {
838                 self = rhs_;
839                 pArg = lhs_;
840             } else
841                 throw new Exception("Neither lhs or rhs were of type " ~ T.stringof);
842 
843             PyObject* impl(BinOpDir dir)() {
844                 enum funcName = functionName(dir);
845                 static if(operator.dirs & dir) {
846                     mixin(`alias parameters = Parameters!(T.`, funcName, `!(operator.op));`);
847                     static assert(parameters.length == 1, "Binary operators must take one parameter");
848                     alias Arg = parameters[0];
849 
850                     auto this_ = self.to!T;
851                     auto dArg  = pArg.to!Arg;
852                     mixin(`return this_.`, funcName, `!(operator.op)(dArg).toPython;`);
853                 } else {
854                     throw new Exception(text(T.stringof, " does not support ", funcName, " with self on ", dir));
855                 }
856             }
857 
858             if(lhs_.isInstanceOf!T) {  // self is on the left hand side
859                 return impl!(BinOpDir.left);
860             } else if(rhs_.isInstanceOf!T) {  // self is on the right hand side
861                 return impl!(BinOpDir.right);
862             } else {
863                 throw new Exception("Neither lhs or rhs were of type " ~ T.stringof);
864             }
865         });
866     }
867 }
868 
869 
870 private template PythonOpCmp(T) {
871     static extern(C) PyObject* _py_cmp(PyObject* lhs, PyObject* rhs, int opId) nothrow {
872         import python.raw: Py_LT, Py_LE, Py_EQ, Py_NE, Py_GT, Py_GE;
873         import std.conv: text;
874         import std.traits: Unqual, Parameters;
875 
876         return noThrowable!({
877 
878             import python.conv.python_to_d: to;
879             import python.conv.d_to_python: toPython;
880 
881             alias parameters = Parameters!(T.opCmp);
882             static assert(parameters.length == 1, T.stringof ~ ".opCmp must have exactly one parameter");
883 
884             const cmp = lhs.to!(Unqual!T).opCmp(rhs.to!(Unqual!(parameters[0])));
885 
886             const dRes = {
887                 switch(opId) {
888                     default: throw new Exception(text("Unknown opId for opCmp: ", opId));
889                     case Py_LT: return cmp < 0;
890                     case Py_LE: return cmp <= 0;
891                     case Py_EQ: return cmp == 0;
892                     case Py_NE: return cmp !=0;
893                     case Py_GT: return cmp > 0;
894                     case Py_GE: return cmp >=0;
895                 }
896             }();
897 
898             return dRes.toPython;
899        });
900     }
901 }
902 
903 
904 private bool isInstanceOf(T)(PyObject* obj) {
905     import python.raw: PyObject_IsInstance;
906     return cast(bool) PyObject_IsInstance(obj, cast(PyObject*) PythonType!T.pyType);
907 }
908 
909 
910 private bool checkPythonType(T)(PyObject* value) if(isArray!T) {
911     import python.raw: pyListCheck;
912     const ret = pyListCheck(value);
913     if(!ret) setPyErrTypeString!"list";
914     return ret;
915 }
916 
917 
918 private bool checkPythonType(T)(PyObject* value) if(isIntegral!T) {
919     import python.raw: pyIntCheck, pyLongCheck;
920     const ret = pyLongCheck(value) || pyIntCheck(value);
921     if(!ret) setPyErrTypeString!"long";
922     return ret;
923 }
924 
925 
926 private bool checkPythonType(T)(PyObject* value) if(isFloatingPoint!T) {
927     import python.raw: pyFloatCheck;
928     const ret = pyFloatCheck(value);
929     if(!ret) setPyErrTypeString!"float";
930     return ret;
931 }
932 
933 
934 private bool checkPythonType(T)(PyObject* value) if(is(T == DateTime)) {
935     import python.raw: pyDateTimeCheck;
936     const ret = pyDateTimeCheck(value);
937     if(!ret) setPyErrTypeString!"DateTime";
938     return ret;
939 }
940 
941 
942 private bool checkPythonType(T)(PyObject* value) if(is(T == Date)) {
943     import python.raw: pyDateCheck;
944     const ret = pyDateCheck(value);
945     if(!ret) setPyErrTypeString!"Date";
946     return ret;
947 }
948 
949 
950 private void setPyErrTypeString(string type)() @trusted @nogc nothrow {
951     import python.raw: PyErr_SetString, PyExc_TypeError;
952     enum str = "must be a " ~ type;
953     PyErr_SetString(PyExc_TypeError, &str[0]);
954 }
955 
956 // Generalises T.init for classes since null isn't a value we want to use
957 T userAggregateInit(T)() {
958     static if(is(T == class)) {
959         auto buffer = new void[__traits(classInstanceSize, T)];
960         // this is needed for the vtable to work
961         buffer[] = typeid(T).initializer[];
962         return cast(T) buffer.ptr;
963     } else
964         return T.init;
965 }