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