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