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