1 module ut.pynih.python.object_;
2 
3 
4 import unit_threaded;
5 import python.object_;
6 import python.conv;
7 import python.exception;
8 
9 
10 @("lt")
11 unittest {
12     const three = PythonObject(3);
13     const five = PythonObject(5);
14 
15     (three < five).should == true;
16     (three < three).should == false;
17     (five < three).should == false;
18 
19     (three < PythonObject("foo"))
20         .shouldThrowWithMessage!PythonException(
21             "TypeError: '<' not supported between instances of 'int' and 'str'");
22 }
23 
24 
25 @("le")
26 unittest {
27     const three = PythonObject(3);
28     const five = PythonObject(5);
29 
30     (three <= five).should == true;
31     (three <= three).should == true;
32     (five <= three).should == false;
33 
34     (three <= PythonObject("foo"))
35         .shouldThrowWithMessage!PythonException(
36             "TypeError: '<' not supported between instances of 'int' and 'str'");
37 }
38 
39 
40 @("eq")
41 unittest {
42     const three = PythonObject(3);
43     const five = PythonObject(5);
44 
45     (three == three).should == true;
46     (three == five).should == false;
47     (five == three).should == false;
48 
49     (three == PythonObject("foo")).should == false;
50 }
51 
52 
53 
54 @("gt")
55 unittest {
56     const three = PythonObject(3);
57     const five = PythonObject(5);
58 
59     (three > five).should == false;
60     (three > three).should == false;
61     (five > three).should == true;
62 
63     (three < PythonObject("foo"))
64         .shouldThrowWithMessage!PythonException(
65             "TypeError: '<' not supported between instances of 'int' and 'str'");
66 }
67 
68 
69 @("ge")
70 unittest {
71     const three = PythonObject(3);
72     const five = PythonObject(5);
73 
74     (three >= five).should == false;
75     (three >= three).should == true;
76     (five >= three).should == true;
77 
78     (three <= PythonObject("foo"))
79         .shouldThrowWithMessage!PythonException(
80             "TypeError: '<' not supported between instances of 'int' and 'str'");
81 }
82 
83 
84 @("str")
85 unittest {
86     PythonObject(3).str.toString.should == "3";
87     PythonObject("foo").str.toString.should == "foo";
88     PythonObject([1: 2]).str.toString.should == "{1: 2}";
89 }
90 
91 
92 @("repr")
93 unittest {
94     PythonObject(3).repr.toString.should == "3";
95     PythonObject("foo").repr.toString.should == "'foo'";
96     PythonObject([1: 2]).repr.toString.should == "{1: 2}";
97 }
98 
99 
100 @("toString")
101 unittest {
102     PythonObject(3).toString.should == "3";
103     PythonObject("foo").toString.should == "foo";
104     PythonObject([1: 2]).toString.should == "{1: 2}";
105 }
106 
107 
108 @("bytes")
109 unittest {
110     int[] ints = ['f', 'o', 'o'];
111     PythonObject(ints).bytes.toString.should == "b'foo'";
112     PythonObject("oops").bytes
113         .shouldThrowWithMessage!PythonException(
114             "TypeError: cannot convert 'str' object to bytes"
115             );
116 }
117 
118 
119 @("hash")
120 unittest {
121     PythonObject(42).hash.should == 42;
122     PythonObject(77).hash.should == 77;
123     PythonObject(3.3).hash.should == 691752902764107779;
124 }
125 
126 
127 @("type")
128 unittest {
129     PythonObject(42).type.toString.should == "<class 'int'>";
130     PythonObject("foo").type.toString.should == "<class 'str'>";
131     PythonObject([1: 2]).type.toString.should == "<class 'dict'>";
132 }
133 
134 @("dir")
135 unittest {
136     const dir = PythonObject(42).dir.to!(string[]);
137     "real".should.be in dir;
138     "imag".should.be in dir;
139 }
140 
141 
142 @("len")
143 unittest {
144     PythonObject(42).len.shouldThrowWithMessage!PythonException(
145         "TypeError: object of type 'int' has no len()"
146     );
147 
148     PythonObject("foo").len.should == 3;
149     PythonObject([1, 2]).len.should == 2;
150     PythonObject([1: 2]).len.should == 1;
151 }
152 
153 @("not")
154 unittest {
155     PythonObject(false).not.should == true;
156     PythonObject(true).not.should == false;
157     PythonObject(0).not.should == true;
158     PythonObject(1).not.should == false;
159 }
160 
161 
162 @("hasattr")
163 unittest {
164     PythonObject(42).hasattr("real").should == true;
165     PythonObject(42).hasattr("foo").should == false;
166 
167     PythonObject(42).hasattr(PythonObject("real")).should == true;
168     PythonObject(42).hasattr(PythonObject("foo")).should == false;
169     PythonObject(42).hasattr(PythonObject(42)).should == false;
170 }
171 
172 
173 @("getattr")
174 unittest {
175     PythonObject(42).getattr("real").to!int.should == 42;
176     PythonObject(42).getattr("imag").to!int.should == 0;
177     PythonObject(42).getattr("foo").shouldThrowWithMessage!PythonException(
178         "AttributeError: 'int' object has no attribute 'foo'");
179 
180     PythonObject(42).getattr(PythonObject("real")).to!int.should == 42;
181     PythonObject(42).getattr(PythonObject("imag")).to!int.should == 0;
182 
183     PythonObject(42).getattr(PythonObject("foo")).shouldThrowWithMessage!PythonException(
184         "AttributeError: 'int' object has no attribute 'foo'");
185     PythonObject(42).getattr(PythonObject(42)).shouldThrowWithMessage!PythonException(
186         "TypeError: attribute name must be string, not 'int'");
187 }
188 
189 
190 @("setattr")
191 unittest {
192     import python.raw: PyRun_StringFlags, Py_file_input, Py_eval_input, PyCompilerFlags, PyDict_New;
193     import std.array: join;
194     import std.string: toStringz;
195 
196     static linesToCode(in string[] lines) {
197         return lines.join("\n").toStringz;
198     }
199 
200     const define = linesToCode(
201         [
202             `class Foo(object):`,
203             `    pass`,
204             ``,
205         ]
206     );
207 
208     PyCompilerFlags flags;
209     auto globals = PyDict_New;
210     auto locals = PyDict_New;
211 
212     auto defRes = PyRun_StringFlags(
213         define,
214         Py_file_input,
215         globals,
216         locals,
217         &flags,
218     );
219 
220     if(defRes is null) throw new PythonException("oops");
221 
222     auto evalRes = PyRun_StringFlags(
223         `Foo()`,
224         Py_eval_input,
225         globals,
226         locals,
227         &flags,
228     );
229 
230     if(evalRes is null) throw new PythonException("oops");
231     auto foo = PythonObject(evalRes);
232     "Foo object".should.be in foo.toString;
233 
234     foo.hasattr("a1").should == false;
235     foo.setattr("a1", "bar");
236     foo.hasattr("a1").should == true;
237     foo.getattr("a1").to!string.should == "bar";
238     foo.setattr("a1", PythonObject("baz"));
239     foo.getattr("a1").to!string.should == "baz";
240 
241     foo.hasattr("a2").should == false;
242     foo.setattr(PythonObject("a2"), "quux");
243     foo.hasattr("a2").should == true;
244     foo.getattr("a2").to!string.should == "quux";
245     foo.setattr(PythonObject("a2"), PythonObject("toto"));
246     foo.hasattr("a2").should == true;
247     foo.getattr("a2").to!string.should == "toto";
248     foo.setattr(PythonObject(42), "oops").shouldThrowWithMessage!PythonException(
249         "TypeError: attribute name must be string, not 'int'");
250 }
251 
252 
253 @("delattr")
254 unittest {
255     import python.raw: PyRun_StringFlags, Py_file_input, Py_eval_input, PyCompilerFlags, PyDict_New;
256     import std.array: join;
257     import std.string: toStringz;
258 
259     static linesToCode(in string[] lines) {
260         return lines.join("\n").toStringz;
261     }
262 
263     const define = linesToCode(
264         [
265             `class Foo(object):`,
266             `    pass`,
267             ``,
268         ]
269     );
270 
271     PyCompilerFlags flags;
272     auto globals = PyDict_New;
273     auto locals = PyDict_New;
274 
275     auto defRes = PyRun_StringFlags(
276         define,
277         Py_file_input,
278         globals,
279         locals,
280         &flags,
281     );
282 
283     if(defRes is null) throw new PythonException("oops");
284 
285     auto evalRes = PyRun_StringFlags(
286         `Foo()`,
287         Py_eval_input,
288         globals,
289         locals,
290         &flags,
291     );
292 
293     if(evalRes is null) throw new PythonException("oops");
294     auto foo = PythonObject(evalRes);
295     "Foo object".should.be in foo.toString;
296 
297     foo.delattr("oops").shouldThrowWithMessage!PythonException(
298         "AttributeError: oops");
299     foo.delattr(PythonObject("oopsie")).shouldThrowWithMessage!PythonException(
300         "AttributeError: oopsie");
301 
302     foo.setattr("key", "val");
303     foo.hasattr("key").should == true;
304     foo.delattr("key");
305     foo.hasattr("key").should == false;
306 
307     foo.setattr(PythonObject("key"), "value");
308     foo.hasattr("key").should == true;
309     foo.delattr(PythonObject("key"));
310     foo.hasattr("key").should == false;
311 }
312 
313 
314 @("opDispatch")
315 unittest {
316     import python.raw: PyRun_StringFlags, Py_file_input, Py_eval_input, PyCompilerFlags,
317         PyDict_New;
318     import std.array: join;
319     import std.string: toStringz;
320     import std.typecons: tuple;
321 
322     static linesToCode(in string[] lines) {
323         return lines.join("\n").toStringz;
324     }
325 
326     const define = linesToCode(
327         [
328             `class Foo(object):`,
329             `    def __init__(self, i):`,
330             `        self.i = i`,
331             `    def meth(self, a, b, c='foo', d='bar'):`,
332             `        return f"{self.i}_{a}_{b}_{c}_{d}"`,
333             ``,
334         ]
335     );
336 
337     PyCompilerFlags flags;
338     auto globals = PyDict_New;
339     auto locals = PyDict_New;
340 
341     auto defRes = PyRun_StringFlags(
342         define,
343         Py_file_input,
344         globals,
345         locals,
346         &flags,
347     );
348 
349     if(defRes is null) throw new PythonException("oops");
350 
351     auto evalRes = PyRun_StringFlags(
352         `Foo(7)`,
353         Py_eval_input,
354         globals,
355         locals,
356         &flags,
357     );
358 
359     if(evalRes is null) throw new PythonException("oops");
360     auto foo = PythonObject(evalRes);
361     "Foo object".should.be in foo.toString;
362 
363     foo.meth(1, 2).to!string.should == "7_1_2_foo_bar";
364     foo.meth(3, 4).to!string.should == "7_3_4_foo_bar";
365     foo.meth(1, 2, 0).to!string.should == "7_1_2_0_bar";
366     foo.meth(3, 4, 5, 6).to!string.should == "7_3_4_5_6";
367 
368     foo.meth(PythonObject(tuple(1, 2))).to!string.should == "7_1_2_foo_bar";
369     foo.meth(PythonObject(tuple(1, 2)), PythonObject(["c": 5, "d": 6]))
370         .to!string.should == "7_1_2_5_6";
371     foo.meth(PythonObject(tuple(1, 2)), PythonObject(["oops": 6]))
372         .shouldThrowWithMessage!PythonException(
373             "TypeError: meth() got an unexpected keyword argument 'oops'");
374 
375     foo.meth(PythonObject([1, 2])).shouldThrowWithMessage!PythonException(
376         "TypeError: argument list must be a tuple");
377 
378     foo.i.to!int.should == 7;
379     foo.i(1, 2).shouldThrowWithMessage!PythonException(
380         "`i` is not a callable");
381     foo.nope.shouldThrowWithMessage!PythonException(
382         "AttributeError: 'Foo' object has no attribute 'nope'");
383 }
384 
385 
386 @("opIndex")
387 unittest {
388     PythonObject([1, 2, 3])[1].to!int.should == 2;
389     PythonObject([1, 2, 3])[2].to!int.should == 3;
390     PythonObject([1, 2, 3])[PythonObject(2)].to!int.should == 3;
391     PythonObject([1: 2, 3: 4])[1].shouldThrowWithMessage!PythonException(
392         "TypeError: dict is not a sequence");
393 
394     PythonObject(["foo": 1, "bar": 2])["foo"].to!int.should == 1;
395     PythonObject(["foo": 1, "bar": 2])[PythonObject("bar")].to!int.should == 2;
396     PythonObject([1, 2, 3])["oops"].shouldThrowWithMessage!PythonException(
397         "TypeError: list indices must be integers or slices, not str");
398 
399 
400     auto lst = PythonObject([1, 2, 3]);
401     lst[1] = 42;
402     lst[1].to!int.should == 42;
403     lst[PythonObject(2)] = 9;
404     lst[2].to!int.should == 9;
405 
406     auto dict = PythonObject(["foo": 1, "bar": 2]);
407     dict["bar"] = 42;
408     dict["bar"].to!int.should == 42;
409     dict[PythonObject("foo")] = 77;
410     dict["foo"].to!int.should == 77;
411 }
412 
413 
414 @("del")
415 unittest {
416     auto lst = PythonObject([1, 2, 3, 4, 5]);
417     lst.del(1);
418     lst.to!(int[]).should == [1, 3, 4, 5];
419     lst.del(0, 2);
420     lst.to!(int[]).should == [4, 5];
421     lst.del();
422     int[] empty;
423     lst.to!(int[]).should == empty;
424 
425     auto dict = PythonObject(["foo": 1, "bar": 2, "baz": 3]);
426     dict.del("foo");
427     dict.to!(int[string]).should == ["bar": 2, "baz": 3];
428     dict.del(PythonObject("bar"));
429     dict.to!(int[string]).should == ["baz": 3];
430 }
431 
432 
433 @("inheritance")
434 unittest {
435     import python.raw: PyRun_StringFlags, Py_file_input, Py_eval_input, PyCompilerFlags,
436         PyDict_New;
437     import std.array: join;
438     import std.string: toStringz;
439     import std.typecons: tuple;
440 
441     static linesToCode(in string[] lines) {
442         return lines.join("\n").toStringz;
443     }
444 
445     const define = linesToCode(
446         [
447             `class Foo(object):`,
448             `    pass`,
449             `class Bar(object):`,
450             `    pass`,
451             `class BabyFoo(Foo):`,
452             `    pass`,
453             ``
454         ]
455     );
456 
457     PyCompilerFlags flags;
458     auto globals = PyDict_New;
459     auto locals = PyDict_New;
460 
461     auto defRes = PyRun_StringFlags(
462         define,
463         Py_file_input,
464         globals,
465         locals,
466         &flags,
467     );
468 
469     if(defRes is null) throw new PythonException("oops");
470 
471     auto instRes = PyRun_StringFlags(
472         `Foo()`,
473         Py_eval_input,
474         globals,
475         locals,
476         &flags,
477     );
478     if(instRes is null) throw new PythonException("oops");
479 
480     auto FooRes = PyRun_StringFlags(
481         `Foo`,
482         Py_eval_input,
483         globals,
484         locals,
485         &flags,
486     );
487     if(FooRes is null) throw new PythonException("oops");
488 
489     auto BarRes = PyRun_StringFlags(
490         `Bar`,
491         Py_eval_input,
492         globals,
493         locals,
494         &flags,
495     );
496     if(BarRes is null) throw new PythonException("oops");
497 
498     auto BabyFooRes = PyRun_StringFlags(
499         `BabyFoo`,
500         Py_eval_input,
501         globals,
502         locals,
503         &flags,
504     );
505     if(BabyFooRes is null) throw new PythonException("oops");
506 
507     const foo = PythonObject(instRes);
508     const Foo = PythonObject(FooRes);
509     const Bar = PythonObject(BarRes);
510 
511     foo.isInstance(Foo).should == true;
512     foo.isInstance(Bar).should == false;
513 
514     const BabyFoo = PythonObject(BabyFooRes);
515     BabyFoo.isSubClass(Foo).should == true;
516     BabyFoo.isSubClass(BabyFoo).should == true;
517     BabyFoo.isSubClass(Bar).should == false;
518 
519 }
520 
521 
522 @("slice")
523 unittest {
524     PythonObject([1, 2, 3, 4, 5])[1..3].to!(int[]).should == [2, 3];
525     PythonObject([1, 2, 3, 4, 5])[].to!(int[]).should == [1, 2, 3, 4, 5];
526 
527     auto lst = PythonObject([1, 2, 3, 4, 5]);
528     lst[1..3] = PythonObject([42]);
529     lst.to!(int[]).should == [1, 42, 4, 5];
530 
531     lst[] = PythonObject([77]);
532     lst.to!(int[]).should == [77];
533 
534     (lst[1..3] = PythonObject(-1)).shouldThrowWithMessage!PythonException(
535         "TypeError: can only assign an iterable");
536     PythonObject(1)[1..2].shouldThrowWithMessage!PythonException(
537         "TypeError: 'int' object is unsliceable");
538 }
539 
540 
541 @("range")
542 unittest {
543     import std.array: array;
544     import std.algorithm: map;
545 
546     PythonObject([1, 2, 3])
547         .range
548         .map!(x => x.to!int * 2)
549         .should == [2, 4, 6];
550 }
551 
552 @("opBinary.+")
553 unittest {
554     (PythonObject(2) + PythonObject(3)).to!int.should == 5;
555 }
556 
557 
558 @("opBinary.-")
559 unittest {
560     (PythonObject(5) - PythonObject(3)).to!int.should == 2;
561 }
562 
563 @("opBinary.*.int")
564 unittest {
565     (PythonObject(2) * PythonObject(3)).to!int.should == 6;
566 }
567 
568 
569 @("opBinary./")
570 unittest {
571     (PythonObject(5) / PythonObject(2)).to!double.should == 2.5;
572 }
573 
574 
575 @("opBinary.%")
576 unittest {
577     (PythonObject(5) % PythonObject(2)).to!int.should == 1;
578     (PythonObject(4) % PythonObject(2)).to!int.should == 0;
579 }
580 
581 @("opBinary.^^")
582 unittest {
583     (PythonObject(2) ^^ PythonObject(3)).to!int.should == 8;
584 }
585 
586 
587 @("opBinary.<<")
588 unittest {
589     (PythonObject(2) << PythonObject(4)).to!int.should == 32;
590 }
591 
592 
593 @("opBinary.>>")
594 unittest {
595     (PythonObject(32) >> PythonObject(4)).to!int.should == 2;
596 }
597 
598 
599 @("opBinary.&")
600 unittest {
601     (PythonObject(0) & PythonObject(0)).to!int.should == 0;
602     (PythonObject(0) & PythonObject(1)).to!int.should == 0;
603     (PythonObject(1) & PythonObject(0)).to!int.should == 0;
604     (PythonObject(1) & PythonObject(1)).to!int.should == 1;
605 }
606 
607 @("opBinary.|")
608 unittest {
609     (PythonObject(0) | PythonObject(0)).to!int.should == 0;
610     (PythonObject(0) | PythonObject(1)).to!int.should == 1;
611     (PythonObject(1) | PythonObject(0)).to!int.should == 1;
612     (PythonObject(1) | PythonObject(1)).to!int.should == 1;
613 }
614 
615 @("opBinary.^")
616 unittest {
617     (PythonObject(0) ^ PythonObject(0)).to!int.should == 0;
618     (PythonObject(0) ^ PythonObject(1)).to!int.should == 1;
619     (PythonObject(1) ^ PythonObject(0)).to!int.should == 1;
620     (PythonObject(1) ^ PythonObject(1)).to!int.should == 0;
621 }
622 
623 @("opBinary.~")
624 unittest {
625     (PythonObject([1, 2, 3]) ~ PythonObject([4, 5])).to!(int[]).should == [1, 2, 3, 4, 5];
626 }
627 
628 @("opBinary.*.seq")
629 unittest {
630     (PythonObject([1]) * 3).to!(int[]).should == [1, 1, 1];
631 }
632 
633 @("opUnary.+")
634 unittest {
635     (+PythonObject( 3)).to!int.should ==  3;
636     (+PythonObject(-3)).to!int.should == -3;
637 }
638 
639 
640 @("opUnary.-")
641 unittest {
642     (-PythonObject( 3)).to!int.should == -3;
643     (-PythonObject(-3)).to!int.should ==  3;
644 }
645 
646 
647 @("opUnary.~")
648 unittest {
649     (~PythonObject(0xf)).to!int.should == 0xfffffff0;
650 }
651 
652 
653 @("pyd.dict")
654 unittest {
655     PythonObject(42).keys.shouldThrowWithMessage!PythonException(
656         "AttributeError: 'int' object has no attribute 'keys'");
657 
658     PythonObject(42).values.shouldThrowWithMessage!PythonException(
659         "AttributeError: 'int' object has no attribute 'values'");
660 
661     PythonObject(42).items.shouldThrowWithMessage!PythonException(
662         "AttributeError: 'int' object has no attribute 'items'");
663 
664     auto g = PythonObject(["a": "b"]);
665     g.keys.to!(string[]).should == ["a"];
666     g.values.to!(string[]).should == ["b"];
667     auto items = g.items;
668     items[0][0].to!string.should == "a";
669     items[0][1].to!string.should == "b";
670 
671     g["a"].to!string.should == "b";
672     g["b"] = "c";
673     g.keys.to!(string[]).should ~ ["a", "b"];
674     g.values.to!(string[]).should ~ ["b", "c"];
675     g["c"] = PythonObject("d");
676     g.keys.to!(string[]).should ~ ["a", "b", "c"];
677     g.values.to!(string[]).should ~ ["b", "c", "d"];
678     g.del("b");
679     g.keys.to!(string[]).should ~ ["a", "c"];
680     g.values.to!(string[]).should ~ ["b", "d"];
681 
682     auto g2 = g.copy;
683     g.keys.to!(string[]).should == g2.keys.to!(string[]);
684     g2.del("c");
685     g.keys.to!(string[]).should == ["a", "c"];
686     g2.keys.to!(string[]).should == ["a"];
687 }
688 
689 
690 @("pyd.dict.merge.noovverride")
691 unittest {
692     auto dict = PythonObject(["a": "1", "b": "2"]);
693     dict.merge(PythonObject(["x": "1", "y": "2", "a": "3"]));
694     dict.to!(string[string]).should == ["a": "3", "b": "2", "x": "1", "y": "2"];
695 }
696 
697 
698 @("pyd.dict.merge.override")
699 unittest {
700     auto dict = PythonObject(["a": "1", "b": "2"]);
701     dict.merge(PythonObject(["x": "1", "y": "2", "a": "3"]), false);
702     dict.to!(string[string]).should == ["a": "1", "b": "2", "x": "1", "y": "2"];
703 }
704 
705 
706 @("pyd.dict.in")
707 unittest {
708     auto dict = PythonObject(["a": "1", "b": "2"]);
709     assert("a" in dict);
710     assert("z" !in dict);
711     assert(PythonObject("a") in dict);
712     assert(PythonObject("z") !in dict);
713 }