1 module python.object_;
2 
3 
4 struct PythonObject {
5     import python.raw: PyObject;
6     import std.traits: Unqual;
7 
8     private PyObject* _obj;
9 
10     this(T)(auto ref T value) if(!is(Unqual!T == PyObject*)) {
11         import python.conv.d_to_python: toPython;
12         _obj = value.toPython;
13     }
14 
15     // can only be used on Python C API calls that create a new PyObject*
16     // due to reference count issues
17     private this(PyObject* obj) {
18         _obj = obj;
19     }
20 
21     // FIXME destructor should dec ref
22 
23     PythonObject str() const {
24         return retPyObject!("PyObject_Str");
25     }
26 
27     PythonObject repr() const {
28         return retPyObject!("PyObject_Repr");
29     }
30 
31     PythonObject bytes() const {
32         return retPyObject!("PyObject_Bytes");
33     }
34 
35     PythonObject type() const {
36         return retPyObject!("PyObject_Type");
37     }
38 
39     PythonObject dir() const {
40         return retPyObject!("PyObject_Dir");
41     }
42 
43     auto hash() const {
44         return retDirect!"PyObject_Hash";
45     }
46 
47     auto len() const {
48         return retDirect!"PyObject_Length";
49     }
50     alias length = len;
51 
52     bool not() const {
53         return cast(bool) retDirect!"PyObject_Not";
54     }
55 
56     bool hasattr(in string attr) const {
57         import std..string: toStringz;
58         return cast(bool) retDirect!"PyObject_HasAttrString"(attr.toStringz);
59     }
60 
61     bool hasattr(in PythonObject attr) const {
62         return cast(bool) retDirect!"PyObject_HasAttr"(cast(PyObject*) attr._obj);
63     }
64 
65     PythonObject getattr(in string attr) const {
66         import std..string: toStringz;
67         return retPyObject!"PyObject_GetAttrString"(attr.toStringz);
68     }
69 
70     PythonObject getattr(in PythonObject attr) const {
71         return retPyObject!"PyObject_GetAttr"(cast(PyObject*) attr._obj);
72     }
73 
74     void setattr(T)(in string attr, auto ref T val) if(!is(Unqual!T == PythonObject)) {
75         setattr(attr, PythonObject(val));
76     }
77 
78     void setattr(in string attr, in PythonObject val) {
79         import python.raw: PyObject_SetAttrString;
80         import python.exception: PythonException;
81         import std..string: toStringz;
82 
83         const res = PyObject_SetAttrString(cast(PyObject*) _obj, attr.toStringz, cast(PyObject*) val._obj);
84         if(res == -1) throw new PythonException("Error setting attribute " ~ attr);
85     }
86 
87     void setattr(T)(in PythonObject attr, auto ref T val) if(!is(Unqual!T == PythonObject)) {
88         setattr(attr, PythonObject(val));
89     }
90 
91     void setattr(in PythonObject attr, in PythonObject val) {
92         import python.raw: PyObject_SetAttr;
93         import python.exception: PythonException;
94 
95         const res = PyObject_SetAttr(cast(PyObject*) _obj, cast(PyObject*) attr._obj, cast(PyObject*) val._obj);
96         if(res == -1) throw new PythonException("Error setting attribute ");
97 
98     }
99 
100     void delattr(in string attr) {
101         import python.raw: PyObject_SetAttrString;
102         import python.exception: PythonException;
103         import std..string: toStringz;
104 
105         const res = PyObject_SetAttrString(cast(PyObject*) _obj, attr.toStringz, null);
106         if(res == -1) throw new PythonException("Error setting attribute " ~ attr);
107     }
108 
109     void delattr(in PythonObject attr) {
110         import python.raw: PyObject_SetAttr;
111         import python.exception: PythonException;
112 
113         const res = PyObject_SetAttr(cast(PyObject*) _obj, cast(PyObject*) attr._obj, null);
114         if(res == -1) throw new PythonException("Error setting attribute ");
115     }
116 
117     T to(T)() const {
118         import python.conv.python_to_d: to;
119         return (cast(PyObject*) _obj).to!T;
120     }
121 
122     string toString() const {
123         import python.raw: PyObject_Str;
124         import python.conv.python_to_d: to;
125         return PyObject_Str(cast(PyObject*) _obj).to!string;
126     }
127 
128     bool callable() const {
129         return retDirect!"pyCallableCheck";
130     }
131 
132     void del() {
133         del(0, len);
134     }
135 
136     void del(in size_t idx) {
137         retDirect!"PySequence_DelItem"(idx);
138     }
139 
140     void del(in size_t i0, in size_t i1) {
141         retDirect!"PySequence_DelSlice"(i0, i1);
142     }
143 
144     void del(in string key) {
145         import std..string: toStringz;
146         retDirect!"PyObject_DelItemString"(key.toStringz);
147     }
148 
149     void del(in PythonObject key) {
150         retDirect!"PyObject_DelItem"(cast(PyObject*) key._obj);
151     }
152 
153     bool isInstance(in PythonObject klass) const {
154         return cast(bool) retDirect!"PyObject_IsInstance"(cast(PyObject*) klass._obj);
155     }
156 
157     bool isSubClass(in PythonObject klass) const {
158         return cast(bool) retDirect!"PyObject_IsSubclass"(cast(PyObject*) klass._obj);
159     }
160 
161     InputRange range() {
162         return InputRange(iter);
163     }
164 
165     PythonObject iter() const {
166         return retPyObject!"PyObject_GetIter"();
167     }
168 
169     PythonObject next() const {
170         import python.raw: PyIter_Next;
171         return PythonObject(PyIter_Next(cast(PyObject*) _obj));
172     }
173 
174     PythonObject keys() const {
175         import python.raw: pyDictCheck, PyMapping_Keys;
176         if(pyDictCheck(cast(PyObject*) _obj))
177             return retPyObject!"PyDict_Keys"();
178         else if(auto keys = PyMapping_Keys(cast(PyObject*) _obj))
179             return PythonObject(keys);
180         else
181             return getattr("keys");
182     }
183 
184     PythonObject values() const {
185         import python.raw: pyDictCheck, PyMapping_Values;
186         if(pyDictCheck(cast(PyObject*) _obj))
187             return retPyObject!"PyDict_Values"();
188         else if(auto keys = PyMapping_Values(cast(PyObject*) _obj))
189             return PythonObject(keys);
190         else
191             return getattr("values");
192     }
193 
194     PythonObject items() const {
195         import python.raw: pyDictCheck, PyMapping_Items;
196         if(pyDictCheck(cast(PyObject*) _obj))
197             return retPyObject!"PyDict_Items"();
198         else if(auto keys = PyMapping_Items(cast(PyObject*) _obj))
199             return PythonObject(keys);
200         else
201             return getattr("items");
202     }
203 
204     PythonObject copy() const {
205         import python.raw: pyDictCheck;
206         if(pyDictCheck(cast(PyObject*) _obj))
207             return retPyObject!"PyDict_Copy"();
208         else
209             return getattr("copy");
210     }
211 
212     bool merge(in PythonObject other, bool override_ = true) {
213         import python.raw: pyDictCheck;
214         import python.exception: PythonException;
215 
216         if(pyDictCheck(_obj))
217             return cast(bool) retDirect!"PyDict_Merge"(cast(PyObject*) other._obj, cast(int) override_);
218         else
219             throw new PythonException("Cannot merge a non-dict");
220     }
221 
222     int opCmp(in PythonObject other) const {
223         import python.raw: PyObject_RichCompareBool, Py_LT, Py_EQ, Py_GT;
224         import python.exception: PythonException;
225 
226         static int[int] pyOpToRet;
227         if(pyOpToRet == pyOpToRet.init)
228             pyOpToRet = [Py_LT: -1, Py_EQ: 0, Py_GT: 1];
229 
230         foreach(pyOp, ret; pyOpToRet) {
231             const pyRes = PyObject_RichCompareBool(
232                 cast(PyObject*) _obj,
233                 cast(PyObject*) other._obj,
234                 pyOp
235             );
236 
237             if(pyRes == -1)
238                 throw new PythonException("Error comparing Python objects");
239 
240             if(pyRes == 1)
241                 return ret;
242         }
243 
244         assert(0);
245     }
246 
247     PythonObject opDispatch(string identifier, A...)(auto ref A args) inout {
248         import python.exception: PythonException;
249 
250         auto value = getattr(identifier);
251 
252         if(value.callable) {
253             return value(args);
254         } else {
255             if(args.length > 0)
256                 throw new PythonException("`" ~ identifier ~ "`" ~ " is not a callable");
257             return value;
258         }
259     }
260 
261     import std.meta: anySatisfy, allSatisfy;
262     private enum isPythonObject(T) = is(Unqual!T == PythonObject);
263 
264     PythonObject opCall(A...)(auto ref A args) if(!anySatisfy!(isPythonObject, A)) {
265         import python.raw: PyTuple_New, PyTuple_SetItem, PyObject_CallObject, pyDecRef;
266         import python.conv.d_to_python: toPython;
267         import python.exception: PythonException;
268 
269         auto pyArgs = PyTuple_New(args.length);
270         scope(exit) pyDecRef(pyArgs);
271 
272         static foreach(i; 0 .. args.length) {
273             PyTuple_SetItem(pyArgs, i, args[i].toPython);
274         }
275 
276         auto ret = PyObject_CallObject(_obj, pyArgs);
277         if(ret is null) throw new PythonException("Could not call callable");
278 
279         return PythonObject(ret);
280     }
281 
282     PythonObject opCall(PythonObject args) {
283         import python.raw: PyObject_CallObject;
284         import python.exception: PythonException;
285 
286         auto ret = PyObject_CallObject(_obj, args._obj);
287         if(ret is null) throw new PythonException("Could not call callable");
288 
289         return PythonObject(ret);
290     }
291 
292     PythonObject opCall(PythonObject args, PythonObject kwargs) {
293         import python.raw: PyObject_Call;
294         import python.exception: PythonException;
295 
296         auto ret = PyObject_Call(_obj, args._obj, kwargs._obj);
297         if(ret is null) throw new PythonException("Could not call callable");
298 
299         return PythonObject(ret);
300     }
301 
302     PythonObject opIndex(in size_t idx) const {
303         return retPyObject!"PySequence_GetItem"(idx);
304     }
305 
306     PythonObject opIndex(in string key) const {
307         import python.raw: pyDictCheck;
308         import std..string: toStringz;
309 
310         const keyz = key.toStringz;
311 
312         if(pyDictCheck(cast(PyObject*) _obj))
313             return retPyObject!"PyDict_GetItemString"(keyz);
314         else
315             return retPyObject!"PyMapping_GetItemString"(keyz);
316     }
317 
318     PythonObject opIndex(in PythonObject key) const {
319         import std..string: toStringz;
320         return retPyObject!"PyObject_GetItem"(cast(PyObject*) key._obj);
321     }
322 
323     void opIndexAssign(K, V)(auto ref V _value, auto ref K _key) {
324 
325         import std.traits: isIntegral;
326 
327         static if(isPythonObject!V)
328             alias value = _value;
329         else
330             auto value = PythonObject(_value);
331 
332         static if(isIntegral!K) {
333             retPyObject!"PySequence_SetItem"(
334                 _key,
335                 cast(PyObject*) value._obj,
336             );
337         } else {
338 
339             static if(isPythonObject!K)
340                 alias key = _key;
341             else
342                 auto key = PythonObject(_key);
343 
344             retPyObject!"PyObject_SetItem"(
345                 cast(PyObject*) key._obj,
346                 cast(PyObject*) value._obj,
347             );
348         }
349     }
350 
351     PythonObject opSlice(size_t i0, size_t i1) const {
352         return retPyObject!"PySequence_GetSlice"(i0, i1);
353     }
354 
355     PythonObject opSlice() const {
356         return this[0 .. len];
357     }
358 
359     void opSliceAssign(in PythonObject val, in size_t i0, in size_t i1) {
360         retDirect!"PySequence_SetSlice"(i0, i1, cast(PyObject*) val._obj);
361     }
362 
363     void opSliceAssign(in PythonObject val) {
364         retDirect!"PySequence_SetSlice"(0, len, cast(PyObject*) val._obj);
365     }
366 
367     PythonObject opBinary(string op)(PythonObject other) if(op == "+") {
368         return retPyObject!"PyNumber_Add"(other._obj);
369     }
370 
371     PythonObject opBinary(string op)(PythonObject other) if(op == "-") {
372         return retPyObject!"PyNumber_Subtract"(other._obj);
373     }
374 
375     PythonObject opBinary(string op)(PythonObject other) if(op == "*") {
376         return retPyObject!"PyNumber_Multiply"(other._obj);
377     }
378 
379     PythonObject opBinary(string op)(int i) if(op == "*") {
380         return retPyObject!"PySequence_Repeat"(i);
381     }
382 
383     PythonObject opBinary(string op)(PythonObject other) if(op == "/") {
384         return retPyObject!"PyNumber_TrueDivide"(other._obj);
385     }
386 
387     PythonObject opBinary(string op)(PythonObject other) if(op == "%") {
388         return retPyObject!"PyNumber_Remainder"(other._obj);
389     }
390 
391     PythonObject opBinary(string op)(PythonObject other) if(op == "^^") {
392         import python.raw: pyIncRef, pyNone;
393         pyIncRef(pyNone);
394         return retPyObject!"PyNumber_Power"(other._obj, pyNone);
395     }
396 
397     PythonObject opBinary(string op)(PythonObject other) if(op == "<<") {
398         return retPyObject!"PyNumber_Lshift"(other._obj);
399     }
400 
401     PythonObject opBinary(string op)(PythonObject other) if(op == ">>") {
402         return retPyObject!"PyNumber_Rshift"(other._obj);
403     }
404 
405     PythonObject opBinary(string op)(PythonObject other) if(op == "&") {
406         return retPyObject!"PyNumber_And"(other._obj);
407     }
408 
409     PythonObject opBinary(string op)(PythonObject other) if(op == "|") {
410         return retPyObject!"PyNumber_Or"(other._obj);
411     }
412 
413     PythonObject opBinary(string op)(PythonObject other) if(op == "^") {
414         return retPyObject!"PyNumber_Xor"(other._obj);
415     }
416 
417     PythonObject opBinary(string op)(PythonObject other) if(op == "~") {
418         return retPyObject!"PySequence_Concat"(other._obj);
419     }
420 
421     PythonObject opUnary(string op)() if(op == "+") {
422         return retPyObject!"PyNumber_Positive"();
423     }
424 
425     PythonObject opUnary(string op)() if(op == "-") {
426         return retPyObject!"PyNumber_Negative"();
427     }
428 
429     PythonObject opUnary(string op)() if(op == "~") {
430         return retPyObject!"PyNumber_Invert"();
431     }
432 
433     bool opBinaryRight(string op)(in PythonObject key) if(op == "in") {
434         return cast(bool) retDirect!"PySequence_Contains"(cast(PyObject*) key._obj);
435     }
436 
437     bool opBinaryRight(string op, T)(auto ref T key) if(op == "in" && !is(Unqual!T == PythonObject)) {
438         return cast(bool) retDirect!"PySequence_Contains"(cast(PyObject*) PythonObject(key)._obj);
439     }
440 
441 private:
442 
443     PythonObject retPyObject(string funcName, A...)(auto ref A args) const {
444         import std.format: format;
445 
446         enum code = q{
447 
448             import python.exception: PythonException;
449             import python.raw: %s;
450             import std.traits: isPointer;
451 
452             auto obj = %s(cast(PyObject*) _obj, args);
453             static if(isPointer!(typeof(obj)))
454                 if(obj is null) throw new PythonException("%s returned null");
455 
456             return PythonObject(obj);
457 
458         }.format(funcName, funcName, funcName);
459 
460         mixin(code);
461     }
462 
463     auto retDirect(string cApiFunc, A...)(auto ref A args) const {
464 
465         import std.format: format;
466 
467         enum code = q{
468 
469             import python.exception: PythonException;
470             import python.raw: %s;
471 
472             const ret = %s(cast(PyObject*) _obj, args);
473             if(ret == -1)
474                 throw new PythonException("Could not call %s");
475 
476             return ret;
477 
478         }.format(cApiFunc, cApiFunc, cApiFunc);
479 
480         mixin(code);
481     }
482 }
483 
484 
485 struct InputRange {
486     import python.raw: PyObject;
487 
488     private PythonObject _iter;
489     private PythonObject _front;
490 
491     private this(PythonObject iter) {
492         import python.raw: PyObject_GetIter;
493         import python.exception: PythonException;
494 
495         _iter._obj = iter._obj;
496         if (_iter._obj is null)
497             throw new PythonException("No iterable for object");
498 
499         popFront;
500     }
501 
502     // FIXME
503     // ~this() {
504     //     import python.raw: pyDecRef;
505     //     pyDecRef(_iter);
506     // }
507 
508     void popFront() {
509         _front = _iter.next;
510     }
511 
512     bool empty() const {
513         return _front._obj is null;
514     }
515 
516     auto front() inout {
517         return _front;
518     }
519 }