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 }