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 }