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) if(isPointer!T && !isUserAggregate!(PointerTarget!T) && !isFunctionPointer!T && !isDelegate!T) { 91 import std.traits: Unqual; 92 auto ret = new Unqual!(PointerTarget!T); 93 *ret = value.to!(Unqual!(PointerTarget!T)); 94 return ret; 95 } 96 97 98 T to(T)(PyObject* value) if(is(Unqual!T == DateTime)) { 99 import python.raw; 100 101 return DateTime(pyDateTimeYear(value), 102 pyDateTimeMonth(value), 103 pyDateTimeDay(value), 104 pyDateTimeHour(value), 105 pyDateTimeMinute(value), 106 pyDateTimeSecond(value)); 107 108 } 109 110 111 T to(T)(PyObject* value) if(is(Unqual!T == Date)) { 112 import python.raw; 113 114 return Date(pyDateTimeYear(value), 115 pyDateTimeMonth(value), 116 pyDateTimeDay(value)); 117 } 118 119 120 T to(T)(PyObject* value) if(isArray!T && !isSomeString!T) 121 in(pyListCheck(value) || pyTupleCheck(value)) 122 { 123 import python.raw: PyList_Size, PyList_GetItem, PyTuple_Size, PyTuple_GetItem; 124 import std.range: ElementEncodingType; 125 import std.traits: Unqual, isDynamicArray; 126 import std.exception: enforce; 127 import std.conv: text; 128 129 alias ElementType = Unqual!(ElementEncodingType!T); 130 131 // This is needed to deal with array of const or immutable 132 static if(isDynamicArray!T) 133 alias ArrayType = ElementType[]; 134 else 135 alias ArrayType = Unqual!T; 136 137 // deal with void[] here since otherwise we won't be able to iterate over it 138 static if(is(ElementType == void)) 139 alias RetType = ubyte[]; 140 else 141 alias RetType = ArrayType; 142 143 RetType ret; 144 145 static if(__traits(compiles, ret.length = 1)) { 146 const valueLength = { 147 if(pyListCheck(value)) 148 return PyList_Size(value); 149 else if(pyTupleCheck(value)) 150 return PyTuple_Size(value); 151 else 152 assert(0); 153 }(); 154 assert(valueLength >= 0, text("Invalid length ", valueLength)); 155 ret.length = valueLength; 156 } 157 158 foreach(i, ref elt; ret) { 159 auto pythonItem = { 160 if(pyListCheck(value)) 161 return PyList_GetItem(value, i); 162 else if(pyTupleCheck(value)) 163 return PyTuple_GetItem(value, i); 164 else 165 assert(0); 166 }(); 167 elt = pythonItem.to!(typeof(elt)); 168 } 169 170 return maybeCast!T(ret); 171 } 172 173 174 T to(T)(PyObject* value) if(isSomeString!T) { 175 176 import python.raw: pyUnicodeGetSize, pyUnicodeCheck, 177 pyBytesAsString, pyObjectUnicode, pyUnicodeAsUtf8String, Py_ssize_t; 178 import std.conv: to; 179 180 value = pyObjectUnicode(value); 181 182 const length = pyUnicodeGetSize(value); 183 if(length == 0) return T.init; 184 185 auto str = pyUnicodeAsUtf8String(value); 186 if(str is null) throw new Exception("Tried to convert a non-string Python value to D string"); 187 188 auto ptr = pyBytesAsString(str); 189 assert(length == 0 || ptr !is null); 190 191 auto slice = ptr[0 .. length]; 192 193 return slice.to!T; 194 } 195 196 197 T to(T)(PyObject* value) if(is(Unqual!T == bool)) { 198 import python.raw: pyTrue; 199 return value is pyTrue; 200 } 201 202 203 T to(T)(PyObject* value) if(isAssociativeArray!T) 204 { 205 import python.raw: pyDictCheck, PyDict_Keys, PyList_Size, PyList_GetItem, PyDict_GetItem; 206 207 assert(pyDictCheck(value)); 208 209 // this enum is to get K and V whilst avoiding auto-decoding, which is why we're not using 210 // std.traits 211 enum _ = is(T == V[K], V, K); 212 alias KeyType = Unqual!K; 213 alias ValueType = Unqual!V; 214 215 ValueType[KeyType] ret; 216 217 auto keys = PyDict_Keys(value); 218 219 foreach(i; 0 .. PyList_Size(keys)) { 220 auto k = PyList_GetItem(keys, i); 221 auto v = PyDict_GetItem(value, k); 222 auto dk = k.to!KeyType; 223 auto dv = v.to!ValueType; 224 225 ret[dk] = dv; 226 } 227 228 return ret; 229 } 230 231 232 T to(T)(PyObject* value) if(isTuple!T) 233 in(pyTupleCheck(value)) 234 in(PyTuple_Size(value) == T.length) 235 do 236 { 237 import python.raw: pyTupleCheck, PyTuple_Size, PyTuple_GetItem; 238 239 T ret; 240 241 static foreach(i; 0 .. T.length) { 242 ret[i] = PyTuple_GetItem(value, i).to!(typeof(ret[i])); 243 } 244 245 return ret; 246 } 247 248 249 T to(T)(PyObject* value) if(is(Unqual!T == char) || is(Unqual!T == wchar) || is(Unqual!T == dchar)) { 250 auto str = value.to!string; 251 return str[0]; 252 } 253 254 255 T to(T)(PyObject* value) if(isDelegate!T) { 256 return value.toDlangFunction!T; 257 } 258 259 T to(T)(PyObject* value) if(isFunctionPointer!T) { 260 throw new Exception("Conversion of Python functions to D function pointers not yet implemented"); 261 } 262 263 264 private T toDlangFunction(T)(PyObject* value) 265 in(pyCallableCheck(value)) 266 do 267 { 268 import python.raw: PyObject_CallObject; 269 import python.conv.d_to_python: toPython; 270 import python.conv.python_to_d: to; 271 import std.traits: ReturnType, Parameters, Unqual; 272 import std.meta: staticMap; 273 import std.typecons: Tuple; 274 275 alias UnqualParams = staticMap!(Unqual, Parameters!T); 276 277 return (UnqualParams dArgs) { 278 Tuple!UnqualParams dArgsTuple; 279 static foreach(i; 0 .. UnqualParams.length) { 280 dArgsTuple[i] = dArgs[i]; 281 } 282 auto pyArgs = dArgsTuple.toPython; 283 auto pyResult = PyObject_CallObject(value, pyArgs); 284 static if(!is(ReturnType!T == void)) 285 return pyResult.to!(ReturnType!T); 286 }; 287 } 288 289 290 T to(T)(PyObject* value) if(is(Unqual!T == Duration)) { 291 import python.raw: PyDateTime_Delta; 292 import core.time: days, seconds, usecs; 293 294 const delta = cast(PyDateTime_Delta*) value; 295 296 return delta.days.days + delta.seconds.seconds + delta.microseconds.usecs; 297 } 298 299 300 T to(T)(PyObject* value) if(is(T == enum)) { 301 import std.traits: OriginalType; 302 return cast(T) value.to!(OriginalType!T); 303 } 304 305 306 // Usually this is needed in the presence of `const` or `immutable` 307 private auto maybeCast(Wanted, Actual)(auto ref Actual value) { 308 static if(is(Wanted == Actual)) 309 return value; 310 // the presence of `opCast` might mean this isn't 311 // always possible 312 else static if(__traits(compiles, cast(Wanted) value)) 313 return cast(Wanted) value; 314 else { 315 Wanted ret = value; 316 return ret; 317 } 318 }