1 /**
2    A D API for dealing with Python's PyTypeObject
3  */
4 module python.type;
5 
6 
7 import autowrap.reflection: isParameter;
8 import python.raw: PyObject;
9 import std.traits: Unqual, isArray, isIntegral, isBoolean, isFloatingPoint, isAggregateType;
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() {
48         initialise;
49         return failedToReady ? null : &_pyType;
50     }
51 
52     private static void initialise() {
53         import python.raw: PyType_GenericNew, PyType_Ready, TypeFlags,
54             PyErr_SetString, PyExc_TypeError;
55 
56         if(_pyType != _pyType.init) return;
57 
58         _pyType.tp_name = T.stringof;
59         _pyType.tp_basicsize = PythonClass!T.sizeof;
60         _pyType.tp_flags = TypeFlags.Default;
61         _pyType.tp_new = &PyType_GenericNew;
62         _pyType.tp_getset = getsetDefs;
63         _pyType.tp_methods = methodDefs;
64         _pyType.tp_repr = &_py_repr;
65         _pyType.tp_init = &_py_init;
66         _pyType.tp_new = &_py_new;
67 
68         static if(hasLength) {
69             if(_pyType.tp_as_sequence is null)
70                 _pyType.tp_as_sequence = new PySequenceMethods;
71             _pyType.tp_as_sequence.sq_length = &_py_length;
72         }
73 
74         if(PyType_Ready(&_pyType) < 0) {
75             PyErr_SetString(PyExc_TypeError, &"not ready"[0]);
76             failedToReady = true;
77         }
78     }
79 
80     private static auto getsetDefs() {
81         import autowrap.reflection: Properties;
82         import python.raw: PyGetSetDef;
83         import std.meta: staticMap, Filter, Alias;
84         import std.traits: isFunction, ReturnType;
85 
86         alias AggMember(string memberName) = Alias!(__traits(getMember, T, memberName));
87         alias memberNames = __traits(allMembers, T);
88         enum isPublic(string memberName) =
89             __traits(getProtection, __traits(getMember, T, memberName)) == "public";
90         alias publicMemberNames = Filter!(isPublic, memberNames);
91         alias members = staticMap!(AggMember, publicMemberNames);
92         alias memberFunctions = Filter!(isFunction, members);
93         alias properties = Properties!memberFunctions;
94 
95         // +1 due to the sentinel
96         static PyGetSetDef[fieldNames.length + properties.length + 1] getsets;
97 
98         // don't bother if already initialised
99         if(getsets != getsets.init) return &getsets[0];
100 
101         // first deal with the public fields
102         static foreach(i; 0 .. fieldNames.length) {
103             getsets[i].name = cast(typeof(PyGetSetDef.name)) fieldNames[i];
104             static if(__traits(getProtection, __traits(getMember, T, fieldNames[i])) == "public") {
105                 getsets[i].get = &PythonClass!T.get!i;
106                 getsets[i].set = &PythonClass!T.set!i;
107             }
108         }
109 
110         // then deal with the property functions
111         static foreach(j, property; properties) {{
112             enum i = fieldNames.length + j;
113 
114             getsets[i].name = cast(typeof(PyGetSetDef.name)) __traits(identifier, property);
115 
116             static foreach(overload; __traits(getOverloads, T, __traits(identifier, property))) {
117                 static if(is(ReturnType!overload == void))
118                     getsets[i].set = &PythonClass!T.propertySet!(overload);
119                 else
120                     getsets[i].get = &PythonClass!T.propertyGet!(overload);
121             }
122         }}
123 
124         return &getsets[0];
125     }
126 
127     private static auto methodDefs()() {
128         import autowrap.reflection: isProperty;
129         import python.raw: PyMethodDef, MethodArgs;
130         import python.cooked: pyMethodDef, defaultMethodFlags;
131         import std.meta: AliasSeq, Alias, staticMap, Filter, templateNot;
132         import std.traits: isSomeFunction;
133         import std.algorithm: startsWith;
134 
135         alias memberNames = AliasSeq!(__traits(allMembers, T));
136         enum ispublic(string name) = isPublic!(T, name);
137         alias publicMemberNames = Filter!(ispublic, memberNames);
138         enum isRegular(string name) =
139             name != "this"
140             && name != "toHash"
141             && name != "factory"
142             && !name.startsWith("op")
143             && name != "__ctor"
144             ;
145         alias regularMemberNames = Filter!(isRegular, publicMemberNames);
146         alias Member(string name) = Alias!(__traits(getMember, T, name));
147         alias members = staticMap!(Member, regularMemberNames);
148         alias memberFunctions = Filter!(templateNot!isProperty, Filter!(isSomeFunction, members));
149 
150         // +1 due to sentinel
151         static PyMethodDef[memberFunctions.length + 1] methods;
152 
153         if(methods != methods.init) return &methods[0];
154 
155         static foreach(i, memberFunction; memberFunctions) {{
156 
157             static if(__traits(isStaticFunction, memberFunction))
158                 enum flags = defaultMethodFlags | MethodArgs.Static;
159             else
160                 enum flags = defaultMethodFlags;
161 
162             methods[i] = pyMethodDef!(__traits(identifier, memberFunction), flags)
163                                      (&PythonMethod!(T, memberFunction)._py_method_impl);
164         }}
165 
166         return &methods[0];
167     }
168 
169     import python.raw: Py_ssize_t;
170     private static extern(C) Py_ssize_t _py_length(PyObject* self_) nothrow {
171 
172         return noThrowable!({
173             assert(self_ !is null);
174             static if(hasLength) {
175                 import python.conv: to;
176                 return self_.to!T.length;
177             } else
178                 return -1;
179         });
180     }
181 
182     private static extern(C) PyObject* _py_repr(PyObject* self_) nothrow {
183 
184         return noThrowable!({
185 
186             import python: pyUnicodeDecodeUTF8;
187             import python.conv: to;
188             import std.string: toStringz;
189             import std.conv: text;
190 
191             assert(self_ !is null);
192             auto ret = text(self_.to!T);
193             return pyUnicodeDecodeUTF8(ret.ptr, ret.length, null /*errors*/);
194         });
195     }
196 
197     private static extern(C) int _py_init(PyObject* self_, PyObject* args, PyObject* kwargs) nothrow {
198         // nothing to do
199         return 0;
200     }
201 
202     private static extern(C) PyObject* _py_new(PyTypeObject *type, PyObject* args, PyObject* kwargs) nothrow {
203         return noThrowable!({
204             import autowrap.reflection: FunctionParameters, Parameter;
205             import python.conv: toPython, to;
206             import python.raw: PyTuple_Size, PyTuple_GetItem;
207             import std.traits: hasMember, Unqual;
208             import std.meta: AliasSeq;
209 
210             const numArgs = PyTuple_Size(args);
211 
212             if(numArgs == 0) {
213                 return toPython(userAggregateInit!T);
214             }
215 
216             static if(hasMember!(T, "__ctor"))
217                 alias constructors = AliasSeq!(__traits(getOverloads, T, "__ctor"));
218             else
219                 alias constructors = AliasSeq!();
220 
221             static if(constructors.length == 0) {
222                 alias parameter(FieldType) = Parameter!(
223                     FieldType,
224                     "",
225                     void,
226                 );
227                 alias parameters = staticMap!(parameter, fieldTypes);
228                 return pythonConstructor!(T, false /*is variadic*/, parameters)(args, kwargs);
229             } else {
230                 import python.raw: PyErr_SetString, PyExc_TypeError;
231                 import std.traits: Parameters, variadicFunctionStyle, Variadic;
232 
233                 static foreach(constructor; constructors) {{
234 
235                     enum isVariadic = variadicFunctionStyle!constructor == Variadic.typesafe;
236 
237                     if(Parameters!constructor.length == numArgs) {
238                         return pythonConstructor!(T, isVariadic, FunctionParameters!constructor)(args, kwargs);
239                     } else if(numArgs >= NumRequiredParameters!constructor
240                               && numArgs <= Parameters!constructor.length)
241                     {
242                         return pythonConstructor!(T, isVariadic, FunctionParameters!constructor)(args, kwargs);
243                     }
244                 }}
245 
246                 PyErr_SetString(PyExc_TypeError, "Could not find a suitable constructor");
247                 return null;
248             }
249 
250         });
251     }
252 
253     // Creates a python object from the given arguments by converting them to D
254     // types, calling the D constructor and converting the result to a Python
255     // object.
256     private static auto pythonConstructor(T, bool isVariadic, P...)(PyObject* args, PyObject* kwargs) {
257         import python.conv: toPython;
258         import std.traits: hasMember;
259 
260         auto dArgs = pythonArgsToDArgs!(isVariadic, P)(args, kwargs);
261 
262         static if(is(T == class)) {
263             static if(hasMember!(T, "__ctor")) {
264                 // When immutable dmd prints an odd error message about not being
265                 // able to modify dobj
266                 static if(is(T == immutable))
267                     auto dobj = new T(dArgs.expand);
268                 else
269                     scope dobj = new T(dArgs.expand);
270             } else
271                 T dobj;
272         } else
273             auto dobj = T(dArgs.expand);
274 
275         return toPython(dobj);
276     }
277 }
278 
279 
280 private auto pythonArgsToDArgs(bool isVariadic, P...)(PyObject* args, PyObject* kwargs)
281     if(allSatisfy!(isParameter, P))
282 {
283     import python.raw: PyTuple_Size, PyTuple_GetItem, PyTuple_GetSlice, pyUnicodeDecodeUTF8, PyDict_GetItem;
284     import python.conv: to;
285     import std.typecons: Tuple;
286     import std.meta: staticMap;
287     import std.traits: Unqual;
288     import std.conv: text;
289     import std.exception: enforce;
290 
291     const argsLength = args is null ? 0 : PyTuple_Size(args);
292 
293     alias Type(alias Param) = Param.Type;
294     alias Types = staticMap!(Type, P);
295 
296     // If one or more of the parameters is const/immutable,
297     // it'll be hard to construct it as such, so we Unqual
298     // the types for construction and cast to the appropriate
299     // type when returning.
300     alias MutableTuple = Tuple!(staticMap!(Unqual, Types));
301     alias RetTuple = Tuple!(Types);
302 
303     MutableTuple dArgs;
304 
305     int pythonArgIndex = 0;
306     static foreach(i; 0 .. P.length) {
307 
308         static if(i == P.length - 1 && isVariadic) {  // last parameter and it's a typesafe variadic one
309             // slice the remaining arguments
310             auto remainingArgs = PyTuple_GetSlice(args, i, PyTuple_Size(args));
311             dArgs[i] = remainingArgs.to!(P[i].Type);
312         } else static if(is(P[i].Default == void)) {
313             // ith parameter is required
314             enforce(i < argsLength,
315                     text(__FUNCTION__, ": not enough Python arguments"));
316             dArgs[i] = PyTuple_GetItem(args, i).to!(typeof(dArgs[i]));
317         } else {
318 
319             if(i < argsLength) {  // regular case
320                 dArgs[i] = PyTuple_GetItem(args, i).to!(P[i].Type);
321             } else {
322                 // Here it gets tricky. The user could have supplied it in
323                 // args positionally or via kwargs
324                 auto key = pyUnicodeDecodeUTF8(&P[i].identifier[0],
325                                                P[i].identifier.length,
326                                                null /*errors*/);
327                 enforce(key, "Errors converting '" ~ P[i].identifier ~ "' to Python object");
328                 auto val = kwargs ? PyDict_GetItem(kwargs, key) : null;
329                 dArgs[i] = val
330                     ? val.to!(P[i].Type) // use kwargs
331                     : P[i].Default; // use default value
332             }
333         }
334     }
335 
336     return cast(RetTuple) dArgs;
337 }
338 
339 
340 private alias Type(alias A) = typeof(A);
341 
342 
343 /**
344    The C API implementation of a Python method F of aggregate type T
345  */
346 struct PythonMethod(T, alias F) {
347     static extern(C) PyObject* _py_method_impl(PyObject* self,
348                                                PyObject* args,
349                                                PyObject* kwargs)
350         nothrow
351     {
352         return noThrowable!({
353             import python.raw: pyDecRef;
354             import python.conv: toPython, to;
355             import std.traits: Parameters, FunctionAttribute, functionAttributes, Unqual, hasFunctionAttributes;
356 
357             static if(!__traits(isStaticFunction, F))
358                 assert(self !is null,
359                        "Cannot call PythonMethod!" ~ __traits(identifier, F) ~ " on null self");
360 
361             static if(functionAttributes!F & FunctionAttribute.const_)
362                 alias Aggregate = const T;
363             else static if(functionAttributes!F & FunctionAttribute.immutable_)
364                 alias Aggregate = immutable T;
365             else
366                 alias Aggregate = Unqual!T;
367 
368             auto dAggregate = {
369                 // could be null for static member functions
370                 return self is null ? Aggregate.init : self.to!Aggregate;
371             }();
372 
373             // Not sure how else to take `dAggregate` and `F` and call the member
374             // function other than a mixin
375             mixin(`auto ret = callDlangFunction!((Parameters!F args) => dAggregate.`,
376                   __traits(identifier, F), `(args), F)(self, args, kwargs);`);
377 
378             // The member function could have side-effects, we need to copy the changes
379             // back to the Python object.
380             static if(!(functionAttributes!F & FunctionAttribute.const_)) {
381                 auto newSelf = {
382                     return self is null ? self : toPython(dAggregate);
383                 }();
384                 scope(exit) {
385                     if(self !is null) pyDecRef(newSelf);
386                 }
387                 auto pyClassSelf = cast(PythonClass!T*) self;
388                 auto pyClassNewSelf = cast(PythonClass!T*) newSelf;
389 
390                 static foreach(i; 0 .. PythonClass!T.fieldNames.length) {
391                     if(self !is null)
392                         pyClassSelf.set!i(self, pyClassNewSelf.get!i(newSelf));
393                 }
394             }
395 
396             return ret;
397         });
398     }
399 }
400 
401 
402 /**
403    The C API implementation that calls a D function F.
404  */
405 struct PythonFunction(alias F) {
406     static extern(C) PyObject* _py_function_impl(PyObject* self, PyObject* args, PyObject* kwargs) nothrow {
407         return noThrowable!(() => callDlangFunction!F(self, args, kwargs));
408     }
409 }
410 
411 
412 private auto noThrowable(alias F, A...)(auto ref A args) {
413     import python.raw: PyErr_SetString, PyExc_RuntimeError;
414     import std.string: toStringz;
415     import std.traits: ReturnType;
416 
417     try {
418         return F(args);
419     } catch(Exception e) {
420         PyErr_SetString(PyExc_RuntimeError, e.msg.toStringz);
421         return ReturnType!F.init;
422     } catch(Error e) {
423         import std.conv: text;
424         try
425             PyErr_SetString(PyExc_RuntimeError, ("FATAL ERROR: " ~ e.text).toStringz);
426         catch(Exception _)
427             PyErr_SetString(PyExc_RuntimeError, ("FATAL ERROR: " ~ e.msg).toStringz);
428 
429         return ReturnType!F.init;
430     }
431 }
432 
433 // simple, regular version for functions
434 private auto callDlangFunction(alias F)
435                               (PyObject* self, PyObject* args, PyObject* kwargs)
436 {
437     return callDlangFunction!(F, F)(self, args, kwargs);
438 }
439 
440 // Takes two functions due to how we're calling methods.
441 // One is the original function to reflect on, the other
442 // is the closure that's actually going to be called.
443 private auto callDlangFunction(alias callable, alias originalFunction)
444                               (PyObject* self, PyObject* args, PyObject* kwargs)
445 {
446     import autowrap.reflection: FunctionParameters;
447     import python.raw: PyTuple_Size;
448     import python.conv: toPython;
449     import std.traits: Parameters, variadicFunctionStyle, Variadic;
450     import std.conv: text;
451     import std.string: toStringz;
452     import std.exception: enforce;
453 
454     enum numDefaults = NumDefaultParameters!originalFunction;
455     enum numRequired = NumRequiredParameters!originalFunction;
456     enum isVariadic = variadicFunctionStyle!originalFunction == Variadic.typesafe;
457 
458     const numArgs = args is null ? 0 : PyTuple_Size(args);
459     if(!isVariadic)
460         enforce(numArgs >= numRequired
461                 && numArgs <= Parameters!originalFunction.length,
462                 text("Received ", numArgs, " parameters but ",
463                      __traits(identifier, originalFunction), " takes ", Parameters!originalFunction.length));
464 
465     auto dArgs = pythonArgsToDArgs!(isVariadic, FunctionParameters!originalFunction)(args, kwargs);
466     return callDlangFunction!callable(dArgs);
467 }
468 
469 
470 private auto callDlangFunction(alias F, A)(auto ref A argTuple) {
471     import python.raw: pyIncRef, pyNone;
472     import python.conv: toPython;
473     import std.traits: ReturnType;
474 
475     // TODO - side-effects on parameters?
476     static if(is(ReturnType!F == void)) {
477         F(argTuple.expand);
478         pyIncRef(pyNone);
479         return pyNone;
480     } else {
481         auto dret = F(argTuple.expand);
482         return dret.toPython;
483     }
484 }
485 
486 
487 private template NumDefaultParameters(alias F) {
488     import std.meta: Filter;
489     import std.traits: ParameterDefaults;
490 
491     template notVoid(T...) if(T.length == 1) {
492         enum notVoid = !is(T[0] == void);
493     }
494 
495     enum NumDefaultParameters = Filter!(notVoid, ParameterDefaults!F).length;
496 }
497 
498 
499 private template NumRequiredParameters(alias F) {
500     import std.traits: Parameters;
501     enum NumRequiredParameters = Parameters!F.length - NumDefaultParameters!F;
502 }
503 
504 /**
505    Creates an instance of a Python class that is equivalent to the D type `T`.
506    Return PyObject*.
507  */
508 PyObject* pythonClass(T)(auto ref T dobj) {
509 
510     import python.conv: toPython;
511     import python.raw: pyObjectNew;
512 
513     static if(is(T == class)) {
514         if(dobj is null)
515             throw new Exception("Cannot create Python class from null D class");
516     }
517 
518     auto ret = pyObjectNew!(PythonClass!T)(PythonType!T.pyType);
519 
520     static foreach(fieldName; PythonType!T.fieldNames) {
521         static if(isPublic!(T, fieldName))
522             mixin(`ret.`, fieldName, ` = dobj.`, fieldName, `.toPython;`);
523     }
524 
525     return cast(PyObject*) ret;
526 }
527 
528 
529 private template isPublic(T, string memberName) {
530 
531     static if(__traits(compiles, __traits(getProtection, __traits(getMember, T, memberName)))) {
532         enum protection = __traits(getProtection, __traits(getMember, T, memberName));
533         enum isPublic = protection == "public" || protection == "export";
534     } else
535         enum isPublic = false;
536 }
537 
538 
539 /**
540    A Python class that mirrors the D type `T`.
541    For instance, this struct:
542    ----------
543    struct Foo {
544        int i;
545        string s;
546    }
547    ----------
548 
549    Will generate a Python class called `Foo` with two members, and trying to
550    assign anything but an integer to `Foo.i` or a string to `Foo.s` in Python
551    will raise `TypeError`.
552  */
553 struct PythonClass(T) if(isUserAggregate!T) {
554     import python.raw: PyObjectHead, PyGetSetDef;
555     import std.traits: Unqual;
556 
557     alias fieldNames = PythonType!(Unqual!T).fieldNames;
558     alias fieldTypes = PythonType!(Unqual!T).fieldTypes;
559 
560     // Every python object must have this
561     mixin PyObjectHead;
562 
563     // Field members
564     // Generate a python object field for every field in T
565     static foreach(fieldName; fieldNames) {
566         mixin(`PyObject* `, fieldName, `;`);
567     }
568 
569     // The function pointer for PyGetSetDef.get
570     private static extern(C) PyObject* get(int FieldIndex)
571                                           (PyObject* self_, void* closure = null)
572         nothrow
573         in(self_ !is null)
574     {
575         import python.raw: pyIncRef;
576 
577         auto self = cast(PythonClass*) self_;
578 
579         auto field = self.getField!FieldIndex;
580         assert(field !is null, "Cannot increase reference count on null field");
581         pyIncRef(field);
582 
583         return field;
584     }
585 
586     // The function pointer for PyGetSetDef.set
587     static extern(C) int set(int FieldIndex)
588                             (PyObject* self_, PyObject* value, void* closure = null)
589         nothrow
590         in(self_ !is null)
591     {
592         import python.raw: pyIncRef, pyDecRef, PyErr_SetString, PyExc_TypeError;
593 
594         if(value is null) {
595             enum deleteErrStr = "Cannot delete " ~ fieldNames[FieldIndex];
596             PyErr_SetString(PyExc_TypeError, deleteErrStr);
597             return -1;
598         }
599 
600         // FIXME
601         // if(!checkPythonType!(fieldTypes[FieldIndex])(value)) {
602         //     return -1;
603         // }
604 
605         auto self = cast(PythonClass!T*) self_;
606         auto tmp = self.getField!FieldIndex;
607 
608         pyIncRef(value);
609         mixin(`self.`, fieldNames[FieldIndex], ` = value;`);
610         pyDecRef(tmp);
611 
612         return 0;
613     }
614 
615     PyObject* getField(int FieldIndex)() {
616         mixin(`return this.`, fieldNames[FieldIndex], `;`);
617     }
618 
619     static extern(C) PyObject* propertyGet(alias F)
620                                           (PyObject* self_, void* closure = null)
621         nothrow
622         in(self_ !is null)
623     {
624         return PythonMethod!(T, F)._py_method_impl(self_, null /*args*/, null /*kwargs*/);
625     }
626 
627     static extern(C) int propertySet(alias F)
628                                     (PyObject* self_, PyObject* value, void* closure = null)
629         nothrow
630         in(self_ !is null)
631     {
632         import python.raw: PyTuple_New, PyTuple_SetItem, pyDecRef;
633 
634         auto args = PyTuple_New(1);
635         PyTuple_SetItem(args, 0, value);
636         scope(exit) pyDecRef(args);
637 
638         PythonMethod!(T, F)._py_method_impl(self_, args, null /*kwargs*/);
639 
640         return 0;
641     }
642 }
643 
644 
645 private bool checkPythonType(T)(PyObject* value) if(isArray!T) {
646     import python.raw: pyListCheck;
647     const ret = pyListCheck(value);
648     if(!ret) setPyErrTypeString!"list";
649     return ret;
650 }
651 
652 
653 private bool checkPythonType(T)(PyObject* value) if(isIntegral!T) {
654     import python.raw: pyIntCheck, pyLongCheck;
655     const ret = pyLongCheck(value) || pyIntCheck(value);
656     if(!ret) setPyErrTypeString!"long";
657     return ret;
658 }
659 
660 
661 private bool checkPythonType(T)(PyObject* value) if(isFloatingPoint!T) {
662     import python.raw: pyFloatCheck;
663     const ret = pyFloatCheck(value);
664     if(!ret) setPyErrTypeString!"float";
665     return ret;
666 }
667 
668 
669 private bool checkPythonType(T)(PyObject* value) if(is(T == DateTime)) {
670     import python.raw: pyDateTimeCheck;
671     const ret = pyDateTimeCheck(value);
672     if(!ret) setPyErrTypeString!"DateTime";
673     return ret;
674 }
675 
676 
677 private bool checkPythonType(T)(PyObject* value) if(is(T == Date)) {
678     import python.raw: pyDateCheck;
679     const ret = pyDateCheck(value);
680     if(!ret) setPyErrTypeString!"Date";
681     return ret;
682 }
683 
684 
685 private void setPyErrTypeString(string type)() @trusted @nogc nothrow {
686     import python.raw: PyErr_SetString, PyExc_TypeError;
687     enum str = "must be a " ~ type;
688     PyErr_SetString(PyExc_TypeError, &str[0]);
689 }
690 
691 // Generalises T.init for classes since null isn't a value we want to use
692 T userAggregateInit(T)() {
693     static if(is(T == class)) {
694         auto buffer = new void[__traits(classInstanceSize, T)];
695         // this is needed for the vtable to work
696         buffer[] = typeid(T).initializer[];
697         return cast(T) buffer.ptr;
698     } else
699         return T.init;
700 }