1 /** 2 A D API for dealing with Python's PyTypeObject 3 */ 4 module python.type; 5 6 7 import python.raw: PyObject; 8 import mirror.traits: isParameter, BinaryOperator; 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 static import core.time; 16 17 18 package enum isPhobos(T) = isDateOrDateTime!T || isTuple!T || is(Unqual!T == core.time.Duration); 19 package enum isDateOrDateTime(T) = is(Unqual!T == DateTime) || is(Unqual!T == Date); 20 package enum isTuple(T) = is(T: Tuple!A, A...); 21 package enum isUserAggregate(T) = isAggregateType!T && !isPhobos!(T); 22 package enum isNonRangeUDT(T) = isUserAggregate!T && !isInputRange!T; 23 24 25 /** 26 A wrapper for `PyTypeObject`. 27 28 This struct does all of the necessary boilerplate to intialise 29 a `PyTypeObject` for a Python extension type that mimics the D 30 type `T`. 31 */ 32 struct PythonType(T) { 33 import python.raw: PyTypeObject, PySequenceMethods, PyMappingMethods; 34 import std.traits: FieldNameTuple, Fields, Unqual, fullyQualifiedName, BaseClassesTuple; 35 import std.meta: Alias, AliasSeq, staticMap; 36 37 static if(is(T == struct)) { 38 alias fieldNames = FieldNameTuple!T; 39 alias fieldTypes = Fields!T; 40 } else static if(is(T == class)) { 41 // recurse over base classes to get all fields 42 alias fieldNames = AliasSeq!(FieldNameTuple!T, staticMap!(FieldNameTuple, BaseClassesTuple!T)); 43 private alias fieldType(string name) = typeof(__traits(getMember, T, name)); 44 alias fieldTypes = staticMap!(fieldType, fieldNames); 45 } else static if(is(T == interface)) { 46 alias fieldNames = AliasSeq!(); 47 alias fieldTypes = AliasSeq!(); 48 } 49 50 enum hasLength = is(typeof({ size_t len = T.init.length; })); 51 52 static PyTypeObject _pyType; 53 static bool failedToReady; 54 55 static PyObject* pyObject() { 56 initialise; 57 return failedToReady ? null : cast(PyObject*) &_pyType; 58 } 59 60 static PyTypeObject* pyType() nothrow { 61 initialise; 62 return failedToReady ? null : &_pyType; 63 } 64 65 private static void initialise() nothrow { 66 import python.raw: PyType_GenericNew, PyType_Ready, TypeFlags, 67 PyErr_SetString, PyExc_TypeError, 68 PyNumberMethods, PySequenceMethods; 69 import mirror.traits: UnaryOperators, BinaryOperators, AssignOperators, functionName; 70 import std.traits: arity, hasMember, TemplateOf; 71 import std.meta: Filter; 72 static import std.typecons; 73 74 if(_pyType != _pyType.init) return; 75 76 // This allows tp_name to do its usual Python job and allos us to 77 // construct a D class from its runtime Python type. 78 _pyType.tp_name = fullyQualifiedName!(Unqual!T).ptr; 79 _pyType.tp_flags = TypeFlags.Default; 80 static if(is(T == class) || is(T == interface)) 81 _pyType.tp_flags |= TypeFlags.BaseType; 82 83 // FIXME: types are that user aggregates *and* callables 84 static if(isUserAggregate!T) { 85 _pyType.tp_basicsize = PythonClass!T.sizeof; 86 87 static if(__traits(compiles, getsetDefs())) 88 _pyType.tp_getset = getsetDefs; 89 else 90 pragma(msg, "WARNING: could not generate attribute accessors for ", fullyQualifiedName!T); 91 92 _pyType.tp_methods = methodDefs; 93 static if(!isAbstract!T) 94 _pyType.tp_new = &_py_new; 95 _pyType.tp_repr = &_py_repr; 96 _pyType.tp_init = &_py_init; 97 98 // special-case std.typecons.Typedef 99 // see: https://issues.dlang.org/show_bug.cgi?id=20117 100 static isSamePtr(void* lhs, void* rhs) { 101 return lhs is rhs; 102 } 103 104 static if(__traits(compiles, isSamePtr(&T.opCmp, &Object.opCmp))) { 105 static if( 106 hasMember!(T, "opCmp") 107 && !__traits(isSame, TemplateOf!T, std.typecons.Typedef) 108 && !isSamePtr(&T.opCmp, &Object.opCmp) 109 ) 110 { 111 _pyType.tp_richcompare = &PythonOpCmp!T._py_cmp; 112 } 113 } 114 115 static if(hasMember!(T, "opSlice")) { 116 static if(__traits(compiles, &PythonIter!T._py_iter)) 117 _pyType.tp_iter = &PythonIter!T._py_iter; 118 else 119 pragma(msg, "WARNING: could not implement Python opSlice for ", fullyQualifiedName!T); 120 } 121 122 // In Python, both D's opIndex and opSlice are dealt with by one function, 123 // in opSlice's case when the type is indexed by a Python slice 124 static if(hasMember!(T, "opIndex") || hasMember!(T, "opSlice")) { 125 if(_pyType.tp_as_mapping is null) 126 _pyType.tp_as_mapping = new PyMappingMethods; 127 _pyType.tp_as_mapping.mp_subscript = &PythonSubscript!T._py_index; 128 } 129 130 static if(hasMember!(T, "opIndexAssign")) { 131 if(_pyType.tp_as_mapping is null) 132 _pyType.tp_as_mapping = new PyMappingMethods; 133 134 static if(__traits(compiles, &PythonIndexAssign!T._py_index_assign)) 135 _pyType.tp_as_mapping.mp_ass_subscript = &PythonIndexAssign!T._py_index_assign; 136 else 137 pragma(msg, "WARNING: could not implement Python index assign for ", 138 fullyQualifiedName!T); 139 } 140 141 enum isPythonableUnary(string op) = op == "+" || op == "-" || op == "~"; 142 enum unaryOperators = Filter!(isPythonableUnary, UnaryOperators!T); 143 alias binaryOperators = BinaryOperators!T; 144 alias assignOperators = AssignOperators!T; 145 146 static if(unaryOperators.length > 0 || binaryOperators.length > 0 || assignOperators.length > 0) { 147 _pyType.tp_as_number = new PyNumberMethods; 148 _pyType.tp_as_sequence = new PySequenceMethods; 149 } 150 151 static foreach(op; unaryOperators) { 152 mixin(`_pyType.`, dlangUnOpToPythonSlot(op), ` = &PythonUnaryOperator!(T, op)._py_un_op;`); 153 } 154 155 static foreach(binOp; binaryOperators) {{ 156 // first get the Python function pointer name 157 enum slot = dlangBinOpToPythonSlot(binOp.op); 158 // some of them differ in arity 159 enum slotArity = arity!(mixin(`typeof(PyTypeObject.`, slot, `)`)); 160 161 // get the function name in PythonBinaryOperator 162 // `in` is special because the function signature is different 163 static if(binOp.op == "in") { 164 enum cFuncName = "_py_in_func"; 165 } else { 166 static if(slotArity == 2) 167 enum cFuncName = "_py_bin_func"; 168 else static if(slotArity == 3) 169 enum cFuncName = "_py_ter_func"; 170 else 171 static assert("Do not know how to deal with slot " ~ slot); 172 } 173 174 // set the C function that implements this operator 175 mixin(`_pyType.`, slot, ` = &PythonBinaryOperator!(T, binOp).`, cFuncName, `;`); 176 }} 177 178 static foreach(assignOp; assignOperators) {{ 179 enum slot = dlangAssignOpToPythonSlot(assignOp); 180 // some of them differ in arity 181 enum slotArity = arity!(mixin(`typeof(PyTypeObject.`, slot, `)`)); 182 183 // get the function name in PythonAssignOperator 184 static if(slotArity == 2) 185 enum cFuncName = "_py_bin_func"; 186 else static if(slotArity == 3) 187 enum cFuncName = "_py_ter_func"; 188 else 189 static assert("Do not know how to deal with slot " ~ slot); 190 191 // set the C function that implements this operator 192 mixin(`_pyType.`, slot, ` = &PythonAssignOperator!(T, assignOp).`, cFuncName, `;`); 193 }} 194 195 static if(isCallable!T) { 196 _pyType.tp_call = &PythonCallable!T._py_call; 197 } 198 199 // inheritance 200 static if(is(T Bases == super)) { 201 enum isSuperClass(U) = is(U == class) && !is(U == Object); 202 alias supers = Filter!(isSuperClass, Bases); 203 static if(supers.length == 1) { 204 _pyType.tp_base = PythonType!(supers[0]).pyType; 205 } 206 } 207 208 } else static if(isCallable!T) { 209 _pyType.tp_basicsize = PythonCallable!T.sizeof; 210 _pyType.tp_call = &PythonCallable!T._py_call; 211 } else static if(is(T == enum)) { 212 import python.raw: PyEnum_Type; 213 _pyType.tp_basicsize = 0; 214 _pyType.tp_base = &PyEnum_Type; 215 try 216 _pyType.tp_dict = classDict; 217 catch(Exception e) { 218 import core.stdc.stdio; 219 enum msg = "Could not create class dict for " ~ T.stringof ~ "\n"; 220 printf(msg); 221 } 222 } else 223 static assert(false, "Don't know what to do for type " ~ T.stringof); 224 225 static if(hasLength) { 226 if(_pyType.tp_as_sequence is null) 227 _pyType.tp_as_sequence = new PySequenceMethods; 228 _pyType.tp_as_sequence.sq_length = &_py_length; 229 } 230 231 if(PyType_Ready(&_pyType) < 0) { 232 PyErr_SetString(PyExc_TypeError, &"not ready"[0]); 233 failedToReady = true; 234 } 235 } 236 237 static if(is(T == enum)) { 238 private static PyObject* classDict() { 239 import python.conv.d_to_python: toPython; 240 import std.traits: EnumMembers, OriginalType; 241 242 OriginalType!T[string] dict; 243 244 static foreach(i; 0 .. EnumMembers!T.length) { 245 dict[__traits(identifier, EnumMembers!T[i])] = EnumMembers!T[i]; 246 } 247 248 return dict.toPython; 249 } 250 } 251 252 static if(isUserAggregate!T) 253 private static auto getsetDefs() { 254 import python.raw: PyGetSetDef; 255 import mirror.traits: isProperty, MemberFunctionsByOverload; 256 import std.meta: Filter; 257 import std.traits: ReturnType; 258 259 alias properties = Filter!(isProperty, MemberFunctionsByOverload!T); 260 261 // +1 due to the sentinel 262 static PyGetSetDef[fieldNames.length + properties.length + 1] getsets; 263 264 // don't bother if already initialised 265 if(getsets != getsets.init) return &getsets[0]; 266 267 template isPublic(string fieldName) { 268 static if(__traits(compiles, __traits(getMember, T, fieldName))) { 269 alias field = __traits(getMember, T, fieldName); 270 static if(__traits(compiles, __traits(getProtection, field))) 271 enum isPublic = __traits(getProtection, field) == "public"; 272 else 273 enum isPublic = false; 274 } else 275 enum isPublic = false; 276 } 277 278 // first deal with the public fields 279 static foreach(i; 0 .. fieldNames.length) { 280 getsets[i].name = cast(typeof(PyGetSetDef.name)) fieldNames[i]; 281 static if(isPublic!(fieldNames[i])) { 282 getsets[i].get = &PythonClass!T._get_impl!i; 283 getsets[i].set = &PythonClass!T._set_impl!i; 284 } 285 } 286 287 // then deal with the property functions 288 static foreach(j, property; properties) {{ 289 enum i = fieldNames.length + j; 290 291 getsets[i].name = cast(typeof(PyGetSetDef.name)) __traits(identifier, property); 292 293 static foreach(overload; __traits(getOverloads, T, __traits(identifier, property))) { 294 static if(is(ReturnType!overload == void)) {// setter 295 static if(__traits(compiles, &PythonClass!T.propertySet!overload)) 296 getsets[i].set = &PythonClass!T.propertySet!overload; 297 else 298 pragma(msg, "Cannot implement ", fullyQualifiedName!T, ".set!", i); 299 } else { // getter 300 static if(__traits(compiles, &PythonClass!T.propertyGet!overload)) 301 getsets[i].get = &PythonClass!T.propertyGet!overload; 302 else 303 pragma(msg, "Cannot implement ", fullyQualifiedName!T, ".get!", i); 304 } 305 } 306 }} 307 308 return &getsets[0]; 309 } 310 311 private static auto methodDefs()() { 312 import python.raw: PyMethodDef, MethodArgs; 313 import python.cooked: pyMethodDef, defaultMethodFlags; 314 import mirror.traits: isProperty; 315 import std.meta: AliasSeq, Alias, staticMap, Filter, templateNot; 316 import std.traits: isSomeFunction; 317 import std.algorithm: startsWith; 318 319 alias memberNames = AliasSeq!(__traits(allMembers, T)); 320 enum ispublic(string name) = isPublic!(T, name); 321 alias publicMemberNames = Filter!(ispublic, memberNames); 322 323 enum isRegular(string name) = 324 name != "this" 325 && name != "toHash" 326 && name != "factory" 327 && !name.startsWith("op") 328 && !name.startsWith("__") 329 ; 330 alias regularMemberNames = Filter!(isRegular, publicMemberNames); 331 alias overloads(string name) = AliasSeq!(__traits(getOverloads, T, name)); 332 alias members = staticMap!(overloads, regularMemberNames); 333 alias memberFunctions = Filter!(templateNot!isProperty, Filter!(isSomeFunction, members)); 334 335 // +1 due to sentinel 336 static PyMethodDef[memberFunctions.length + 1] methods; 337 338 if(methods != methods.init) return &methods[0]; 339 340 static foreach(i, memberFunction; memberFunctions) {{ 341 342 static if(__traits(isStaticFunction, memberFunction)) 343 enum flags = defaultMethodFlags | MethodArgs.Static; 344 else 345 enum flags = defaultMethodFlags; 346 347 static if(__traits(compiles, &PythonMethod!(T, memberFunction)._py_method_impl)) 348 methods[i] = pyMethodDef!(__traits(identifier, memberFunction), flags) 349 (&PythonMethod!(T, memberFunction)._py_method_impl); 350 else { 351 pragma(msg, "WARNING: could not wrap D method `", T, ".", __traits(identifier, memberFunction), "`"); 352 // uncomment to get the compiler error message to find out why not 353 // auto ptr = &PythonMethod!(T, memberFunction)._py_method_impl; 354 } 355 }} 356 357 return &methods[0]; 358 } 359 360 import python.raw: Py_ssize_t; 361 private static extern(C) Py_ssize_t _py_length(PyObject* self_) nothrow { 362 363 return noThrowable!({ 364 assert(self_ !is null); 365 static if(hasLength) { 366 import python.conv: to; 367 return self_.to!T.length; 368 } else 369 return -1; 370 }); 371 } 372 373 private static extern(C) PyObject* _py_repr(PyObject* self_) nothrow { 374 375 return noThrowable!({ 376 377 import python: pyUnicodeDecodeUTF8; 378 import python.conv: to; 379 import std.string: toStringz; 380 import std.conv: text; 381 import std.traits: fullyQualifiedName; 382 383 assert(self_ !is null); 384 385 static if(__traits(compiles, self_.to!T)) { 386 auto ret = text(self_.to!T); 387 return pyUnicodeDecodeUTF8(ret.ptr, ret.length, null /*errors*/); 388 } else { 389 pragma(msg, "WARNING: cannot generate repr for ", fullyQualifiedName!T); 390 PyObject* impl() { 391 throw new Exception("Unable to generate Python repr for F " ~ fullyQualifiedName!T); 392 } 393 return impl; 394 } 395 }); 396 } 397 398 private static extern(C) int _py_init(PyObject* self_, PyObject* args, PyObject* kwargs) nothrow { 399 // nothing to do 400 return 0; 401 } 402 403 static if(isUserAggregate!T && !isAbstract!T) 404 private static extern(C) PyObject* _py_new(PyTypeObject *type, PyObject* args, PyObject* kwargs) nothrow { 405 return noThrowable!({ 406 import python.conv: toPython; 407 import python.raw: PyTuple_Size; 408 import mirror.traits: isPrivate; 409 import std.traits: hasMember, fullyQualifiedName; 410 411 if(PyTuple_Size(args) == 0) return toPython(userAggregateInit!T); 412 413 static if(hasMember!(T, "__ctor") && !isPrivate!(__traits(getMember, T, "__ctor"))) { 414 static if(__traits(compiles, callDlangFunction!(T, __traits(getMember, T, "__ctor"))(null /*self*/, args, kwargs))) 415 return callDlangFunction!(T, __traits(getMember, T, "__ctor"))(null /*self*/, args, kwargs); 416 else { 417 pragma(msg, "WARNING: cannot wrap constructor for `", fullyQualifiedName!T, "`"); 418 // uncomment below to see the compilation error 419 // return callDlangFunction!(T, __traits(getMember, T, "__ctor"))(null /*self*/, args, kwargs); 420 return toPython(userAggregateInit!T); 421 } 422 423 } else { // allow implicit constructors to work in Python 424 T impl(fieldTypes fields = fieldTypes.init) { 425 static if(is(T == class)) { 426 if(PyTuple_Size(args) != 0) 427 throw new Exception(T.stringof ~ " has no constructor therefore can't construct one from arguments"); 428 return T.init; 429 } else { 430 static if(__traits(compiles, T(fields))) 431 return T(fields); 432 else { 433 pragma(msg, "WARNING: cannot use implicit constructor for `", T, "`"); 434 // uncomment below to see the compiler error 435 // auto _t_tmp = T(fields); 436 return T.init; 437 } 438 } 439 } 440 441 static if(__traits(compiles, callDlangFunction!(typeof(impl), impl)(null, args, kwargs))) 442 return callDlangFunction!(typeof(impl), impl)(null /*self*/, args, kwargs); 443 else { 444 enum msg = "could not generate constructor for " ~ fullyQualifiedName!T; 445 pragma(msg, "WARNING: ", msg); 446 static PyObject* oops() { 447 throw new Exception(msg); 448 } 449 return oops; 450 } 451 } 452 }); 453 } 454 } 455 456 457 private template isAbstract(T) { 458 import std.traits: isAbstractClass; 459 enum isAbstract = is(T == interface) || isAbstractClass!T; 460 } 461 462 463 // From a D operator (e.g. `+`) to a Python function pointer member name 464 private string dlangUnOpToPythonSlot(string op) { 465 enum opToSlot = [ 466 "+": "tp_as_number.nb_positive", 467 "-": "tp_as_number.nb_negative", 468 "~": "tp_as_number.nb_invert", 469 ]; 470 if(op !in opToSlot) throw new Exception("Unknown unary operator " ~ op); 471 return opToSlot[op]; 472 } 473 474 475 // From a D operator (e.g. `+`) to a Python function pointer member name 476 private string dlangBinOpToPythonSlot(string op) { 477 enum opToSlot = [ 478 "+": "tp_as_number.nb_add", 479 "+=": "tp_as_number.nb_inplace_add", 480 "-": "tp_as_number.nb_subtract", 481 "-=": "tp_as_number.nb_inplace_subtract", 482 "*": "tp_as_number.nb_multiply", 483 "*=": "tp_as_number.nb_inplace_multiply", 484 "/": "tp_as_number.nb_divide", 485 "/=": "tp_as_number.nb_inplace_true_divide", 486 "%": "tp_as_number.nb_remainder", 487 "%=": "tp_as_number.nb_inplace_remainder", 488 "^^": "tp_as_number.nb_power", 489 "^^=": "tp_as_number.nb_inplace_power", 490 "&": "tp_as_number.nb_and", 491 "&=": "tp_as_number.nb_inplace_and", 492 "|": "tp_as_number.nb_or", 493 "|=": "tp_as_number.nb_inplace_or", 494 "^": "tp_as_number.nb_xor", 495 "^=": "tp_as_number.nb_inplace_xor", 496 "<<": "tp_as_number.nb_lshift", 497 "<<=": "tp_as_number.nb_inplace_lshift", 498 ">>": "tp_as_number.nb_rshift", 499 ">>=": "tp_as_number.nb_inplace_rshift", 500 "~": "tp_as_sequence.sq_concat", 501 "~=": "tp_as_sequence.sq_concat", 502 "in": "tp_as_sequence.sq_contains", 503 ]; 504 if(op !in opToSlot) throw new Exception("Unknown binary operator " ~ op); 505 return opToSlot[op]; 506 } 507 508 509 // From a D operator (e.g. `+`) to a Python function pointer member name 510 private string dlangAssignOpToPythonSlot(string op) { 511 enum opToSlot = [ 512 "+": "tp_as_number.nb_inplace_add", 513 "-": "tp_as_number.nb_inplace_subtract", 514 "*": "tp_as_number.nb_inplace_multiply", 515 "/": "tp_as_number.nb_inplace_true_divide", 516 "%": "tp_as_number.nb_inplace_remainder", 517 "^^": "tp_as_number.nb_inplace_power", 518 "&": "tp_as_number.nb_inplace_and", 519 "|": "tp_as_number.nb_inplace_or", 520 "^": "tp_as_number.nb_inplace_xor", 521 "<<": "tp_as_number.nb_inplace_lshift", 522 ">>": "tp_as_number.nb_inplace_rshift", 523 "~": "tp_as_sequence.sq_concat", 524 ]; 525 if(op !in opToSlot) throw new Exception("Unknown assignment operator " ~ op); 526 return opToSlot[op]; 527 } 528 529 530 private auto pythonArgsToDArgs(bool isVariadic, P...)(PyObject* args, PyObject* kwargs) 531 if(allSatisfy!(isParameter, P)) 532 { 533 import python.raw: PyTuple_Size, PyTuple_GetItem, PyTuple_GetSlice, pyUnicodeDecodeUTF8, PyDict_GetItem; 534 import python.conv: to; 535 import std.typecons: Tuple; 536 import std.meta: staticMap; 537 import std.traits: Unqual; 538 import std.conv: text; 539 import std.exception: enforce; 540 541 const argsLength = args is null ? 0 : PyTuple_Size(args); 542 543 alias Type(alias Param) = Param.Type; 544 alias Types = staticMap!(Type, P); 545 546 // If one or more of the parameters is const/immutable, 547 // it'll be hard to construct it as such, so we Unqual 548 // the types for construction and cast to the appropriate 549 // type when returning. 550 alias MutableTuple = Tuple!(staticMap!(Unqual, Types)); 551 alias RetTuple = Tuple!(Types); 552 553 MutableTuple dArgs; 554 555 void positional(size_t i, T)() { 556 auto item = PyTuple_GetItem(args, i); 557 558 static if(__traits(compiles, checkPythonType!T(item))) { 559 if(!checkPythonType!T(item)) { 560 import python.raw: PyErr_Clear; 561 PyErr_Clear; 562 throw new ArgumentConversionException("Can't convert to " ~ T.stringof); 563 } 564 } else { 565 pragma(msg, "WARNING: cannot check python type for `", T, "`"); 566 // uncomment to see the compilation error 567 // checkPythonType!T(item); 568 } 569 570 dArgs[i] = item.to!T; 571 } 572 573 int pythonArgIndex = 0; 574 static foreach(i; 0 .. P.length) { 575 576 static if(i == P.length - 1 && isVariadic) { // last parameter and it's a typesafe variadic one 577 // slice the remaining arguments 578 auto remainingArgs = PyTuple_GetSlice(args, i, PyTuple_Size(args)); 579 dArgs[i] = remainingArgs.to!(P[i].Type); 580 } else static if(is(P[i].Default == void)) { 581 // ith parameter is required 582 enforce(i < argsLength, 583 text(__FUNCTION__, ": not enough Python arguments")); 584 positional!(i, typeof(dArgs[i])); 585 } else { 586 587 if(i < argsLength) { // regular case 588 positional!(i, P[i].Type); 589 } else { 590 // Here it gets tricky. The user could have supplied it in 591 // args positionally or via kwargs 592 auto key = pyUnicodeDecodeUTF8(&P[i].identifier[0], 593 P[i].identifier.length, 594 null /*errors*/); 595 enforce(key, "Errors converting '" ~ P[i].identifier ~ "' to Python object"); 596 auto val = kwargs ? PyDict_GetItem(kwargs, key) : null; 597 dArgs[i] = val 598 ? val.to!(P[i].Type) // use kwargs 599 : P[i].Default; // use default value 600 } 601 } 602 } 603 604 return cast(RetTuple) dArgs; 605 } 606 607 608 private alias Type(alias A) = typeof(A); 609 610 611 /** 612 The C API implementation of a Python method F of aggregate type T 613 */ 614 struct PythonMethod(T, alias F) { 615 static extern(C) PyObject* _py_method_impl(PyObject* self, 616 PyObject* args, 617 PyObject* kwargs) 618 nothrow 619 { 620 return noThrowable!(callDlangFunction!(T, F))(self, args, kwargs); 621 } 622 } 623 624 625 private void mutateSelf(T)(PyObject* self, auto ref T dAggregate) { 626 627 import python.conv.d_to_python: toPython; 628 import python.raw: pyDecRef; 629 630 auto newSelf = { 631 return self is null ? self : toPython(dAggregate); 632 }(); 633 scope(exit) { 634 if(self !is null) pyDecRef(newSelf); 635 } 636 auto pyClassSelf = cast(PythonClass!T*) self; 637 auto pyClassNewSelf = cast(PythonClass!T*) newSelf; 638 639 static foreach(i; 0 .. PythonClass!T.fieldNames.length) { 640 if(self !is null) 641 pyClassSelf._set_impl!i(self, pyClassNewSelf._get_impl!i(newSelf)); 642 } 643 644 } 645 646 647 /** 648 The C API implementation that calls a D function F. 649 */ 650 struct PythonFunction(alias F) { 651 static extern(C) PyObject* _py_function_impl(PyObject* self, PyObject* args, PyObject* kwargs) nothrow { 652 return noThrowable!(callDlangFunction!(void, F))(self, args, kwargs); 653 } 654 } 655 656 657 private auto noThrowable(alias F, A...)(auto ref A args) { 658 import python.raw: PyErr_SetString, PyExc_RuntimeError; 659 import std.string: toStringz; 660 import std.traits: ReturnType; 661 662 try { 663 return F(args); 664 } catch(Exception e) { 665 PyErr_SetString(PyExc_RuntimeError, e.msg.toStringz); 666 return ReturnType!F.init; 667 } catch(Error e) { 668 import std.conv: text; 669 try 670 PyErr_SetString(PyExc_RuntimeError, ("FATAL ERROR: " ~ e.text).toStringz); 671 catch(Exception _) 672 PyErr_SetString(PyExc_RuntimeError, ("FATAL ERROR: " ~ e.msg).toStringz); 673 674 return ReturnType!F.init; 675 } 676 } 677 678 679 class ArgsException: Exception { 680 import std.exception: basicExceptionCtors; 681 mixin basicExceptionCtors; 682 } 683 684 private PyObject* callDlangFunction(T, alias F)(PyObject* self, PyObject* args, PyObject *kwargs) { 685 686 import python.raw: PyTuple_Size; 687 import python.conv: toPython, to; 688 import mirror.traits: Parameters, NumDefaultParameters, NumRequiredParameters; 689 import std.traits: variadicFunctionStyle, Variadic, functionAttributes, 690 FunctionAttribute, moduleName, isCallable, StdParameters = Parameters; 691 import std.conv: text; 692 import std.exception: enforce; 693 import std.meta: AliasSeq; 694 695 enum identifier = __traits(identifier, F); 696 enum isCtor = isUserAggregate!T && identifier == "__ctor"; 697 enum isMethod = isUserAggregate!T && identifier != "__ctor"; 698 699 static if(is(T == void)) { // regular function 700 enum parent = moduleName!F; 701 mixin(`static import `, parent, `;`); 702 mixin(`alias Parent = `, parent, `;`); 703 } else static if(isMethod) { 704 enum parent = "dAggregate"; 705 alias Parent = T; 706 } else static if(isCallable!T) { 707 // nothing to do here 708 } else static if(isCtor) { 709 alias Parent = T; 710 } else 711 static assert(false, __FUNCTION__ ~ " does not know how to handle " ~ T.stringof); 712 713 static if(is(T == void)) 714 enum callMixin = `auto ret = callDlangFunction!F(dArgs);`; 715 else static if(isMethod) 716 enum callMixin = `auto ret = callDlangFunction!((StdParameters!overload dArgs) => ` ~ parent ~ `.` ~ identifier ~ `(dArgs))(dArgs);`; 717 else static if(isCtor) { 718 static if(is(T == class)) 719 enum callMixin = `auto ret = callDlangFunction!((StdParameters!overload dArgs) => new T(dArgs))(dArgs);`; 720 else 721 enum callMixin = `auto ret = callDlangFunction!((StdParameters!overload dArgs) => T(dArgs))(dArgs);`; 722 } else static if(isCallable!T && !isUserAggregate!T) 723 enum callMixin = `auto ret = callDlangFunction!F(dArgs);`; 724 else 725 static assert(false); 726 727 static if(__traits(compiles, __traits(getOverloads, Parent, identifier))) { 728 alias candidates = __traits(getOverloads, Parent, identifier); 729 // Deal with possible template instantiation functions. 730 // If it's a free function (T is void), then there must be at least 731 // one overload. The only reason for there to not be one is because 732 // it's a function template. 733 static if(is(T == void) && candidates.length == 0) 734 alias overloads = AliasSeq!F; 735 else 736 alias overloads = candidates; 737 } else 738 alias overloads = AliasSeq!F; 739 740 static foreach(overload; overloads) {{ 741 enum numDefaults = NumDefaultParameters!overload; 742 enum numRequired = NumRequiredParameters!overload; 743 enum isVariadic = variadicFunctionStyle!overload == Variadic.typesafe; 744 enum isMemberFunction = !__traits(isStaticFunction, overload) && !is(T == void); 745 746 static if(isUserAggregate!T && isMemberFunction && !isCtor) 747 assert(self !is null, 748 "Cannot call PythonMethod!" ~ identifier ~ " on null self"); 749 750 static if(functionAttributes!overload & FunctionAttribute.const_) 751 alias Aggregate = const T; 752 else static if(functionAttributes!overload & FunctionAttribute.immutable_) 753 alias Aggregate = immutable T; 754 else 755 alias Aggregate = Unqual!T; 756 757 static if(isUserAggregate!T) { // member function, static or not 758 // self could be null for static member functions 759 auto dAggregate = self is null ? Aggregate.init : self.to!Aggregate; 760 } 761 762 try { 763 const numArgs = args is null ? 0 : PyTuple_Size(args); 764 if(!isVariadic) 765 enforce!ArgumentConversionException( 766 numArgs >= numRequired 767 && numArgs <= Parameters!overload.length, 768 text("Received ", numArgs, " parameters but ", 769 identifier, " takes ", Parameters!overload.length)); 770 771 auto dArgs = pythonArgsToDArgs!(isVariadic, Parameters!overload)(args, kwargs); 772 mixin(callMixin); 773 774 static if(isUserAggregate!T && isMemberFunction && !isConstMemberFunction!overload) { 775 mutateSelf(self, dAggregate); 776 } 777 778 return ret; 779 } catch(ArgumentConversionException _) { 780 // only using this to weed out incompatible overloads 781 } 782 }} 783 784 throw new Exception("Could not find suitable overload for `" ~ identifier ~ "`"); 785 } 786 787 788 class ArgumentConversionException: Exception { 789 import std.exception: basicExceptionCtors; 790 mixin basicExceptionCtors; 791 } 792 793 794 private PyObject* callDlangFunction(alias F, A)(auto ref A argTuple) { 795 import python.raw: pyIncRef, pyNone; 796 import python.conv: toPython; 797 import std.traits: ReturnType; 798 799 // TODO - side-effects on parameters? 800 static if(is(ReturnType!F == void)) { 801 F(argTuple.expand); 802 pyIncRef(pyNone); 803 return pyNone; 804 } else { 805 auto dret = F(argTuple.expand); 806 return dret.toPython; 807 } 808 } 809 810 811 /** 812 Creates an instance of a Python class that is equivalent to the D type `T`. 813 Return PyObject*. 814 */ 815 PyObject* pythonClass(T)(auto ref T dobj) { 816 817 import python.conv: toPython; 818 import python.raw: pyObjectNew; 819 import std.traits: isPointer, PointerTarget; 820 821 static if(is(T == class) || isPointer!T) { 822 if(dobj is null) 823 throw new Exception("Cannot create Python class from null D object"); 824 } 825 826 static if(isPointer!T) 827 alias Type = PointerTarget!T; 828 else 829 alias Type = T; 830 831 auto _type = PythonType!Type.pyType; 832 833 auto ret = pyObjectNew!(PythonClass!Type)(PythonType!Type.pyType); 834 835 static foreach(fieldName; PythonType!Type.fieldNames) { 836 static if(isPublic!(T, fieldName)) 837 mixin(`ret.`, fieldName, ` = dobj.`, fieldName, `.toPython;`); 838 } 839 840 return cast(PyObject*) ret; 841 } 842 843 844 private template isPublic(T, string memberName) { 845 846 static if(__traits(compiles, __traits(getProtection, __traits(getMember, T, memberName)))) { 847 enum protection = __traits(getProtection, __traits(getMember, T, memberName)); 848 enum isPublic = protection == "public" || protection == "export"; 849 } else 850 enum isPublic = false; 851 } 852 853 /** 854 OOP types register factory functions here, indexed by the fully qualified 855 name of the type. This allows us to construct D class types from the 856 runtime types of Python values. 857 */ 858 Object delegate(PyObject*)[string] gFactory; 859 860 /** 861 A Python class that mirrors the D type `T`. 862 For instance, this struct: 863 ---------- 864 struct Foo { 865 int i; 866 string s; 867 } 868 ---------- 869 870 Will generate a Python class called `Foo` with two members, and trying to 871 assign anything but an integer to `Foo.i` or a string to `Foo.s` in Python 872 will raise `TypeError`. 873 */ 874 struct PythonClass(T) if(isUserAggregate!T) { 875 import python.raw: PyObjectHead, PyGetSetDef; 876 import std.traits: Unqual; 877 878 alias fieldNames = PythonType!(Unqual!T).fieldNames; 879 alias fieldTypes = PythonType!(Unqual!T).fieldTypes; 880 881 // Every python object must have this 882 mixin PyObjectHead; 883 884 // Field members 885 // Generate a python object field for every field in T 886 static foreach(fieldName; fieldNames) { 887 mixin(`PyObject* `, fieldName, `;`); 888 } 889 890 static if(is(T == class)) { 891 static this() { 892 import std.traits: fullyQualifiedName; 893 894 gFactory[fullyQualifiedName!(Unqual!T)] = (PyObject* value) { 895 import python.conv.python_to_d: to; 896 897 auto pyclass = cast(PythonClass!T*) value; 898 auto ret = userAggregateInit!(Unqual!T); 899 900 static foreach(fieldName; fieldNames) {{ 901 alias Field = typeof(__traits(getMember, ret, fieldName)); 902 __traits(getMember, ret, fieldName) = __traits(getMember, pyclass, fieldName).to!Field; 903 }} 904 905 return cast(Object) ret; 906 }; 907 } 908 } 909 910 // The function pointer for PyGetSetDef.get 911 private static extern(C) PyObject* _get_impl(int FieldIndex) 912 (PyObject* self_, void* closure = null) 913 nothrow 914 in(self_ !is null) 915 { 916 import python.raw: pyIncRef; 917 918 auto self = cast(PythonClass*) self_; 919 920 auto impl() { 921 auto field = self.getField!FieldIndex; 922 assert(field !is null, "Cannot increase reference count on null field"); 923 pyIncRef(field); 924 925 return field; 926 } 927 928 return noThrowable!impl; 929 } 930 931 // The function pointer for PyGetSetDef.set 932 static extern(C) int _set_impl(int FieldIndex) 933 (PyObject* self_, PyObject* value, void* closure = null) 934 nothrow 935 in(self_ !is null) 936 { 937 import python.raw: pyIncRef, pyDecRef, PyErr_SetString, PyExc_TypeError; 938 939 if(value is null) { 940 enum deleteErrStr = "Cannot delete " ~ fieldNames[FieldIndex]; 941 PyErr_SetString(PyExc_TypeError, deleteErrStr); 942 return -1; 943 } 944 945 static if(__traits(compiles, checkPythonType!(fieldTypes[FieldIndex])(value))) { 946 if(!checkPythonType!(fieldTypes[FieldIndex])(value)) { 947 return -1; 948 } 949 } else { 950 pragma(msg, "WARNING: cannot check python type for field #", FieldIndex, " of ", T); 951 // uncomment below to see compilation failure 952 // checkPythonType!(fieldTypes[FieldIndex])(value); 953 } 954 955 auto impl() { 956 auto self = cast(PythonClass!T*) self_; 957 auto tmp = self.getField!FieldIndex; 958 959 pyIncRef(value); 960 mixin(`self.`, fieldNames[FieldIndex], ` = value;`); 961 pyDecRef(tmp); 962 963 return 0; 964 } 965 966 return noThrowable!impl; 967 } 968 969 PyObject* getField(int FieldIndex)() { 970 971 auto impl()() { 972 mixin(`return this.`, fieldNames[FieldIndex], `;`); 973 } 974 975 static if(__traits(compiles, impl!()())) 976 return impl; 977 else { 978 import std.traits: fullyQualifiedName; 979 import std.conv: text; 980 981 enum msg = text("cannot implement ", fullyQualifiedName!T, ".getField!", FieldIndex); 982 pragma(msg, "WARNING: ", msg); 983 throw new Exception(msg); 984 } 985 } 986 987 static extern(C) PyObject* propertyGet(alias F) 988 (PyObject* self_, void* closure = null) 989 nothrow 990 in(self_ !is null) 991 { 992 return PythonMethod!(T, F)._py_method_impl(self_, null /*args*/, null /*kwargs*/); 993 } 994 995 static extern(C) int propertySet(alias F) 996 (PyObject* self_, PyObject* value, void* closure = null) 997 nothrow 998 in(self_ !is null) 999 { 1000 import python.raw: PyTuple_New, PyTuple_SetItem, pyDecRef; 1001 1002 auto args = PyTuple_New(1); 1003 PyTuple_SetItem(args, 0, value); 1004 scope(exit) pyDecRef(args); 1005 1006 PythonMethod!(T, F)._py_method_impl(self_, args, null /*kwargs*/); 1007 1008 return 0; 1009 } 1010 } 1011 1012 1013 PyObject* pythonCallable(T)(T callable) { 1014 import python.raw: pyObjectNew; 1015 1016 auto ret = pyObjectNew!(PythonCallable!T)(PythonType!T.pyType); 1017 ret._callable = callable; 1018 1019 return cast(PyObject*) ret; 1020 } 1021 1022 1023 private struct PythonCallable(T) if(isCallable!T) { 1024 1025 import std.traits: hasMember; 1026 1027 static if(hasMember!(T, "opCall")) { 1028 private static extern(C) PyObject* _py_call(PyObject* self, PyObject* args, PyObject* kwargs) nothrow { 1029 return PythonMethod!(T, T.opCall)._py_method_impl(self, args, kwargs); 1030 } 1031 } else { 1032 /** 1033 Reserves space for a callable to be stored in a PyObject struct so that it 1034 can later be called. 1035 */ 1036 1037 import python.raw: PyObjectHead; 1038 1039 // Every python object must have this 1040 mixin PyObjectHead; 1041 1042 private T _callable; 1043 1044 private static extern(C) PyObject* _py_call(PyObject* self_, PyObject* args, PyObject* kwargs) 1045 nothrow 1046 in(self_ !is null) 1047 do 1048 { 1049 import std.traits: Parameters, ReturnType; 1050 auto self = cast(PythonCallable!T*) self_; 1051 assert(self._callable !is null, "Cannot have null callable"); 1052 return noThrowable!(callDlangFunction!(T, (Parameters!T args) => self._callable(args)))(self_, args, kwargs); 1053 } 1054 } 1055 } 1056 1057 private bool isConstMemberFunction(alias F)() { 1058 import std.traits: functionAttributes, FunctionAttribute; 1059 return cast(bool) (functionAttributes!F & FunctionAttribute.const_); 1060 } 1061 1062 1063 private template PythonUnaryOperator(T, string op) { 1064 static extern(C) PyObject* _py_un_op(PyObject* self) nothrow { 1065 return noThrowable!({ 1066 import python.conv.python_to_d: to; 1067 import python.conv.d_to_python: toPython; 1068 import std.traits: Parameters; 1069 1070 static assert(Parameters!(T.opUnary!op).length == 0, "opUnary can't take any parameters"); 1071 1072 return self.to!T.opUnary!op.toPython; 1073 }); 1074 } 1075 } 1076 1077 1078 private template PythonBinaryOperator(T, BinaryOperator operator) { 1079 1080 static extern(C) int _py_in_func(PyObject* lhs, PyObject* rhs) 1081 nothrow 1082 in(operator.op == "in") 1083 { 1084 import python.conv.python_to_d: to; 1085 import python.conv.d_to_python: toPython; 1086 import std.traits: Parameters, hasMember; 1087 1088 alias inParams(U) = Parameters!(U.opBinaryRight!(operator.op)); 1089 1090 static if(__traits(compiles, inParams!T)) 1091 alias parameters = inParams!T; 1092 else 1093 alias parameters = void; 1094 1095 static if(is(typeof(T.init.opBinaryRight!(operator.op)(parameters.init)): bool)) { 1096 return noThrowable!({ 1097 1098 static assert(parameters.length == 1, "opBinaryRight!in must have one parameter"); 1099 alias Arg = parameters[0]; 1100 1101 auto this_ = lhs.to!T; 1102 auto dArg = rhs.to!Arg; 1103 1104 const ret = this_.opBinaryRight!(operator.op)(dArg); 1105 // See https://docs.python.org/3/c-api/sequence.html#c.PySequence_Contains 1106 return ret ? 1 : 0; 1107 }); 1108 } else { 1109 // Error. See https://docs.python.org/3/c-api/sequence.html#c.PySequence_Contains 1110 return -1; 1111 } 1112 } 1113 1114 static extern(C) PyObject* _py_bin_func(PyObject* lhs, PyObject* rhs) nothrow { 1115 return _py_ter_func(lhs, rhs, null); 1116 } 1117 1118 // Should only be for `^^` because in Python the function is ternary 1119 static extern(C) PyObject* _py_ter_func(PyObject* lhs_, PyObject* rhs_, PyObject* extra) nothrow { 1120 import python.conv.python_to_d: to; 1121 import python.conv.d_to_python: toPython; 1122 import mirror.traits: BinOpDir, functionName; 1123 import std.traits: Parameters; 1124 import std.exception: enforce; 1125 import std.conv: text; 1126 1127 return noThrowable!({ 1128 1129 PyObject* self, pArg; 1130 1131 if(lhs_.isInstanceOf!T) { 1132 self = lhs_; 1133 pArg = rhs_; 1134 } else if(rhs_.isInstanceOf!T) { 1135 self = rhs_; 1136 pArg = lhs_; 1137 } else 1138 throw new Exception("Neither lhs or rhs were of type " ~ T.stringof); 1139 1140 PyObject* impl(BinOpDir dir)() { 1141 1142 enum funcName = functionName(dir); 1143 1144 static if(operator.dirs & dir) { 1145 mixin(`alias parameters = Parameters!(T.`, funcName, `!(operator.op));`); 1146 static assert(parameters.length == 1, "Binary operators must take one parameter"); 1147 alias Arg = parameters[0]; 1148 1149 auto this_ = self.to!T; 1150 auto dArg = pArg.to!Arg; 1151 mixin(`return this_.`, funcName, `!(operator.op)(dArg).toPython;`); 1152 } else 1153 throw new Exception(text(T.stringof, " does not support ", funcName, " with self on ", dir)); 1154 } 1155 1156 if(lhs_.isInstanceOf!T) // self is on the left hand side 1157 return impl!(BinOpDir.left); 1158 else if(rhs_.isInstanceOf!T) // self is on the right hand side 1159 return impl!(BinOpDir.right); 1160 else 1161 throw new Exception("Neither lhs or rhs were of type " ~ T.stringof); 1162 }); 1163 } 1164 } 1165 1166 private template PythonAssignOperator(T, string op) { 1167 1168 static extern(C) PyObject* _py_bin_func(PyObject* lhs, PyObject* rhs) nothrow { 1169 return _py_ter_func(lhs, rhs, null); 1170 } 1171 1172 // Should only be for `^^` because in Python the function is ternary 1173 static extern(C) PyObject* _py_ter_func(PyObject* lhs, PyObject* rhs, PyObject* extra) nothrow { 1174 import python.conv.python_to_d: to; 1175 import python.conv.d_to_python: toPython; 1176 import std.traits: Parameters; 1177 1178 PyObject* impl() { 1179 alias parameters = Parameters!(T.opOpAssign!op); 1180 static assert(parameters.length == 1, "Assignment operators must take one parameter"); 1181 1182 auto dObj = lhs.to!T; 1183 dObj.opOpAssign!op(rhs.to!(parameters[0])); 1184 return dObj.toPython; 1185 } 1186 1187 return noThrowable!impl; 1188 } 1189 } 1190 1191 1192 private template PythonOpCmp(T) { 1193 static extern(C) PyObject* _py_cmp(PyObject* lhs, PyObject* rhs, int opId) nothrow { 1194 import python.raw: Py_LT, Py_LE, Py_EQ, Py_NE, Py_GT, Py_GE; 1195 import python.conv.python_to_d: to; 1196 import python.conv.d_to_python: toPython; 1197 import std.conv: text; 1198 import std.traits: Unqual, Parameters; 1199 1200 return noThrowable!({ 1201 1202 alias parameters = Parameters!(T.opCmp); 1203 static assert(parameters.length == 1, T.stringof ~ ".opCmp must have exactly one parameter"); 1204 1205 const cmp = lhs.to!(Unqual!T).opCmp(rhs.to!(Unqual!(parameters[0]))); 1206 1207 const dRes = { 1208 switch(opId) { 1209 default: throw new Exception(text("Unknown opId for opCmp: ", opId)); 1210 case Py_LT: return cmp < 0; 1211 case Py_LE: return cmp <= 0; 1212 case Py_EQ: return cmp == 0; 1213 case Py_NE: return cmp !=0; 1214 case Py_GT: return cmp > 0; 1215 case Py_GE: return cmp >=0; 1216 } 1217 }(); 1218 1219 return dRes.toPython; 1220 }); 1221 } 1222 } 1223 1224 1225 private template PythonSubscript(T) { 1226 1227 static extern(C) PyObject* _py_index(PyObject* self, PyObject* key) nothrow { 1228 import python.raw: pyIndexCheck, pySliceCheck, PyObject_Repr, PyObject_Length, 1229 Py_ssize_t, PySlice_GetIndices; 1230 import python.conv.python_to_d: to; 1231 import python.conv.d_to_python: toPython; 1232 import std.traits: Parameters, Unqual, hasMember, fullyQualifiedName; 1233 import std.meta: Filter, AliasSeq; 1234 1235 PyObject* impl() { 1236 static if(hasMember!(T, "opIndex")) { 1237 if(pyIndexCheck(key)) { 1238 static if(__traits(compiles, Parameters!(T.opIndex))) { 1239 alias parameters = Parameters!(T.opIndex); 1240 static if(parameters.length == 1) 1241 return self.to!(Unqual!T).opIndex(key.to!(parameters[0])).toPython; 1242 else 1243 throw new Exception("Don't know how to handle opIndex with more than one parameter"); 1244 } 1245 } else if(pySliceCheck(key)) { 1246 1247 enum hasTwoParams(alias F) = Parameters!F.length == 2; 1248 1249 static if(!hasMember!(T, "opSlice")) { 1250 throw new Exception(fullyQualifiedName!T ~ " has no opSlice"); 1251 } else { 1252 1253 alias twoParamOpSlices = Filter!(hasTwoParams, __traits(getOverloads, T, "opSlice")); 1254 1255 static if(twoParamOpSlices.length > 0) { 1256 1257 static assert(twoParamOpSlices.length == 1); 1258 alias opSlice = twoParamOpSlices[0]; 1259 1260 const len = PyObject_Length(self); 1261 Py_ssize_t start, stop, step; 1262 const indicesRet = PySlice_GetIndices(key, len, &start, &stop, &step); 1263 1264 if(indicesRet < 0) 1265 throw new Exception("Could not get slice indices for key '" ~ PyObject_Repr(key).to!string ~ "'"); 1266 1267 if(step != 1) 1268 throw new Exception("Slice steps other than 1 not supported in D: " ~ PyObject_Repr(key).to!string); 1269 1270 auto dObj = self.to!T; 1271 return dObj[start .. stop].toPython; 1272 1273 } else { 1274 throw new Exception(T.stringof ~ " cannot be sliced by " ~ PyObject_Repr(key).to!string); 1275 } 1276 1277 assert(0, "Error in slicing " ~ T.stringof ~ " with " ~ PyObject_Repr(key).to!string); 1278 } 1279 } else 1280 throw new Exception(T.stringof ~ " failed pyIndexCheck and pySliceCheck for key '" ~ PyObject_Repr(key).to!string ~ "'"); 1281 assert(0); 1282 } else 1283 throw new Exception(fullyQualifiedName!T ~ " has no opIndex"); 1284 } 1285 1286 return noThrowable!impl; 1287 } 1288 } 1289 1290 1291 /** 1292 Implement a Python iterator for D type T. 1293 We get a D slice from it, convert it to a Python list, 1294 then return its iterator. 1295 */ 1296 private template PythonIter(T) { 1297 1298 static extern(C) PyObject* _py_iter(PyObject* self) nothrow { 1299 import python.raw: PyObject_GetIter; 1300 import python.conv.d_to_python: toPython; 1301 import python.conv.python_to_d: to; 1302 import std.array; 1303 import std.traits: fullyQualifiedName; 1304 1305 PyObject* impl() { 1306 static if(__traits(compiles, T.init[].array[0])) { 1307 auto dObj = self.to!T; 1308 auto list = dObj[].array.toPython; 1309 return PyObject_GetIter(list); 1310 } else { 1311 throw new Exception("Cannot get an array from " ~ fullyQualifiedName!T ~ "[]"); 1312 } 1313 } 1314 1315 return noThrowable!impl; 1316 } 1317 } 1318 1319 1320 private template PythonIndexAssign(T) { 1321 1322 static extern(C) int _py_index_assign(PyObject* self, PyObject* key, PyObject* val) nothrow { 1323 1324 import python.conv.python_to_d: to; 1325 import python.conv.d_to_python: toPython; 1326 import python.raw: pyIndexCheck, pySliceCheck, PyObject_Repr, PyObject_Length, PySlice_GetIndices, Py_ssize_t; 1327 import std.traits: Parameters, Unqual; 1328 import std.conv: to; 1329 import std.meta: Filter, AliasSeq; 1330 1331 int impl() { 1332 if(pyIndexCheck(key)) { 1333 static if(__traits(compiles, Parameters!(T.opIndexAssign))) { 1334 alias parameters = Parameters!(T.opIndexAssign); 1335 static if(parameters.length == 2) { 1336 auto dObj = self.to!(Unqual!T); 1337 dObj.opIndexAssign(val.to!(parameters[0]), key.to!(parameters[1])); 1338 mutateSelf(self, dObj); 1339 return 0; 1340 } else 1341 //throw new Exception("Don't know how to handle opIndex with more than one parameter"); 1342 return -1; 1343 } else 1344 return -1; 1345 } else if(pySliceCheck(key)) { 1346 1347 enum hasThreeParams(alias F) = Parameters!F.length == 3; 1348 alias threeParamOps = Filter!(hasThreeParams, 1349 AliasSeq!( 1350 __traits(getOverloads, T, "opIndexAssign"), 1351 __traits(getOverloads, T, "opSliceAssign"), 1352 ) 1353 ); 1354 1355 static if(threeParamOps.length > 0) { 1356 1357 static assert(threeParamOps.length == 1); 1358 alias opIndexAssign = threeParamOps[0]; 1359 alias parameters = Parameters!opIndexAssign; 1360 1361 const len = PyObject_Length(self); 1362 Py_ssize_t start, stop, step; 1363 const indicesRet = PySlice_GetIndices(key, len, &start, &stop, &step); 1364 1365 if(indicesRet < 0) 1366 return -1; 1367 1368 if(step != 1) 1369 return -1; 1370 1371 auto dObj = self.to!(Unqual!T); 1372 mixin(`dObj.`, __traits(identifier, opIndexAssign), `(val.to!(parameters[0]), start, stop);`); 1373 mutateSelf(self, dObj); 1374 return 0; 1375 } else { 1376 return -1; 1377 } 1378 } else 1379 return -1; 1380 } 1381 1382 return noThrowable!impl; 1383 } 1384 } 1385 1386 private bool isInstanceOf(T)(PyObject* obj) { 1387 import python.raw: PyObject_IsInstance; 1388 return cast(bool) PyObject_IsInstance(obj, cast(PyObject*) PythonType!T.pyType); 1389 } 1390 1391 1392 private bool checkPythonType(T)(PyObject* value) if(isArray!T) { 1393 import python.raw: pySequenceCheck; 1394 const ret = pySequenceCheck(value); 1395 if(!ret) setPyErrTypeString!"sequence"; 1396 return ret; 1397 } 1398 1399 1400 private bool checkPythonType(T)(PyObject* value) if(isIntegral!T) { 1401 import python.raw: pyIntCheck, pyLongCheck; 1402 const ret = pyLongCheck(value) || pyIntCheck(value); 1403 if(!ret) setPyErrTypeString!"long"; 1404 return ret; 1405 } 1406 1407 1408 private bool checkPythonType(T)(PyObject* value) if(isFloatingPoint!T) { 1409 import python.raw: pyFloatCheck; 1410 const ret = pyFloatCheck(value); 1411 if(!ret) setPyErrTypeString!"float"; 1412 return ret; 1413 } 1414 1415 1416 private bool checkPythonType(T)(PyObject* value) if(is(T == DateTime)) { 1417 import python.raw: pyDateTimeCheck; 1418 const ret = pyDateTimeCheck(value); 1419 if(!ret) setPyErrTypeString!"DateTime"; 1420 return ret; 1421 } 1422 1423 1424 private bool checkPythonType(T)(PyObject* value) if(is(T == Date)) { 1425 import python.raw: pyDateCheck; 1426 const ret = pyDateCheck(value); 1427 if(!ret) setPyErrTypeString!"Date"; 1428 return ret; 1429 } 1430 1431 1432 private bool checkPythonType(T)(PyObject* value) if(isAssociativeArray!T) { 1433 import python.raw: pyMappingCheck; 1434 const ret = pyMappingCheck(value); 1435 if(!ret) setPyErrTypeString!"dict"; 1436 return ret; 1437 } 1438 1439 1440 private bool checkPythonType(T)(PyObject* value) if(isUserAggregate!T) { 1441 return true; // FIXMME 1442 } 1443 1444 1445 private bool checkPythonType(T)(PyObject* value) if(isSomeFunction!T) { 1446 import python.raw: pyCallableCheck; 1447 const ret = pyCallableCheck(value); 1448 if(!ret) setPyErrTypeString!"callable"; 1449 return ret; 1450 } 1451 1452 1453 private void setPyErrTypeString(string type)() @trusted @nogc nothrow { 1454 import python.raw: PyErr_SetString, PyExc_TypeError; 1455 enum str = "must be a " ~ type; 1456 PyErr_SetString(PyExc_TypeError, &str[0]); 1457 } 1458 1459 // Generalises T.init for classes since null isn't a value we want to use 1460 T userAggregateInit(T)() { 1461 static if(is(T == class)) { 1462 auto buffer = new void[__traits(classInstanceSize, T)]; 1463 // this is needed for the vtable to work 1464 buffer[] = typeid(T).initializer[]; 1465 return cast(T) buffer.ptr; 1466 } else 1467 return T.init; 1468 }