1- import logging
1+ import logging , inspect
22from functools import wraps
3- from typing import Any , Callable , Literal , Optional , ParamSpec , Tuple , Type , TypeAlias , TypeVar , Union , overload
3+ from typing import Any , Callable , Literal , Optional , ParamSpec , Tuple , TypeAlias , TypeVar , Union , overload
44
55LOG_LEVEL : TypeAlias = Literal ['CRITICAL' , 'ERROR' , 'WARNING' , 'INFO' , 'DEBUG' , 'NOTSET' , 'DEFAULT' ]
66
@@ -14,45 +14,52 @@ def logwrap(
1414 after : Optional [Tuple [LOG_LEVEL , str ]] | str | bool = None ,
1515 ) -> Callable [[Callable [P , R ]], Callable [P , R ]]:
1616 """
17- Simple dynamic decorator to log function calls. Uses the logging module with your current project configurations.
18- Use LOG_LEVEL literal for using standard log levels.
19- The messages will get formated in proccess so you can use templating for better logging.
17+ A simple dynamic decorator to log function calls using the `logging` module with your current project configurations.
18+ Use the `LOG_LEVEL` literal to specify standard log levels.
2019
21- (`func`: Function Name, `args`: Arguments Tuple, `kwargs`: Keyword Arguments Dict, `e`: The Exception if Exists)
20+ LOG_LEVEL = ['CRITICAL', 'ERROR', 'WARNING', 'INFO', 'DEBUG', 'NOTSET', 'DEFAULT']
2221
23- Passing bool will use default levels and messages.(
24- Before: 'DEBUG - Calling {func} - args:{args}, kwargs:{kwargs}'
25- After: 'INFO - Function {func} ended'
26- On Exception: 'ERROR - Error on {func}: {e}')
22+ - The messages are formatted dynamically using templating.
23+ - Available variables:
24+ - `func`: Function name
25+ - `args`: Tuple of positional arguments
26+ - `kwargs`: Dictionary of keyword arguments
27+ - `e`: Exception object (if any)
28+ - `result`: Return value of the function
2729
28- **Warning**: If options are negative, it will not log the option.
29- **Warning**: Providing wrong log level won't raise any exception but will default to specified defaults with DEFAULT level.
30+ - If `True` is passed to an option, it will use the default log level and message:
31+ - Before: DEBUG - Calling {func} - args={args}, kwargs={kwargs}
32+ - After: INFO - Function {func} ended. result={result}
33+ - On Exception: ERROR - Error in {func}: {e}
34+
35+ - **Warning**: If an option is set to a negative value, logging for that stage will be skipped.
36+ - **Warning**: If an invalid log level is provided, no exception will be raised. Instead, the decorator will fall back to the default log level.
3037
3138 Args:
32- before: Tuple of log level and message to log before function call or string with default of `DEBUG` level .
33- on_exception: Tuple of log level and message to log exception of function call or string with default of `ERROR` level .
34- after: Tuple of log level and message to log after function call or string with default of `INFO` level .
39+ before: A tuple of log level and message to log * before* the function call, or `True` to use the default .
40+ on_exception: A tuple of log level and message to log *on exception*, or `True` to use the default .
41+ after: A tuple of log level and message to log * after* the function call, or `True` to use the default .
3542
3643 Examples:
37- >>> @logwrap(before=('INFO', '{func} starting, args={args} kwargs={kwargs}'), after=('INFO', '{func} ended'))
44+ >>> @logwrap(before=('INFO', '{func} starting, args={args}, kwargs={kwargs}'), after=('INFO', '{func} ended'))
3845 ... def my_func(my_arg, my_kwarg=None):
3946 ... ...
4047 ... my_func('hello', my_kwarg=123)
41- Info - my_func Starting , args=('hello',) , kwargs={'my_kwarg': 123}
42- Info - my_func Ended
48+ Info - my_func starting , args={'my_arg', 123} , kwargs={'my_arg': 'hello', 'my_kwarg': 123}
49+ Info - my_func ended
4350
4451 >>> @logwrap(before=True, after=True)
4552 ... def my_new_func():
4653 ... ...
4754 ... my_new_func()
48- Debug - Calling my_new_func - args:(), kwargs: {}
49- Info - Function my_new_func ended
55+ Debug - Calling my_new_func - kwargs= {}
56+ Info - Function my_new_func ended. result=None
5057
5158 >>> @logwrap(on_exception=True)
5259 ... def error_func():
5360 ... raise Exception('My exception msg')
5461 ... error_func()
55- Error - Error on error_func: My exception msg
62+ Error - Error in error_func: My exception msg
5663 """
5764 def normalize (
5865 default_level : LOG_LEVEL ,
@@ -71,7 +78,7 @@ def normalize(
7178 Returns:
7279 (Tuple[LOG_LEVEL, str] | None): Normalized output for logging wraper
7380 """
74- if isinstance (option , bool ) and option is True :
81+ if isinstance (option , bool ) and option :
7582 return (default_level , default_msg )
7683
7784 elif isinstance (option , str ):
@@ -80,23 +87,26 @@ def normalize(
8087 elif isinstance (option , tuple ):
8188 return option
8289
83- elif not option or option is None :
84- return None
90+ before = normalize ('DEBUG' , 'Calling {func} - kwargs={kwargs}' , before )
91+ after = normalize ('INFO' , 'Function {func} ended. result={result}' , after )
92+ on_exception = normalize ('ERROR' , 'Error in {func}: {e}' , on_exception )
8593
86- before = normalize ( 'DEBUG' , 'Calling {func} - args:{args}, kwargs:{kwargs}' , before )
87- after = normalize ( 'INFO' , 'Function {func} ended' , after )
88- on_exception = normalize ( 'ERROR' , 'Error on {func}: {e}' , on_exception )
94+ def decorator ( func : Callable [ P , R ]) -> Callable [ P , R ]:
95+ sig = inspect . signature ( func )
96+ logger = logging . getLogger ( func . __module__ )
8997
90- def decorator (func : Callable ) -> Callable :
9198 @wraps (func )
9299 def wrapper (* args : P .args , ** kwargs : P .kwargs ) -> R :
93- logger = logging .getLogger (func .__module__ )
100+ bound_args = sig .bind (* args , ** kwargs )
101+ bound_args .apply_defaults ()
102+
94103 func_name = func .__name__
104+ unified_kwargs = dict (bound_args .arguments )
95105
96106 fmt_context = {
97107 'func' : func_name ,
98- 'args' : args ,
99- 'kwargs' : kwargs ,
108+ 'args' : tuple ( unified_kwargs . values ()) ,
109+ 'kwargs' : unified_kwargs ,
100110 }
101111
102112 if before :
@@ -105,6 +115,7 @@ def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
105115
106116 try :
107117 result = func (* args , ** kwargs )
118+ fmt_context ['result' ] = result
108119 except Exception as e :
109120 if on_exception :
110121 level , msg = on_exception
@@ -120,12 +131,10 @@ def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
120131 return wrapper
121132 return decorator
122133
123-
124134@overload
125135def suppress_errors (fallback : type [Exception ]) -> Callable [[Callable [..., R ]], Callable [..., Union [R , Exception ]]]: ...
126136@overload
127137def suppress_errors (fallback : T ) -> Callable [[Callable [..., R ]], Callable [..., Union [R , T ]]]: ...
128-
129138def suppress_errors (fallback : Any ) -> Callable [[Callable [..., R ]], Callable [..., Union [R , Any ]]]:
130139 """
131140 A decorator that suppresses exceptions raised by the wrapped function and returns
@@ -167,4 +176,4 @@ def wrapper(*args: Any, **kwargs: Any) -> Union[R, Any]:
167176 return e
168177 return fallback
169178 return wrapper
170- return decorator
179+ return decorator
0 commit comments