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 }