@@ -651,3 +651,68 @@ def test_check_shape(target, test_shape):
651651 with pytest .raises (ValueError ,
652652 match = error_pattern ):
653653 cbook ._check_shape (target , aardvark = data )
654+
655+
656+ def test_setattr_cm ():
657+ class A :
658+
659+ cls_level = object ()
660+ override = object ()
661+ def __init__ (self ):
662+ self .aardvark = 'aardvark'
663+ self .override = 'override'
664+ self ._p = 'p'
665+
666+ def meth (self ):
667+ ...
668+
669+ @property
670+ def prop (self ):
671+ return self ._p
672+
673+ @prop .setter
674+ def prop (self , val ):
675+ self ._p = val
676+
677+ class B (A ):
678+ ...
679+
680+ def verify_pre_post_state (obj ):
681+ # When you access a Python method the function is bound
682+ # to the object at access time so you get a new instance
683+ # of MethodType every time.
684+ #
685+ # https://docs.python.org/3/howto/descriptor.html#functions-and-methods
686+ assert obj .meth is not obj .meth
687+ # normal attribute should give you back the same
688+ # instance every time
689+ assert obj .aardvark is obj .aardvark
690+ assert a .aardvark == 'aardvark'
691+ # and our property happens to give the same instance every time
692+ assert obj .prop is obj .prop
693+ assert obj .cls_level is A .cls_level
694+ assert obj .override == 'override'
695+ assert not hasattr (obj , 'extra' )
696+ assert obj .prop == 'p'
697+
698+ a = B ()
699+ verify_pre_post_state (a )
700+ with cbook ._setattr_cm (
701+ a , prop = 'squirrel' ,
702+ aardvark = 'moose' , meth = lambda : None ,
703+ override = 'boo' , extra = 'extra'
704+ ):
705+ # because we have set a lambda, it is normal attribute access
706+ # and the same every time
707+ assert a .meth is a .meth
708+ assert a .aardvark is a .aardvark
709+ assert a .aardvark == 'moose'
710+ assert a .override == 'boo'
711+ assert a .extra == 'extra'
712+ assert a .prop == 'squirrel'
713+
714+ verify_pre_post_state (a )
715+
716+ with pytest .raises (ValueError ):
717+ with cbook ._setattr_cm (a , cls_level = 'bob' ):
718+ ...
0 commit comments