@@ -173,6 +173,7 @@ def set_fates(clotho, lachesis, atropos, father="Zues", mother="Themis"):
173173    Any ,
174174    Callable ,
175175    Collection ,
176+     ContextManager ,
176177    Dict ,
177178    Generator ,
178179    Iterable ,
@@ -823,75 +824,117 @@ def extract_text_map(carrier: Dict[str, str]) -> Optional["opentracing.SpanConte
823824# Tracing decorators 
824825
825826
826- def  trace_with_opname (opname : str ) ->  Callable [[Callable [P , R ]], Callable [P , R ]]:
827+ def  _custom_sync_async_decorator (
828+     func : Callable [P , R ],
829+     wrapping_logic : Callable [[Callable [P , R ], Any , Any ], ContextManager [None ]],
830+ ) ->  Callable [P , R ]:
827831    """ 
828-     Decorator to trace a function with a custom opname. 
829- 
830-     See the module's doc string for usage examples. 
832+     Decorates a function that is sync or async (coroutines), or that returns a Twisted 
833+     `Deferred`. The custom business logic of the decorator goes in `wrapping_logic`. 
834+ 
835+     Example usage: 
836+     ```py 
837+     # Decorator to time the function and log it out 
838+     def duration(func: Callable[P, R]) -> Callable[P, R]: 
839+         @contextlib.contextmanager 
840+         def _wrapping_logic(func: Callable[P, R], *args: P.args, **kwargs: P.kwargs) -> Generator[None, None, None]: 
841+             start_ts = time.time() 
842+             try: 
843+                 yield 
844+             finally: 
845+                 end_ts = time.time() 
846+                 duration = end_ts - start_ts 
847+                 logger.info("%s took %s seconds", func.__name__, duration) 
848+         return _custom_sync_async_decorator(func, _wrapping_logic) 
849+     ``` 
831850
851+     Args: 
852+         func: The function to be decorated 
853+         wrapping_logic: The business logic of your custom decorator. 
854+             This should be a ContextManager so you are able to run your logic 
855+             before/after the function as desired. 
832856    """ 
833857
834-     def  decorator (func : Callable [P , R ]) ->  Callable [P , R ]:
835-         if  opentracing  is  None :
836-             return  func   # type: ignore[unreachable] 
858+     if  inspect .iscoroutinefunction (func ):
837859
838-         if  inspect .iscoroutinefunction (func ):
860+         @wraps (func ) 
861+         async  def  _wrapper (* args : P .args , ** kwargs : P .kwargs ) ->  R :
862+             with  wrapping_logic (func , * args , ** kwargs ):
863+                 return  await  func (* args , ** kwargs )  # type: ignore[misc] 
839864
840-             @wraps (func ) 
841-             async  def  _trace_inner (* args : P .args , ** kwargs : P .kwargs ) ->  R :
842-                 with  start_active_span (opname ):
843-                     return  await  func (* args , ** kwargs )  # type: ignore[misc] 
865+     else :
866+         # The other case here handles both sync functions and those 
867+         # decorated with inlineDeferred. 
868+         @wraps (func ) 
869+         def  _wrapper (* args : P .args , ** kwargs : P .kwargs ) ->  R :
870+             scope  =  wrapping_logic (func , * args , ** kwargs )
871+             scope .__enter__ ()
844872
845-         else :
846-             # The other case here handles both sync functions and those 
847-             # decorated with inlineDeferred. 
848-             @wraps (func ) 
849-             def  _trace_inner (* args : P .args , ** kwargs : P .kwargs ) ->  R :
850-                 scope  =  start_active_span (opname )
851-                 scope .__enter__ ()
852- 
853-                 try :
854-                     result  =  func (* args , ** kwargs )
855-                     if  isinstance (result , defer .Deferred ):
856- 
857-                         def  call_back (result : R ) ->  R :
858-                             scope .__exit__ (None , None , None )
859-                             return  result 
860- 
861-                         def  err_back (result : R ) ->  R :
862-                             scope .__exit__ (None , None , None )
863-                             return  result 
864- 
865-                         result .addCallbacks (call_back , err_back )
866- 
867-                     else :
868-                         if  inspect .isawaitable (result ):
869-                             logger .error (
870-                                 "@trace may not have wrapped %s correctly! " 
871-                                 "The function is not async but returned a %s." ,
872-                                 func .__qualname__ ,
873-                                 type (result ).__name__ ,
874-                             )
873+             try :
874+                 result  =  func (* args , ** kwargs )
875+                 if  isinstance (result , defer .Deferred ):
876+ 
877+                     def  call_back (result : R ) ->  R :
878+                         scope .__exit__ (None , None , None )
879+                         return  result 
875880
881+                     def  err_back (result : R ) ->  R :
876882                        scope .__exit__ (None , None , None )
883+                         return  result 
884+ 
885+                     result .addCallbacks (call_back , err_back )
886+ 
887+                 else :
888+                     if  inspect .isawaitable (result ):
889+                         logger .error (
890+                             "@trace may not have wrapped %s correctly! " 
891+                             "The function is not async but returned a %s." ,
892+                             func .__qualname__ ,
893+                             type (result ).__name__ ,
894+                         )
895+ 
896+                     scope .__exit__ (None , None , None )
877897
878-                      return  result 
898+                 return  result 
879899
880-                  except  Exception  as  e :
881-                      scope .__exit__ (type (e ), None , e .__traceback__ )
882-                      raise 
900+             except  Exception  as  e :
901+                 scope .__exit__ (type (e ), None , e .__traceback__ )
902+                 raise 
883903
884-          return  _trace_inner   # type: ignore[return-value] 
904+     return  _wrapper   # type: ignore[return-value] 
885905
886-     return  decorator 
906+ 
907+ def  trace_with_opname (
908+     opname : str ,
909+     * ,
910+     tracer : Optional ["opentracing.Tracer" ] =  None ,
911+ ) ->  Callable [[Callable [P , R ]], Callable [P , R ]]:
912+     """ 
913+     Decorator to trace a function with a custom opname. 
914+     See the module's doc string for usage examples. 
915+     """ 
916+ 
917+     # type-ignore: mypy bug, see https://github.com/python/mypy/issues/12909 
918+     @contextlib .contextmanager   # type: ignore[arg-type]  
919+     def  _wrapping_logic (
920+         func : Callable [P , R ], * args : P .args , ** kwargs : P .kwargs 
921+     ) ->  Generator [None , None , None ]:
922+         with  start_active_span (opname , tracer = tracer ):
923+             yield 
924+ 
925+     def  _decorator (func : Callable [P , R ]) ->  Callable [P , R ]:
926+         if  not  opentracing :
927+             return  func 
928+ 
929+         return  _custom_sync_async_decorator (func , _wrapping_logic )
930+ 
931+     return  _decorator 
887932
888933
889934def  trace (func : Callable [P , R ]) ->  Callable [P , R ]:
890935    """ 
891936    Decorator to trace a function. 
892- 
893937    Sets the operation name to that of the function's name. 
894- 
895938    See the module's doc string for usage examples. 
896939    """ 
897940
@@ -900,7 +943,7 @@ def trace(func: Callable[P, R]) -> Callable[P, R]:
900943
901944def  tag_args (func : Callable [P , R ]) ->  Callable [P , R ]:
902945    """ 
903-     Tags  all of the args to the active span. 
946+     Decorator to tag  all of the args to the active span. 
904947
905948    Args: 
906949        func: `func` is assumed to be a method taking a `self` parameter, or a 
@@ -911,22 +954,25 @@ def tag_args(func: Callable[P, R]) -> Callable[P, R]:
911954    if  not  opentracing :
912955        return  func 
913956
914-     @wraps (func ) 
915-     def  _tag_args_inner (* args : P .args , ** kwargs : P .kwargs ) ->  R :
957+     # type-ignore: mypy bug, see https://github.com/python/mypy/issues/12909 
958+     @contextlib .contextmanager   # type: ignore[arg-type]  
959+     def  _wrapping_logic (
960+         func : Callable [P , R ], * args : P .args , ** kwargs : P .kwargs 
961+     ) ->  Generator [None , None , None ]:
916962        argspec  =  inspect .getfullargspec (func )
917963        # We use `[1:]` to skip the `self` object reference and `start=1` to 
918964        # make the index line up with `argspec.args`. 
919965        # 
920-         # FIXME: We could update this handle any type of function by ignoring the 
966+         # FIXME: We could update this to  handle any type of function by ignoring the 
921967        #   first argument only if it's named `self` or `cls`. This isn't fool-proof 
922968        #   but handles the idiomatic cases. 
923969        for  i , arg  in  enumerate (args [1 :], start = 1 ):  # type: ignore[index] 
924970            set_tag ("ARG_"  +  argspec .args [i ], str (arg ))
925971        set_tag ("args" , str (args [len (argspec .args ) :]))  # type: ignore[index] 
926972        set_tag ("kwargs" , str (kwargs ))
927-         return   func ( * args ,  ** kwargs ) 
973+         yield 
928974
929-     return  _tag_args_inner 
975+     return  _custom_sync_async_decorator ( func ,  _wrapping_logic ) 
930976
931977
932978@contextlib .contextmanager  
0 commit comments