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 // Returns T if T is copyable, T* otherwise 32 auto to(T)(PyObject* value) @trusted if(isUserAggregate!T && is(T == struct)) { 33 import std.traits: Unqual, isCopyable; 34 35 static if(isCopyable!T) { 36 alias RetType = T; 37 Unqual!T ret; 38 toStructImpl(value, &ret); 39 } else { 40 alias RetType = T*; 41 auto ret = new Unqual!T; 42 toStructImpl(value, ret); 43 } 44 45 return maybeCast!RetType(ret); 46 } 47 48 49 private void toStructImpl(T)(PyObject* value, T* ret) { 50 import python.type: PythonClass; 51 import std.traits: fullyQualifiedName; 52 53 auto pyclass = cast(PythonClass!T*) value; 54 55 static foreach(i; 0 .. typeof(*ret).tupleof.length) { 56 static if(__traits(compiles, pyclass.getField!i.to!(typeof(T.tupleof[i])))) 57 (*ret).tupleof[i] = pyclass.getField!i.to!(typeof(T.tupleof[i])); 58 else 59 pragma(msg, "WARNING: cannot convert struct field #", i, " of ", fullyQualifiedName!T); 60 } 61 } 62 63 64 T to(T)(PyObject* value) @trusted if(isUserAggregate!T && !is(T == struct)) 65 { 66 import python.type: PythonClass, userAggregateInit, gFactory; 67 import std.traits: Unqual; 68 import std.string: fromStringz; 69 import std.conv: text; 70 71 assert(value !is null, "Cannot convert null PyObject to `" ~ T.stringof ~ "`"); 72 73 auto pyclass = cast(PythonClass!T*) value; 74 const runtimeType = value.ob_type.tp_name.fromStringz.text; 75 auto creator = runtimeType in gFactory; 76 return creator 77 ? cast(T) (*creator)(value) 78 : userAggregateInit!T; 79 } 80 81 82 T to(T)(PyObject* value) if(isPointer!T && isUserAggregate!(PointerTarget!T)) { 83 auto ret = new Unqual!(PointerTarget!T); 84 toStructImpl(value, ret); 85 return ret; 86 } 87 88 89 // FIXME - not sure why a separate implementation is needed for non user aggregates 90 T to(T)(PyObject* value) 91 if(isPointer!T && !isUserAggregate!(PointerTarget!T) && 92 !isFunctionPointer!T && !isDelegate!T && 93 !is(Unqual!(PointerTarget!T) == void)) 94 { 95 import python.raw: pyUnicodeCheck; 96 import std.traits: Unqual, PointerTarget; 97 import std.string: toStringz; 98 99 enum isStringz = is(PointerTarget!T == const(char)) || is(PointerTarget!T == immutable(char)); 100 101 if(isStringz && pyUnicodeCheck(value)) 102 return value.to!string.toStringz.maybeCast!T; 103 else { 104 auto ret = new Unqual!(PointerTarget!T); 105 *ret = value.to!(Unqual!(PointerTarget!T)); 106 return ret.maybeCast!T; 107 } 108 } 109 110 111 T to(T)(PyObject* value) if(isPointer!T && is(Unqual!(PointerTarget!T) == void)) 112 { 113 import python.raw: pyBytesCheck, PyBytes_AsString; 114 import std.exception: enforce; 115 116 enforce(pyBytesCheck(value), "Can only convert Python bytes object to void*"); 117 return cast(void*) PyBytes_AsString(value); 118 } 119 120 121 T to(T)(PyObject* value) if(is(Unqual!T == DateTime)) { 122 import python.raw; 123 124 return DateTime(pyDateTimeYear(value), 125 pyDateTimeMonth(value), 126 pyDateTimeDay(value), 127 pyDateTimeHour(value), 128 pyDateTimeMinute(value), 129 pyDateTimeSecond(value)); 130 131 } 132 133 134 T to(T)(PyObject* value) if(is(Unqual!T == Date)) { 135 import python.raw; 136 137 return Date(pyDateTimeYear(value), 138 pyDateTimeMonth(value), 139 pyDateTimeDay(value)); 140 } 141 142 143 T to(T)(PyObject* value) if(isArray!T && !isSomeString!T) 144 in(pyListCheck(value) || pyTupleCheck(value)) 145 { 146 import python.raw: PyList_Size, PyList_GetItem, PyTuple_Size, PyTuple_GetItem; 147 import std.range: ElementEncodingType; 148 import std.traits: Unqual, isDynamicArray; 149 import std.exception: enforce; 150 import std.conv: text; 151 152 alias ElementType = Unqual!(ElementEncodingType!T); 153 154 // This is needed to deal with array of const or immutable 155 static if(isDynamicArray!T) 156 alias ArrayType = ElementType[]; 157 else 158 alias ArrayType = Unqual!T; 159 160 // deal with void[] here since otherwise we won't be able to iterate over it 161 static if(is(ElementType == void)) 162 alias RetType = ubyte[]; 163 else 164 alias RetType = ArrayType; 165 166 RetType ret; 167 168 static if(__traits(compiles, ret.length = 1)) { 169 const valueLength = { 170 if(pyListCheck(value)) 171 return PyList_Size(value); 172 else if(pyTupleCheck(value)) 173 return PyTuple_Size(value); 174 else 175 assert(0); 176 }(); 177 assert(valueLength >= 0, text("Invalid length ", valueLength)); 178 ret.length = valueLength; 179 } 180 181 foreach(i, ref elt; ret) { 182 auto pythonItem = { 183 if(pyListCheck(value)) 184 return PyList_GetItem(value, i); 185 else if(pyTupleCheck(value)) 186 return PyTuple_GetItem(value, i); 187 else 188 assert(0); 189 }(); 190 elt = pythonItem.to!(typeof(elt)); 191 } 192 193 return maybeCast!T(ret); 194 } 195 196 197 T to(T)(PyObject* value) if(isSomeString!T) { 198 199 import python.raw: pyUnicodeGetSize, pyUnicodeCheck, 200 pyBytesAsString, pyObjectUnicode, pyUnicodeAsUtf8String, Py_ssize_t; 201 import std.conv: to; 202 203 value = pyObjectUnicode(value); 204 205 const length = pyUnicodeGetSize(value); 206 if(length == 0) return T.init; 207 208 auto str = pyUnicodeAsUtf8String(value); 209 if(str is null) throw new Exception("Tried to convert a non-string Python value to D string"); 210 211 auto ptr = pyBytesAsString(str); 212 assert(length == 0 || ptr !is null); 213 214 auto slice = ptr[0 .. length]; 215 216 return slice.to!T; 217 } 218 219 220 T to(T)(PyObject* value) if(is(Unqual!T == bool)) { 221 import python.raw: pyTrue; 222 return value is pyTrue; 223 } 224 225 226 T to(T)(PyObject* value) if(isAssociativeArray!T) 227 { 228 import python.raw: pyDictCheck, PyDict_Keys, PyList_Size, PyList_GetItem, PyDict_GetItem; 229 230 assert(pyDictCheck(value)); 231 232 // this enum is to get K and V whilst avoiding auto-decoding, which is why we're not using 233 // std.traits 234 enum _ = is(T == V[K], V, K); 235 alias KeyType = Unqual!K; 236 alias ValueType = Unqual!V; 237 238 ValueType[KeyType] ret; 239 240 auto keys = PyDict_Keys(value); 241 242 foreach(i; 0 .. PyList_Size(keys)) { 243 auto k = PyList_GetItem(keys, i); 244 auto v = PyDict_GetItem(value, k); 245 auto dk = k.to!KeyType; 246 auto dv = v.to!ValueType; 247 248 ret[dk] = dv; 249 } 250 251 return ret; 252 } 253 254 255 T to(T)(PyObject* value) if(isTuple!T) 256 in(pyTupleCheck(value)) 257 in(PyTuple_Size(value) == T.length) 258 do 259 { 260 import python.raw: pyTupleCheck, PyTuple_Size, PyTuple_GetItem; 261 262 T ret; 263 264 static foreach(i; 0 .. T.length) { 265 ret[i] = PyTuple_GetItem(value, i).to!(typeof(ret[i])); 266 } 267 268 return ret; 269 } 270 271 272 T to(T)(PyObject* value) if(is(Unqual!T == char) || is(Unqual!T == wchar) || is(Unqual!T == dchar)) { 273 auto str = value.to!string; 274 return str[0]; 275 } 276 277 278 T to(T)(PyObject* value) if(isDelegate!T) { 279 return value.toDlangFunction!T; 280 } 281 282 T to(T)(PyObject* value) if(isFunctionPointer!T) { 283 throw new Exception("Conversion of Python functions to D function pointers not yet implemented"); 284 } 285 286 287 private T toDlangFunction(T)(PyObject* value) 288 in(pyCallableCheck(value)) 289 do 290 { 291 import python.raw: PyObject_CallObject; 292 import python.conv.d_to_python: toPython; 293 import python.conv.python_to_d: to; 294 import std.traits: ReturnType, Parameters, Unqual; 295 import std.meta: staticMap; 296 import std.typecons: Tuple; 297 298 alias UnqualParams = staticMap!(Unqual, Parameters!T); 299 300 return (UnqualParams dArgs) { 301 Tuple!UnqualParams dArgsTuple; 302 static foreach(i; 0 .. UnqualParams.length) { 303 dArgsTuple[i] = dArgs[i]; 304 } 305 auto pyArgs = dArgsTuple.toPython; 306 auto pyResult = PyObject_CallObject(value, pyArgs); 307 static if(!is(ReturnType!T == void)) 308 return pyResult.to!(ReturnType!T); 309 }; 310 } 311 312 313 T to(T)(PyObject* value) if(is(Unqual!T == Duration)) { 314 import python.raw: PyDateTime_Delta; 315 import core.time: days, seconds, usecs; 316 317 const delta = cast(PyDateTime_Delta*) value; 318 319 return delta.days.days + delta.seconds.seconds + delta.microseconds.usecs; 320 } 321 322 323 T to(T)(PyObject* value) if(is(T == enum)) { 324 import std.traits: OriginalType; 325 return cast(T) value.to!(OriginalType!T); 326 } 327 328 329 // Usually this is needed in the presence of `const` or `immutable` 330 private auto maybeCast(Wanted, Actual)(auto ref Actual value) { 331 static if(is(Wanted == Actual)) 332 return value; 333 // the presence of `opCast` might mean this isn't 334 // always possible 335 else static if(__traits(compiles, cast(Wanted) value)) 336 return cast(Wanted) value; 337 else { 338 Wanted ret = value; 339 return ret; 340 } 341 }