11from typing import Any , Dict , List , Tuple , Type , Callable
22from functools import wraps
33
4- HandlerCallable = Callable [[ object , Any ] , None ]
4+ SpyCallable = Callable [... , None ]
55
66class MethodSpy :
77 # Dictionary to store spies for each (class, method) pair
@@ -10,16 +10,40 @@ class MethodSpy:
1010 def __init__ (
1111 self ,
1212 target : Type ,
13- spy_callable : HandlerCallable ,
13+ spy_callable : SpyCallable ,
1414 spy_args : tuple = (),
1515 spy_kwargs : dict = {},
1616 * ,
1717 target_method : str = '__init__' ,
1818 active : bool = True ,
1919 ) -> None :
2020 """
21- Method Spy
21+ A class to spy on method calls of a target class, triggering a handler function after the method is called.
22+
23+ The MethodSpy wraps a target method of a class and executes a spy handler whenever the method is invoked.
24+ Multiple spies can be registered for the same (class, method) pair, and all active spies will be triggered
25+ sequentially after the original method call.
26+
27+ Args:
28+ target (Type): The target class whose method will be spied on.
29+ spy_callable (SpyCallable): A callable to execute when the target method is called.
30+ Signature: spy_callable(instance: object, *spy_args, **spy_kwargs).
31+ spy_args (tuple): Positional arguments to pass to `spy_callable` (default: empty tuple).
32+ spy_kwargs (dict): Keyword arguments to pass to `spy_callable` (default: empty dict).
33+ target_method (str): Name of the method to spy on (default: '__init__').
34+ active (bool): Whether the spy is active initially (default: True).
35+
36+ Example:
37+ >>> class MyClass:
38+ ... def my_method(self):
39+ ... pass
40+ >>> def my_handler(instance):
41+ ... print(f"Spy triggered on {instance}")
42+ >>> spy = MethodSpy(MyClass, my_handler, target_method='my_method')
43+ >>> obj = MyClass()
44+ >>> obj.my_method() # Calls my_handler(obj)
2245 """
46+
2347 self ._target = target
2448 self ._spy_callable = spy_callable
2549 self ._spy_args = spy_args
@@ -35,12 +59,14 @@ def __init__(
3559
3660 self .spies_registery [key ].append (self )
3761
62+
3863 def _create_registery_key (self ) -> Tuple [Type , str ]:
3964 return (self ._target , self ._target_method )
4065
4166 def _create_original_name (self , method_name : str ) -> str :
4267 return f'__original_{ method_name } '
4368
69+
4470 def _wrap_class_method (self , target : Type , method_name : str ) -> None :
4571 """Wrap the target method to call all Spies."""
4672 original_name = self ._create_original_name (method_name )
@@ -66,6 +92,7 @@ def new_method(instance: Any, *args, **kwargs) -> Any:
6692
6793 setattr (target , method_name , new_method )
6894
95+
6996 def activate (self ) -> None :
7097 """Activate the spy."""
7198 self ._active = True
0 commit comments