1""" 2Tests for `attr._make`. 3""" 4 5from __future__ import absolute_import, division, print_function 6 7import copy 8import inspect 9import itertools 10import sys 11 12from operator import attrgetter 13 14import pytest 15 16from hypothesis import given 17from hypothesis.strategies import booleans, integers, lists, sampled_from, text 18 19import attr 20 21from attr import _config 22from attr._compat import PY2, ordered_dict 23from attr._make import ( 24 Attribute, Factory, _AndValidator, _Attributes, _ClassBuilder, 25 _CountingAttr, _transform_attrs, and_, fields, fields_dict, make_class, 26 validate 27) 28from attr.exceptions import DefaultAlreadySetError, NotAnAttrsClassError 29 30from .strategies import ( 31 gen_attr_names, list_of_attrs, simple_attrs, simple_attrs_with_metadata, 32 simple_attrs_without_metadata, simple_classes 33) 34from .utils import simple_attr 35 36 37attrs_st = simple_attrs.map(lambda c: Attribute.from_counting_attr("name", c)) 38 39 40class TestCountingAttr(object): 41 """ 42 Tests for `attr`. 43 """ 44 def test_returns_Attr(self): 45 """ 46 Returns an instance of _CountingAttr. 47 """ 48 a = attr.ib() 49 50 assert isinstance(a, _CountingAttr) 51 52 def test_validators_lists_to_wrapped_tuples(self): 53 """ 54 If a list is passed as validator, it's just converted to a tuple. 55 """ 56 def v1(_, __): 57 pass 58 59 def v2(_, __): 60 pass 61 62 a = attr.ib(validator=[v1, v2]) 63 64 assert _AndValidator((v1, v2,)) == a._validator 65 66 def test_validator_decorator_single(self): 67 """ 68 If _CountingAttr.validator is used as a decorator and there is no 69 decorator set, the decorated method is used as the validator. 70 """ 71 a = attr.ib() 72 73 @a.validator 74 def v(): 75 pass 76 77 assert v == a._validator 78 79 @pytest.mark.parametrize("wrap", [ 80 lambda v: v, 81 lambda v: [v], 82 lambda v: and_(v) 83 84 ]) 85 def test_validator_decorator(self, wrap): 86 """ 87 If _CountingAttr.validator is used as a decorator and there is already 88 a decorator set, the decorators are composed using `and_`. 89 """ 90 def v(_, __): 91 pass 92 93 a = attr.ib(validator=wrap(v)) 94 95 @a.validator 96 def v2(self, _, __): 97 pass 98 99 assert _AndValidator((v, v2,)) == a._validator 100 101 def test_default_decorator_already_set(self): 102 """ 103 Raise DefaultAlreadySetError if the decorator is used after a default 104 has been set. 105 """ 106 a = attr.ib(default=42) 107 108 with pytest.raises(DefaultAlreadySetError): 109 @a.default 110 def f(self): 111 pass 112 113 def test_default_decorator_sets(self): 114 """ 115 Decorator wraps the method in a Factory with pass_self=True and sets 116 the default. 117 """ 118 a = attr.ib() 119 120 @a.default 121 def f(self): 122 pass 123 124 assert Factory(f, True) == a._default 125 126 127class TestAttribute(object): 128 """ 129 Tests for `attr.Attribute`. 130 """ 131 def test_deprecated_convert_argument(self): 132 """ 133 Using *convert* raises a DeprecationWarning and sets the converter 134 field. 135 """ 136 def conv(v): 137 return v 138 139 with pytest.warns(DeprecationWarning) as wi: 140 a = Attribute( 141 "a", True, True, True, True, True, True, convert=conv 142 ) 143 w = wi.pop() 144 145 assert conv == a.converter 146 assert ( 147 "The `convert` argument is deprecated in favor of `converter`. " 148 "It will be removed after 2019/01.", 149 ) == w.message.args 150 assert __file__ == w.filename 151 152 def test_deprecated_convert_attribute(self): 153 """ 154 If Attribute.convert is accessed, a DeprecationWarning is raised. 155 """ 156 def conv(v): 157 return v 158 159 a = simple_attr("a", converter=conv) 160 with pytest.warns(DeprecationWarning) as wi: 161 convert = a.convert 162 w = wi.pop() 163 164 assert conv is convert is a.converter 165 assert ( 166 "The `convert` attribute is deprecated in favor of `converter`. " 167 "It will be removed after 2019/01.", 168 ) == w.message.args 169 assert __file__ == w.filename 170 171 def test_convert_converter(self): 172 """ 173 A TypeError is raised if both *convert* and *converter* are passed. 174 """ 175 with pytest.raises(RuntimeError) as ei: 176 Attribute( 177 "a", True, True, True, True, True, True, 178 convert=lambda v: v, converter=lambda v: v, 179 ) 180 181 assert ( 182 "Can't pass both `convert` and `converter`. " 183 "Please use `converter` only.", 184 ) == ei.value.args 185 186 187def make_tc(): 188 class TransformC(object): 189 z = attr.ib() 190 y = attr.ib() 191 x = attr.ib() 192 a = 42 193 return TransformC 194 195 196class TestTransformAttrs(object): 197 """ 198 Tests for `_transform_attrs`. 199 """ 200 def test_no_modifications(self): 201 """ 202 Doesn't attach __attrs_attrs__ to the class anymore. 203 """ 204 C = make_tc() 205 _transform_attrs(C, None, False) 206 207 assert None is getattr(C, "__attrs_attrs__", None) 208 209 def test_normal(self): 210 """ 211 Transforms every `_CountingAttr` and leaves others (a) be. 212 """ 213 C = make_tc() 214 attrs, _, _ = _transform_attrs(C, None, False) 215 216 assert ["z", "y", "x"] == [a.name for a in attrs] 217 218 def test_empty(self): 219 """ 220 No attributes works as expected. 221 """ 222 @attr.s 223 class C(object): 224 pass 225 226 assert _Attributes(((), [], {})) == _transform_attrs(C, None, False) 227 228 def test_transforms_to_attribute(self): 229 """ 230 All `_CountingAttr`s are transformed into `Attribute`s. 231 """ 232 C = make_tc() 233 attrs, super_attrs, _ = _transform_attrs(C, None, False) 234 235 assert [] == super_attrs 236 assert 3 == len(attrs) 237 assert all(isinstance(a, Attribute) for a in attrs) 238 239 def test_conflicting_defaults(self): 240 """ 241 Raises `ValueError` if attributes with defaults are followed by 242 mandatory attributes. 243 """ 244 class C(object): 245 x = attr.ib(default=None) 246 y = attr.ib() 247 248 with pytest.raises(ValueError) as e: 249 _transform_attrs(C, None, False) 250 assert ( 251 "No mandatory attributes allowed after an attribute with a " 252 "default value or factory. Attribute in question: Attribute" 253 "(name='y', default=NOTHING, validator=None, repr=True, " 254 "cmp=True, hash=None, init=True, metadata=mappingproxy({}), " 255 "type=None, converter=None)", 256 ) == e.value.args 257 258 def test_these(self): 259 """ 260 If these is passed, use it and ignore body and super classes. 261 """ 262 class Base(object): 263 z = attr.ib() 264 265 class C(Base): 266 y = attr.ib() 267 268 attrs, super_attrs, _ = _transform_attrs(C, {"x": attr.ib()}, False) 269 270 assert [] == super_attrs 271 assert ( 272 simple_attr("x"), 273 ) == attrs 274 275 def test_these_leave_body(self): 276 """ 277 If these is passed, no attributes are removed from the body. 278 """ 279 @attr.s(init=False, these={"x": attr.ib()}) 280 class C(object): 281 x = 5 282 283 assert 5 == C().x 284 assert "C(x=5)" == repr(C()) 285 286 def test_these_ordered(self): 287 """ 288 If these is passed ordered attrs, their order respect instead of the 289 counter. 290 """ 291 b = attr.ib(default=2) 292 a = attr.ib(default=1) 293 294 @attr.s(these=ordered_dict([("a", a), ("b", b)])) 295 class C(object): 296 pass 297 298 assert "C(a=1, b=2)" == repr(C()) 299 300 def test_multiple_inheritance(self): 301 """ 302 Order of attributes doesn't get mixed up by multiple inheritance. 303 304 See #285 305 """ 306 @attr.s 307 class A(object): 308 a1 = attr.ib(default="a1") 309 a2 = attr.ib(default="a2") 310 311 @attr.s 312 class B(A): 313 b1 = attr.ib(default="b1") 314 b2 = attr.ib(default="b2") 315 316 @attr.s 317 class C(B, A): 318 c1 = attr.ib(default="c1") 319 c2 = attr.ib(default="c2") 320 321 @attr.s 322 class D(A): 323 d1 = attr.ib(default="d1") 324 d2 = attr.ib(default="d2") 325 326 @attr.s 327 class E(C, D): 328 e1 = attr.ib(default="e1") 329 e2 = attr.ib(default="e2") 330 331 assert ( 332 "E(a1='a1', a2='a2', b1='b1', b2='b2', c1='c1', c2='c2', d1='d1', " 333 "d2='d2', e1='e1', e2='e2')" 334 ) == repr(E()) 335 336 337class TestAttributes(object): 338 """ 339 Tests for the `attrs`/`attr.s` class decorator. 340 """ 341 @pytest.mark.skipif(not PY2, reason="No old-style classes in Py3") 342 def test_catches_old_style(self): 343 """ 344 Raises TypeError on old-style classes. 345 """ 346 with pytest.raises(TypeError) as e: 347 @attr.s 348 class C: 349 pass 350 351 assert ("attrs only works with new-style classes.",) == e.value.args 352 353 def test_sets_attrs(self): 354 """ 355 Sets the `__attrs_attrs__` class attribute with a list of `Attribute`s. 356 """ 357 @attr.s 358 class C(object): 359 x = attr.ib() 360 361 assert "x" == C.__attrs_attrs__[0].name 362 assert all(isinstance(a, Attribute) for a in C.__attrs_attrs__) 363 364 def test_empty(self): 365 """ 366 No attributes, no problems. 367 """ 368 @attr.s 369 class C3(object): 370 pass 371 372 assert "C3()" == repr(C3()) 373 assert C3() == C3() 374 375 @given(attr=attrs_st, attr_name=sampled_from(Attribute.__slots__)) 376 def test_immutable(self, attr, attr_name): 377 """ 378 Attribute instances are immutable. 379 """ 380 with pytest.raises(AttributeError): 381 setattr(attr, attr_name, 1) 382 383 @pytest.mark.parametrize("method_name", [ 384 "__repr__", 385 "__eq__", 386 "__hash__", 387 "__init__", 388 ]) 389 def test_adds_all_by_default(self, method_name): 390 """ 391 If no further arguments are supplied, all add_XXX functions except 392 add_hash are applied. __hash__ is set to None. 393 """ 394 # Set the method name to a sentinel and check whether it has been 395 # overwritten afterwards. 396 sentinel = object() 397 398 class C(object): 399 x = attr.ib() 400 401 setattr(C, method_name, sentinel) 402 403 C = attr.s(C) 404 meth = getattr(C, method_name) 405 406 assert sentinel != meth 407 if method_name == "__hash__": 408 assert meth is None 409 410 @pytest.mark.parametrize("arg_name, method_name", [ 411 ("repr", "__repr__"), 412 ("cmp", "__eq__"), 413 ("hash", "__hash__"), 414 ("init", "__init__"), 415 ]) 416 def test_respects_add_arguments(self, arg_name, method_name): 417 """ 418 If a certain `add_XXX` is `False`, `__XXX__` is not added to the class. 419 """ 420 # Set the method name to a sentinel and check whether it has been 421 # overwritten afterwards. 422 sentinel = object() 423 424 am_args = { 425 "repr": True, 426 "cmp": True, 427 "hash": True, 428 "init": True 429 } 430 am_args[arg_name] = False 431 432 class C(object): 433 x = attr.ib() 434 435 setattr(C, method_name, sentinel) 436 437 C = attr.s(**am_args)(C) 438 439 assert sentinel == getattr(C, method_name) 440 441 @pytest.mark.skipif(PY2, reason="__qualname__ is PY3-only.") 442 @given(slots_outer=booleans(), slots_inner=booleans()) 443 def test_repr_qualname(self, slots_outer, slots_inner): 444 """ 445 On Python 3, the name in repr is the __qualname__. 446 """ 447 @attr.s(slots=slots_outer) 448 class C(object): 449 @attr.s(slots=slots_inner) 450 class D(object): 451 pass 452 453 assert "C.D()" == repr(C.D()) 454 assert "GC.D()" == repr(GC.D()) 455 456 @given(slots_outer=booleans(), slots_inner=booleans()) 457 def test_repr_fake_qualname(self, slots_outer, slots_inner): 458 """ 459 Setting repr_ns overrides a potentially guessed namespace. 460 """ 461 @attr.s(slots=slots_outer) 462 class C(object): 463 @attr.s(repr_ns="C", slots=slots_inner) 464 class D(object): 465 pass 466 assert "C.D()" == repr(C.D()) 467 468 @pytest.mark.skipif(PY2, reason="__qualname__ is PY3-only.") 469 @given(slots_outer=booleans(), slots_inner=booleans()) 470 def test_name_not_overridden(self, slots_outer, slots_inner): 471 """ 472 On Python 3, __name__ is different from __qualname__. 473 """ 474 @attr.s(slots=slots_outer) 475 class C(object): 476 @attr.s(slots=slots_inner) 477 class D(object): 478 pass 479 480 assert C.D.__name__ == "D" 481 assert C.D.__qualname__ == C.__qualname__ + ".D" 482 483 @given(with_validation=booleans()) 484 def test_post_init(self, with_validation, monkeypatch): 485 """ 486 Verify that __attrs_post_init__ gets called if defined. 487 """ 488 monkeypatch.setattr(_config, "_run_validators", with_validation) 489 490 @attr.s 491 class C(object): 492 x = attr.ib() 493 y = attr.ib() 494 495 def __attrs_post_init__(self2): 496 self2.z = self2.x + self2.y 497 498 c = C(x=10, y=20) 499 500 assert 30 == getattr(c, 'z', None) 501 502 def test_types(self): 503 """ 504 Sets the `Attribute.type` attr from type argument. 505 """ 506 @attr.s 507 class C(object): 508 x = attr.ib(type=int) 509 y = attr.ib(type=str) 510 z = attr.ib() 511 512 assert int is fields(C).x.type 513 assert str is fields(C).y.type 514 assert None is fields(C).z.type 515 516 @pytest.mark.parametrize("slots", [True, False]) 517 def test_clean_class(self, slots): 518 """ 519 Attribute definitions do not appear on the class body after @attr.s. 520 """ 521 @attr.s(slots=slots) 522 class C(object): 523 x = attr.ib() 524 525 x = getattr(C, "x", None) 526 527 assert not isinstance(x, _CountingAttr) 528 529 def test_factory_sugar(self): 530 """ 531 Passing factory=f is syntactic sugar for passing default=Factory(f). 532 """ 533 @attr.s 534 class C(object): 535 x = attr.ib(factory=list) 536 537 assert Factory(list) == attr.fields(C).x.default 538 539 def test_sugar_factory_mutex(self): 540 """ 541 Passing both default and factory raises ValueError. 542 """ 543 with pytest.raises(ValueError, match="mutually exclusive"): 544 @attr.s 545 class C(object): 546 x = attr.ib(factory=list, default=Factory(list)) 547 548 def test_sugar_callable(self): 549 """ 550 Factory has to be a callable to prevent people from passing Factory 551 into it. 552 """ 553 with pytest.raises(ValueError, match="must be a callable"): 554 @attr.s 555 class C(object): 556 x = attr.ib(factory=Factory(list)) 557 558 559@attr.s 560class GC(object): 561 @attr.s 562 class D(object): 563 pass 564 565 566class TestMakeClass(object): 567 """ 568 Tests for `make_class`. 569 """ 570 @pytest.mark.parametrize("ls", [ 571 list, 572 tuple 573 ]) 574 def test_simple(self, ls): 575 """ 576 Passing a list of strings creates attributes with default args. 577 """ 578 C1 = make_class("C1", ls(["a", "b"])) 579 580 @attr.s 581 class C2(object): 582 a = attr.ib() 583 b = attr.ib() 584 585 assert C1.__attrs_attrs__ == C2.__attrs_attrs__ 586 587 def test_dict(self): 588 """ 589 Passing a dict of name: _CountingAttr creates an equivalent class. 590 """ 591 C1 = make_class("C1", { 592 "a": attr.ib(default=42), 593 "b": attr.ib(default=None), 594 }) 595 596 @attr.s 597 class C2(object): 598 a = attr.ib(default=42) 599 b = attr.ib(default=None) 600 601 assert C1.__attrs_attrs__ == C2.__attrs_attrs__ 602 603 def test_attr_args(self): 604 """ 605 attributes_arguments are passed to attributes 606 """ 607 C = make_class("C", ["x"], repr=False) 608 609 assert repr(C(1)).startswith("<tests.test_make.C object at 0x") 610 611 def test_catches_wrong_attrs_type(self): 612 """ 613 Raise `TypeError` if an invalid type for attrs is passed. 614 """ 615 with pytest.raises(TypeError) as e: 616 make_class("C", object()) 617 618 assert ( 619 "attrs argument must be a dict or a list.", 620 ) == e.value.args 621 622 def test_bases(self): 623 """ 624 Parameter bases default to (object,) and subclasses correctly 625 """ 626 class D(object): 627 pass 628 629 cls = make_class("C", {}) 630 631 assert cls.__mro__[-1] == object 632 633 cls = make_class("C", {}, bases=(D,)) 634 635 assert D in cls.__mro__ 636 assert isinstance(cls(), D) 637 638 @pytest.mark.parametrize("slots", [True, False]) 639 def test_clean_class(self, slots): 640 """ 641 Attribute definitions do not appear on the class body. 642 """ 643 C = make_class("C", ["x"], slots=slots) 644 645 x = getattr(C, "x", None) 646 647 assert not isinstance(x, _CountingAttr) 648 649 def test_missing_sys_getframe(self, monkeypatch): 650 """ 651 `make_class()` does not fail when `sys._getframe()` is not available. 652 """ 653 monkeypatch.delattr(sys, '_getframe') 654 C = make_class("C", ["x"]) 655 656 assert 1 == len(C.__attrs_attrs__) 657 658 def test_make_class_ordered(self): 659 """ 660 If `make_class()` is passed ordered attrs, their order is respected 661 instead of the counter. 662 """ 663 b = attr.ib(default=2) 664 a = attr.ib(default=1) 665 666 C = attr.make_class("C", ordered_dict([("a", a), ("b", b)])) 667 668 assert "C(a=1, b=2)" == repr(C()) 669 670 671class TestFields(object): 672 """ 673 Tests for `fields`. 674 """ 675 def test_instance(self, C): 676 """ 677 Raises `TypeError` on non-classes. 678 """ 679 with pytest.raises(TypeError) as e: 680 fields(C(1, 2)) 681 682 assert "Passed object must be a class." == e.value.args[0] 683 684 def test_handler_non_attrs_class(self, C): 685 """ 686 Raises `ValueError` if passed a non-``attrs`` instance. 687 """ 688 with pytest.raises(NotAnAttrsClassError) as e: 689 fields(object) 690 691 assert ( 692 "{o!r} is not an attrs-decorated class.".format(o=object) 693 ) == e.value.args[0] 694 695 @given(simple_classes()) 696 def test_fields(self, C): 697 """ 698 Returns a list of `Attribute`a. 699 """ 700 assert all(isinstance(a, Attribute) for a in fields(C)) 701 702 @given(simple_classes()) 703 def test_fields_properties(self, C): 704 """ 705 Fields returns a tuple with properties. 706 """ 707 for attribute in fields(C): 708 assert getattr(fields(C), attribute.name) is attribute 709 710 711class TestFieldsDict(object): 712 """ 713 Tests for `fields_dict`. 714 """ 715 def test_instance(self, C): 716 """ 717 Raises `TypeError` on non-classes. 718 """ 719 with pytest.raises(TypeError) as e: 720 fields_dict(C(1, 2)) 721 722 assert "Passed object must be a class." == e.value.args[0] 723 724 def test_handler_non_attrs_class(self, C): 725 """ 726 Raises `ValueError` if passed a non-``attrs`` instance. 727 """ 728 with pytest.raises(NotAnAttrsClassError) as e: 729 fields_dict(object) 730 731 assert ( 732 "{o!r} is not an attrs-decorated class.".format(o=object) 733 ) == e.value.args[0] 734 735 @given(simple_classes()) 736 def test_fields_dict(self, C): 737 """ 738 Returns an ordered dict of ``{attribute_name: Attribute}``. 739 """ 740 d = fields_dict(C) 741 742 assert isinstance(d, ordered_dict) 743 assert list(fields(C)) == list(d.values()) 744 assert [a.name for a in fields(C)] == [field_name for field_name in d] 745 746 747class TestConverter(object): 748 """ 749 Tests for attribute conversion. 750 """ 751 def test_convert(self): 752 """ 753 Return value of converter is used as the attribute's value. 754 """ 755 C = make_class("C", { 756 "x": attr.ib(converter=lambda v: v + 1), 757 "y": attr.ib(), 758 }) 759 c = C(1, 2) 760 761 assert c.x == 2 762 assert c.y == 2 763 764 @given(integers(), booleans()) 765 def test_convert_property(self, val, init): 766 """ 767 Property tests for attributes with convert. 768 """ 769 C = make_class("C", { 770 "y": attr.ib(), 771 "x": attr.ib(init=init, default=val, converter=lambda v: v + 1), 772 }) 773 c = C(2) 774 775 assert c.x == val + 1 776 assert c.y == 2 777 778 @given(integers(), booleans()) 779 def test_convert_factory_property(self, val, init): 780 """ 781 Property tests for attributes with convert, and a factory default. 782 """ 783 C = make_class("C", ordered_dict([ 784 ("y", attr.ib()), 785 ("x", attr.ib( 786 init=init, 787 default=Factory(lambda: val), 788 converter=lambda v: v + 1 789 )), 790 ])) 791 c = C(2) 792 793 assert c.x == val + 1 794 assert c.y == 2 795 796 def test_factory_takes_self(self): 797 """ 798 If takes_self on factories is True, self is passed. 799 """ 800 C = make_class("C", { 801 "x": attr.ib( 802 default=Factory((lambda self: self), takes_self=True) 803 ), 804 }) 805 806 i = C() 807 808 assert i is i.x 809 810 def test_factory_hashable(self): 811 """ 812 Factory is hashable. 813 """ 814 assert hash(Factory(None, False)) == hash(Factory(None, False)) 815 816 def test_convert_before_validate(self): 817 """ 818 Validation happens after conversion. 819 """ 820 def validator(inst, attr, val): 821 raise RuntimeError("foo") 822 C = make_class( 823 "C", { 824 "x": attr.ib(validator=validator, converter=lambda v: 1 / 0), 825 "y": attr.ib(), 826 }) 827 with pytest.raises(ZeroDivisionError): 828 C(1, 2) 829 830 def test_frozen(self): 831 """ 832 Converters circumvent immutability. 833 """ 834 C = make_class("C", { 835 "x": attr.ib(converter=lambda v: int(v)), 836 }, frozen=True) 837 C("1") 838 839 def test_deprecated_convert(self): 840 """ 841 Using *convert* raises a DeprecationWarning and sets the converter 842 field. 843 """ 844 def conv(v): 845 return v 846 847 with pytest.warns(DeprecationWarning) as wi: 848 @attr.s 849 class C(object): 850 x = attr.ib(convert=conv) 851 852 convert = fields(C).x.convert 853 854 assert 2 == len(wi.list) 855 w = wi.pop() 856 857 assert conv == fields(C).x.converter == convert 858 assert ( 859 "The `convert` argument is deprecated in favor of `converter`. " 860 "It will be removed after 2019/01.", 861 ) == w.message.args 862 assert __file__ == w.filename 863 864 def test_convert_converter(self): 865 """ 866 A TypeError is raised if both *convert* and *converter* are passed. 867 """ 868 with pytest.raises(RuntimeError) as ei: 869 @attr.s 870 class C(object): 871 x = attr.ib(convert=lambda v: v, converter=lambda v: v) 872 873 assert ( 874 "Can't pass both `convert` and `converter`. " 875 "Please use `converter` only.", 876 ) == ei.value.args 877 878 879class TestValidate(object): 880 """ 881 Tests for `validate`. 882 """ 883 def test_success(self): 884 """ 885 If the validator succeeds, nothing gets raised. 886 """ 887 C = make_class("C", { 888 "x": attr.ib(validator=lambda *a: None), 889 "y": attr.ib() 890 }) 891 validate(C(1, 2)) 892 893 def test_propagates(self): 894 """ 895 The exception of the validator is handed through. 896 """ 897 def raiser(_, __, value): 898 if value == 42: 899 raise FloatingPointError 900 901 C = make_class("C", {"x": attr.ib(validator=raiser)}) 902 i = C(1) 903 i.x = 42 904 905 with pytest.raises(FloatingPointError): 906 validate(i) 907 908 def test_run_validators(self): 909 """ 910 Setting `_run_validators` to False prevents validators from running. 911 """ 912 _config._run_validators = False 913 obj = object() 914 915 def raiser(_, __, ___): 916 raise Exception(obj) 917 918 C = make_class("C", {"x": attr.ib(validator=raiser)}) 919 c = C(1) 920 validate(c) 921 assert 1 == c.x 922 _config._run_validators = True 923 924 with pytest.raises(Exception): 925 validate(c) 926 927 with pytest.raises(Exception) as e: 928 C(1) 929 assert (obj,) == e.value.args 930 931 def test_multiple_validators(self): 932 """ 933 If a list is passed as a validator, all of its items are treated as one 934 and must pass. 935 """ 936 def v1(_, __, value): 937 if value == 23: 938 raise TypeError("omg") 939 940 def v2(_, __, value): 941 if value == 42: 942 raise ValueError("omg") 943 944 C = make_class("C", {"x": attr.ib(validator=[v1, v2])}) 945 946 validate(C(1)) 947 948 with pytest.raises(TypeError) as e: 949 C(23) 950 951 assert "omg" == e.value.args[0] 952 953 with pytest.raises(ValueError) as e: 954 C(42) 955 956 assert "omg" == e.value.args[0] 957 958 def test_multiple_empty(self): 959 """ 960 Empty list/tuple for validator is the same as None. 961 """ 962 C1 = make_class("C", {"x": attr.ib(validator=[])}) 963 C2 = make_class("C", {"x": attr.ib(validator=None)}) 964 965 assert inspect.getsource(C1.__init__) == inspect.getsource(C2.__init__) 966 967 968# Hypothesis seems to cache values, so the lists of attributes come out 969# unsorted. 970sorted_lists_of_attrs = list_of_attrs.map( 971 lambda l: sorted(l, key=attrgetter("counter"))) 972 973 974class TestMetadata(object): 975 """ 976 Tests for metadata handling. 977 """ 978 @given(sorted_lists_of_attrs) 979 def test_metadata_present(self, list_of_attrs): 980 """ 981 Assert dictionaries are copied and present. 982 """ 983 C = make_class("C", dict(zip(gen_attr_names(), list_of_attrs))) 984 985 for hyp_attr, class_attr in zip(list_of_attrs, fields(C)): 986 if hyp_attr.metadata is None: 987 # The default is a singleton empty dict. 988 assert class_attr.metadata is not None 989 assert len(class_attr.metadata) == 0 990 else: 991 assert hyp_attr.metadata == class_attr.metadata 992 993 # Once more, just to assert getting items and iteration. 994 for k in class_attr.metadata: 995 assert hyp_attr.metadata[k] == class_attr.metadata[k] 996 assert (hyp_attr.metadata.get(k) == 997 class_attr.metadata.get(k)) 998 999 @given(simple_classes(), text()) 1000 def test_metadata_immutability(self, C, string): 1001 """ 1002 The metadata dict should be best-effort immutable. 1003 """ 1004 for a in fields(C): 1005 with pytest.raises(TypeError): 1006 a.metadata[string] = string 1007 with pytest.raises(AttributeError): 1008 a.metadata.update({string: string}) 1009 with pytest.raises(AttributeError): 1010 a.metadata.clear() 1011 with pytest.raises(AttributeError): 1012 a.metadata.setdefault(string, string) 1013 1014 for k in a.metadata: 1015 # For some reason, Python 3's MappingProxyType throws an 1016 # IndexError for deletes on a large integer key. 1017 with pytest.raises((TypeError, IndexError)): 1018 del a.metadata[k] 1019 with pytest.raises(AttributeError): 1020 a.metadata.pop(k) 1021 with pytest.raises(AttributeError): 1022 a.metadata.popitem() 1023 1024 @given(lists(simple_attrs_without_metadata, min_size=2, max_size=5)) 1025 def test_empty_metadata_singleton(self, list_of_attrs): 1026 """ 1027 All empty metadata attributes share the same empty metadata dict. 1028 """ 1029 C = make_class("C", dict(zip(gen_attr_names(), list_of_attrs))) 1030 for a in fields(C)[1:]: 1031 assert a.metadata is fields(C)[0].metadata 1032 1033 @given(lists(simple_attrs_without_metadata, min_size=2, max_size=5)) 1034 def test_empty_countingattr_metadata_independent(self, list_of_attrs): 1035 """ 1036 All empty metadata attributes are independent before ``@attr.s``. 1037 """ 1038 for x, y in itertools.combinations(list_of_attrs, 2): 1039 assert x.metadata is not y.metadata 1040 1041 @given(lists(simple_attrs_with_metadata(), min_size=2, max_size=5)) 1042 def test_not_none_metadata(self, list_of_attrs): 1043 """ 1044 Non-empty metadata attributes exist as fields after ``@attr.s``. 1045 """ 1046 C = make_class("C", dict(zip(gen_attr_names(), list_of_attrs))) 1047 1048 assert len(fields(C)) > 0 1049 1050 for cls_a, raw_a in zip(fields(C), list_of_attrs): 1051 assert cls_a.metadata != {} 1052 assert cls_a.metadata == raw_a.metadata 1053 1054 def test_metadata(self): 1055 """ 1056 If metadata that is not None is passed, it is used. 1057 1058 This is necessary for coverage because the previous test is 1059 hypothesis-based. 1060 """ 1061 md = {} 1062 a = attr.ib(metadata=md) 1063 1064 assert md is a.metadata 1065 1066 1067class TestClassBuilder(object): 1068 """ 1069 Tests for `_ClassBuilder`. 1070 """ 1071 def test_repr_str(self): 1072 """ 1073 Trying to add a `__str__` without having a `__repr__` raises a 1074 ValueError. 1075 """ 1076 with pytest.raises(ValueError) as ei: 1077 make_class("C", {}, repr=False, str=True) 1078 1079 assert ( 1080 "__str__ can only be generated if a __repr__ exists.", 1081 ) == ei.value.args 1082 1083 def test_repr(self): 1084 """ 1085 repr of builder itself makes sense. 1086 """ 1087 class C(object): 1088 pass 1089 1090 b = _ClassBuilder(C, None, True, True, False) 1091 1092 assert "<_ClassBuilder(cls=C)>" == repr(b) 1093 1094 def test_returns_self(self): 1095 """ 1096 All methods return the builder for chaining. 1097 """ 1098 class C(object): 1099 x = attr.ib() 1100 1101 b = _ClassBuilder(C, None, True, True, False) 1102 1103 cls = b.add_cmp().add_hash().add_init().add_repr("ns").add_str() \ 1104 .build_class() 1105 1106 assert "ns.C(x=1)" == repr(cls(1)) 1107 1108 @pytest.mark.parametrize("meth_name", [ 1109 "__init__", "__hash__", "__repr__", "__str__", 1110 "__eq__", "__ne__", "__lt__", "__le__", "__gt__", "__ge__", 1111 ]) 1112 def test_attaches_meta_dunders(self, meth_name): 1113 """ 1114 Generated methods have correct __module__, __name__, and __qualname__ 1115 attributes. 1116 """ 1117 @attr.s(hash=True, str=True) 1118 class C(object): 1119 def organic(self): 1120 pass 1121 1122 meth = getattr(C, meth_name) 1123 1124 assert meth_name == meth.__name__ 1125 assert C.organic.__module__ == meth.__module__ 1126 if not PY2: 1127 organic_prefix = C.organic.__qualname__.rsplit(".", 1)[0] 1128 assert organic_prefix + "." + meth_name == meth.__qualname__ 1129 1130 def test_handles_missing_meta_on_class(self): 1131 """ 1132 If the class hasn't a __module__ or __qualname__, the method hasn't 1133 either. 1134 """ 1135 class C(object): 1136 pass 1137 1138 b = _ClassBuilder( 1139 C, these=None, slots=False, frozen=False, auto_attribs=False, 1140 ) 1141 b._cls = {} # no __module__; no __qualname__ 1142 1143 def fake_meth(self): 1144 pass 1145 1146 fake_meth.__module__ = "42" 1147 fake_meth.__qualname__ = "23" 1148 1149 rv = b._add_method_dunders(fake_meth) 1150 1151 assert "42" == rv.__module__ == fake_meth.__module__ 1152 assert "23" == rv.__qualname__ == fake_meth.__qualname__ 1153 1154 def test_weakref_setstate(self): 1155 """ 1156 __weakref__ is not set on in setstate because it's not writable in 1157 slots classes. 1158 """ 1159 @attr.s(slots=True) 1160 class C(object): 1161 __weakref__ = attr.ib( 1162 init=False, hash=False, repr=False, cmp=False 1163 ) 1164 1165 assert C() == copy.deepcopy(C()) 1166