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