1 module python.conv.python_to_d; 2 3 4 import python.raw: PyObject, pyListCheck, pyTupleCheck, PyTuple_Size, pyCallableCheck, pyTimeCheck; 5 import python.type: isUserAggregate, isTuple; 6 import std.traits: Unqual, isIntegral, isFloatingPoint, isAggregateType, isArray, 7 isStaticArray, isAssociativeArray, isPointer, PointerTarget, isSomeChar, isSomeString, 8 isDelegate, isFunctionPointer; 9 import std.range: isInputRange; 10 import std.datetime: DateTime, Date, TimeOfDay; 11 import core.time: Duration; 12 13 14 T to(T)(PyObject* value) @trusted if(isIntegral!T && !is(T == enum)) { 15 import python.raw: PyLong_AsLong; 16 17 const ret = PyLong_AsLong(value); 18 if(ret > T.max || ret < T.min) 19 throw new Exception("Overflow converting PyObject to " ~ T.stringof); 20 21 return cast(T) ret; 22 } 23 24 25 T to(T)(PyObject* value) @trusted if(isFloatingPoint!T) { 26 import python.raw: PyFloat_AsDouble; 27 auto ret = PyFloat_AsDouble(value); 28 return cast(T) ret; 29 } 30 31 32 private template UserAggregateReturnType(T) { 33 import std.traits: isCopyable; 34 35 static if(isCopyable!T) 36 alias UserAggregateReturnType = T; 37 else 38 alias UserAggregateReturnType = T*; 39 } 40 41 // Returns T if T is copyable, T* otherwise 42 UserAggregateReturnType!T to(T)(PyObject* value) 43 @trusted 44 if(isUserAggregate!T && (is(T == struct) || is(T == union))) 45 { 46 import std.traits: Unqual, isCopyable; 47 48 alias RetType = UserAggregateReturnType!T; 49 50 static if(isCopyable!T) { 51 Unqual!T ret; 52 toStructImpl(value, &ret); 53 } else { 54 auto ret = new Unqual!T; 55 toStructImpl(value, ret); 56 } 57 58 return maybeCast!RetType(ret); 59 } 60 61 62 private void toStructImpl(T)(PyObject* value, T* ret) { 63 import autowrap.common: AlwaysTry; 64 import python.raw: Py_None; 65 import python.type: PythonClass; 66 import std.traits: fullyQualifiedName, isCopyable, isPointer; 67 68 if(value == Py_None) 69 throw new Exception("Cannot convert None to " ~ T.stringof); 70 71 auto pyclass = cast(PythonClass!T*) value; 72 73 static foreach(i; 0 .. typeof(*ret).tupleof.length) {{ 74 static if(AlwaysTry || __traits(compiles, pyclass.getField!i.to!(typeof(T.tupleof[i])))) { 75 76 auto pythonField = pyclass.getField!i; 77 auto converted = pythonField.to!(typeof(T.tupleof[i])); 78 79 static if(isCopyable!(typeof((*ret).tupleof[i]))) 80 (*ret).tupleof[i] = converted; 81 else { 82 static assert(isPointer!(typeof(converted))); 83 toStructImpl(pyclass.getField!i, &((*ret).tupleof[i])); 84 } 85 } else 86 pragma(msg, "WARNING: cannot convert struct field #", i, " of ", fullyQualifiedName!T); 87 }} 88 } 89 90 91 T to(T)(PyObject* value) @trusted if(isUserAggregate!T && !is(T == struct) && !is(T == union)) 92 { 93 import python.type: PythonClass, userAggregateInit, gFactory; 94 import std.traits: Unqual; 95 import std.string: fromStringz; 96 import std.conv: text; 97 98 assert(value !is null, "Cannot convert null PyObject to `" ~ T.stringof ~ "`"); 99 100 auto pyclass = cast(PythonClass!T*) value; 101 const runtimeType = value.ob_type.tp_name.fromStringz.text; 102 auto creator = runtimeType in gFactory; 103 return creator 104 ? cast(T) (*creator)(value) 105 : userAggregateInit!T; 106 } 107 108 109 T to(T)(PyObject* value) if(isPointer!T && isUserAggregate!(PointerTarget!T)) { 110 auto ret = new Unqual!(PointerTarget!T); 111 toStructImpl(value, ret); 112 return ret; 113 } 114 115 116 // FIXME - not sure why a separate implementation is needed for non user aggregates 117 T to(T)(PyObject* value) 118 if(isPointer!T && !isUserAggregate!(PointerTarget!T) && 119 !isFunctionPointer!T && !isDelegate!T && 120 !is(Unqual!(PointerTarget!T) == void)) 121 { 122 import python.raw: pyUnicodeCheck; 123 import std.traits: Unqual, PointerTarget; 124 import std.string: toStringz; 125 126 enum isStringz = is(PointerTarget!T == const(char)) || is(PointerTarget!T == immutable(char)); 127 128 if(isStringz && pyUnicodeCheck(value)) 129 return value.to!string.toStringz.maybeCast!T; 130 else { 131 auto ret = new Unqual!(PointerTarget!T); 132 *ret = value.to!(Unqual!(PointerTarget!T)); 133 return ret.maybeCast!T; 134 } 135 } 136 137 138 T to(T)(PyObject* value) if(isPointer!T && is(Unqual!(PointerTarget!T) == void)) 139 { 140 import python.raw: pyBytesCheck, PyBytes_AsString; 141 import std.exception: enforce; 142 143 enforce(pyBytesCheck(value), "Can only convert Python bytes object to void*"); 144 return cast(void*) PyBytes_AsString(value); 145 } 146 147 148 T to(T)(PyObject* value) if(is(Unqual!T == DateTime)) { 149 import python.raw; 150 151 return DateTime(pyDateTimeYear(value), 152 pyDateTimeMonth(value), 153 pyDateTimeDay(value), 154 pyDateTimeHour(value), 155 pyDateTimeMinute(value), 156 pyDateTimeSecond(value)); 157 } 158 159 160 T to(T)(PyObject* value) if(is(Unqual!T == Date)) { 161 import python.raw; 162 163 return Date(pyDateTimeYear(value), 164 pyDateTimeMonth(value), 165 pyDateTimeDay(value)); 166 } 167 168 T to(T)(PyObject* value) if(is(Unqual!T == TimeOfDay)) { 169 import python.raw; 170 171 if(!pyTimeCheck(value)) 172 throw new Exception("Can only convert time objects to TimeOfDay"); 173 174 return TimeOfDay(pyTimeHour(value), 175 pyTimeMinute(value), 176 pyTimeSecond(value)); 177 } 178 179 180 T to(T)(PyObject* value) if(isArray!T && !isSomeString!T) 181 in(pyListCheck(value) || pyTupleCheck(value)) 182 { 183 import python.raw: PyList_Size, PyList_GetItem, PyTuple_Size, PyTuple_GetItem; 184 import std.range: ElementEncodingType; 185 import std.traits: Unqual, isDynamicArray; 186 import std.exception: enforce; 187 import std.conv: text; 188 189 alias ElementType = Unqual!(ElementEncodingType!T); 190 191 // This is needed to deal with array of const or immutable 192 static if(isDynamicArray!T) 193 alias ArrayType = ElementType[]; 194 else 195 alias ArrayType = Unqual!T; 196 197 // deal with void[] here since otherwise we won't be able to iterate over it 198 static if(is(ElementType == void)) 199 alias RetType = ubyte[]; 200 else 201 alias RetType = ArrayType; 202 203 RetType ret; 204 205 static if(__traits(compiles, ret.length = 1)) { 206 const valueLength = { 207 if(pyListCheck(value)) 208 return PyList_Size(value); 209 else if(pyTupleCheck(value)) 210 return PyTuple_Size(value); 211 else 212 assert(0); 213 }(); 214 assert(valueLength >= 0, text("Invalid length ", valueLength)); 215 ret.length = valueLength; 216 } 217 218 foreach(i, ref elt; ret) { 219 auto pythonItem = { 220 if(pyListCheck(value)) 221 return PyList_GetItem(value, i); 222 else if(pyTupleCheck(value)) 223 return PyTuple_GetItem(value, i); 224 else 225 assert(0); 226 }(); 227 elt = pythonItem.to!(typeof(elt)); 228 } 229 230 return maybeCast!T(ret); 231 } 232 233 234 T to(T)(PyObject* value) if(isSomeString!T) { 235 236 import python.raw: pyUnicodeGetSize, pyUnicodeCheck, 237 pyBytesAsString, pyObjectUnicode, pyUnicodeAsUtf8String, Py_ssize_t; 238 import std.conv: to; 239 240 value = pyObjectUnicode(value); 241 242 const length = pyUnicodeGetSize(value); 243 if(length == 0) return T.init; 244 245 auto str = pyUnicodeAsUtf8String(value); 246 if(str is null) throw new Exception("Tried to convert a non-string Python value to D string"); 247 248 auto ptr = pyBytesAsString(str); 249 assert(length == 0 || ptr !is null); 250 251 auto slice = ptr[0 .. length]; 252 253 return slice.to!T; 254 } 255 256 257 T to(T)(PyObject* value) if(is(Unqual!T == bool)) { 258 import python.raw: pyTrue; 259 return value is pyTrue; 260 } 261 262 263 T to(T)(PyObject* value) if(isAssociativeArray!T) 264 { 265 import python.raw: pyDictCheck, PyDict_Keys, PyList_Size, PyList_GetItem, PyDict_GetItem; 266 267 assert(pyDictCheck(value)); 268 269 // this enum is to get K and V whilst avoiding auto-decoding, which is why we're not using 270 // std.traits 271 enum _ = is(T == V[K], V, K); 272 alias KeyType = Unqual!K; 273 alias ValueType = Unqual!V; 274 275 ValueType[KeyType] ret; 276 277 auto keys = PyDict_Keys(value); 278 279 foreach(i; 0 .. PyList_Size(keys)) { 280 auto k = PyList_GetItem(keys, i); 281 auto v = PyDict_GetItem(value, k); 282 auto dk = k.to!KeyType; 283 auto dv = v.to!ValueType; 284 285 ret[dk] = dv; 286 } 287 288 return ret; 289 } 290 291 292 T to(T)(PyObject* value) if(isTuple!T) 293 in(pyTupleCheck(value)) 294 in(PyTuple_Size(value) == T.length) 295 do 296 { 297 import python.raw: pyTupleCheck, PyTuple_Size, PyTuple_GetItem; 298 299 T ret; 300 301 static foreach(i; 0 .. T.length) { 302 ret[i] = PyTuple_GetItem(value, i).to!(typeof(ret[i])); 303 } 304 305 return ret; 306 } 307 308 309 T to(T)(PyObject* value) if(is(Unqual!T == char) || is(Unqual!T == wchar) || is(Unqual!T == dchar)) { 310 auto str = value.to!string; 311 return str[0]; 312 } 313 314 315 T to(T)(PyObject* value) if(isDelegate!T) { 316 return value.toDlangFunction!T; 317 } 318 319 T to(T)(PyObject* value) if(isFunctionPointer!T) { 320 throw new Exception("Conversion of Python functions to D function pointers not yet implemented"); 321 } 322 323 324 private T toDlangFunction(T)(PyObject* value) 325 in(pyCallableCheck(value)) 326 do 327 { 328 import python.raw: PyObject_CallObject; 329 import python.conv.d_to_python: toPython; 330 import python.conv.python_to_d: to; 331 import std.traits: ReturnType, Unqual; 332 static import std.traits; 333 import std.meta: staticMap; 334 import std.typecons: Tuple; 335 import std.format: format; 336 import std.conv: text; 337 338 alias UnqualParams = staticMap!(Unqual, std.traits.Parameters!T); 339 340 enum code = 341 q{ 342 // FIXME: the @trusted here is due to conversions to @safe 343 // D delegates 344 return (%s) @trusted { 345 try { 346 Tuple!UnqualParams dArgsTuple; 347 static foreach(i; 0 .. UnqualParams.length) { 348 dArgsTuple[i] = mixin(`arg` ~ i.text); 349 } 350 auto pyArgs = dArgsTuple.toPython; 351 auto pyResult = PyObject_CallObject(value, pyArgs); 352 static if(!is(ReturnType!T == void)) 353 return pyResult.to!(ReturnType!T); 354 else 355 return; 356 } catch(Exception e) { 357 import python.raw: PyErr_SetString, PyExc_RuntimeError; 358 import std.string: toStringz; 359 PyErr_SetString(PyExc_RuntimeError, e.msg.toStringz); 360 } 361 assert(0); 362 363 }; 364 }.format( 365 parametersRecipe!T("T"), 366 ) 367 ; 368 369 // pragma(msg, code); 370 mixin(code); 371 } 372 373 private string parametersRecipe(alias F)(in string symbol) 374 in(__ctfe) 375 do 376 { 377 378 import std.array: join; 379 import std.traits: Parameters; 380 381 string[] parameters; 382 383 static foreach(i; 0 .. Parameters!F.length) { 384 parameters ~= parameterRecipe!(F, i)(symbol); 385 } 386 387 return parameters.join(", "); 388 } 389 390 391 private string parameterRecipe(alias F, size_t i)(in string symbol) 392 in(__ctfe) 393 do 394 { 395 import std.array: join; 396 import std.conv: text; 397 import std.traits: ParameterDefaults; 398 399 const string[] storageClasses = [ __traits(getParameterStorageClasses, F, i) ]; 400 401 static string defaultValue(alias default_)() { 402 static if(is(default_ == void)) 403 return ""; 404 else 405 return text(" = ", default_); 406 } 407 408 409 return 410 text(storageClasses.join(" "), " ", 411 `std.traits.Parameters!(`, symbol, `)[`, i, `] `, 412 `arg`, i, 413 defaultValue!(ParameterDefaults!F[i]), 414 ); 415 } 416 417 418 419 T to(T)(PyObject* value) if(is(Unqual!T == Duration)) { 420 import python.raw: PyDateTime_Delta; 421 import core.time: days, seconds, usecs; 422 423 const delta = cast(PyDateTime_Delta*) value; 424 425 return delta.days.days + delta.seconds.seconds + delta.microseconds.usecs; 426 } 427 428 429 T to(T)(PyObject* value) if(is(T == enum)) { 430 import std.traits: OriginalType; 431 return cast(T) value.to!(OriginalType!T); 432 } 433 434 435 // Usually this is needed in the presence of `const` or `immutable` 436 private auto maybeCast(Wanted, Actual)(auto ref Actual value) { 437 static if(is(Wanted == Actual)) 438 return value; 439 // the presence of `opCast` might mean this isn't 440 // always possible 441 else static if(__traits(compiles, cast(Wanted) value)) 442 return cast(Wanted) value; 443 else { 444 Wanted ret = value; 445 return ret; 446 } 447 }