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