3131from . import invocation , signature , utils
3232from . import verification as verificationModule
3333from .mock_registry import mock_registry
34+ from .patching import Patch , patcher
3435
3536
3637__all__ = ['mock' ]
4142)
4243SUPPORTS_MARKCOROUTINEFUNCTION = hasattr (inspect , "markcoroutinefunction" )
4344
44- _MISSING_ATTRIBUTE = object ()
45-
4645_CONFIG_ASYNC_PREFIX = "async "
4746_ASYNC_BY_PROTOCOL_METHODS = {"__aenter__" , "__aexit__" , "__anext__" }
4847
@@ -324,7 +323,7 @@ def __init__(
324323 self .stubbed_invocations : deque [invocation .StubbedInvocation ] = deque ()
325324
326325 self ._original_methods : dict [str , object | None ] = {}
327- self ._methods_to_unstub : dict [str , object ] = {}
326+ self ._methods_to_unstub : dict [str , Patch ] = {}
328327 self ._signatures_store : dict [str , signature .Signature | None ] = {}
329328 self ._property_access_context : \
330329 list [tuple [str , object | None , object ]] = []
@@ -447,30 +446,13 @@ def _get_original_method_before_stub(
447446 if self .spec is None :
448447 return None , False
449448
450- try :
451- return self .spec .__dict__ [method_name ], True
452- except (AttributeError , KeyError ):
453- # If the attr is not directly in __dict__, class specs should use
454- # static lookup so inherited descriptors are preserved as
455- # descriptors (instead of triggering __get__ via getattr).
456- if inspect .isclass (self .spec ):
457- try :
458- return inspect .getattr_static (self .spec , method_name ), False
459- except AttributeError :
460- # If static lookup misses (e.g. metaclass __getattr__),
461- # fall back to dynamic lookup.
462- pass
463-
464- # For instance specs, keep dynamic getattr so existing
465- # bound-method/spying behavior stays unchanged.
466- return getattr (self .spec , method_name , None ), False
467-
468- def set_method (self , method_name : str , new_method : object ) -> None :
469- setattr (self .mocked_obj , method_name , new_method )
449+ return utils .get_original_attribute (self .spec , method_name , default = None )
470450
471451 def replace_method (
472- self , method_name : str , original_method : object | None
473- ) -> None :
452+ self ,
453+ method_name : str ,
454+ original_method : object | None ,
455+ ) -> Patch :
474456 discard_first_arg = self ._takes_implicit_self_or_cls (original_method )
475457
476458 def new_mocked_method (* args , ** kwargs ):
@@ -506,44 +488,36 @@ def new_mocked_method(*args, **kwargs):
506488 ):
507489 new_mocked_method = staticmethod (new_mocked_method )
508490
509- self .set_method (method_name , new_mocked_method )
491+ return patcher .patch_attribute (
492+ self .mocked_obj ,
493+ method_name ,
494+ new_mocked_method ,
495+ allow_unstub_by_replacement = False ,
496+ )
510497
511498 def stub (self , method_name : str ) -> None :
512499 try :
513500 self ._methods_to_unstub [method_name ]
514501 except KeyError :
515- (
516- original_method ,
517- was_in_spec
518- ) = self ._get_original_method_before_stub (method_name )
519- if was_in_spec :
520- # This indicates the original method was found directly on
521- # the spec object and should therefore be restored by unstub
522- self ._methods_to_unstub [method_name ] = original_method
523- else :
524- self ._methods_to_unstub [method_name ] = _MISSING_ATTRIBUTE
525-
502+ original_method , _ = self ._get_original_method_before_stub (method_name )
526503 self ._original_methods [method_name ] = original_method
527- self .replace_method (method_name , original_method )
504+ self ._methods_to_unstub [method_name ] = self .replace_method (
505+ method_name ,
506+ original_method ,
507+ )
528508
529509 def stub_property (self , method_name : str ) -> None :
530510 try :
531511 self ._methods_to_unstub [method_name ]
532512 except KeyError :
533- (
534- original_method ,
535- was_in_spec
536- ) = self ._get_original_method_before_stub (method_name )
537-
513+ original_method , _ = self ._get_original_method_before_stub (method_name )
538514 self ._original_methods [method_name ] = original_method
539- self .set_method (method_name , _mocked_property (self , method_name ))
540-
541- if was_in_spec :
542- # This indicates the original method was found directly on
543- # the spec object and should therefore be restored by unstub
544- self ._methods_to_unstub [method_name ] = original_method
545- else :
546- self ._methods_to_unstub [method_name ] = _MISSING_ATTRIBUTE
515+ self ._methods_to_unstub [method_name ] = patcher .patch_attribute (
516+ self .mocked_obj ,
517+ method_name ,
518+ _mocked_property (self , method_name ),
519+ allow_unstub_by_replacement = False ,
520+ )
547521
548522
549523 def forget_stubbed_invocation (
@@ -558,26 +532,18 @@ def forget_stubbed_invocation(
558532 inv .method_name == invocation .method_name
559533 for inv in self .stubbed_invocations
560534 ):
561- original_method = self ._methods_to_unstub .pop (
562- invocation .method_name
563- )
564- self .restore_method (invocation .method_name , original_method )
535+ patch = self ._methods_to_unstub .pop (invocation .method_name )
536+ patch .restore_and_unregister ()
565537
566538 if self .stubbed_invocations :
567539 return
568540
569541 mock_registry .unstub (self .mocked_obj )
570542
571- def restore_method (self , method_name : str , original_method : object ) -> None :
572- if original_method is _MISSING_ATTRIBUTE :
573- delattr (self .mocked_obj , method_name )
574- else :
575- self .set_method (method_name , original_method )
576-
577543 def unstub (self ) -> None :
578544 while self ._methods_to_unstub :
579- method_name , original_method = self ._methods_to_unstub .popitem ()
580- self . restore_method ( method_name , original_method )
545+ _ , patch = self ._methods_to_unstub .popitem ()
546+ patch . restore_and_unregister ( )
581547 self .stubbed_invocations = deque ()
582548 self .invocations = []
583549 self ._methods_marked_as_coroutine = set ()
0 commit comments