Skip to content

Commit 6bb86d7

Browse files
Merge pull request #13 from hmohammad2520-org:12-enhance-logwrap
`improved documentation. and msg format improvements`
2 parents a17a26b + e73e32f commit 6bb86d7

1 file changed

Lines changed: 43 additions & 34 deletions

File tree

classmods/_decorators.py

Lines changed: 43 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import logging
1+
import logging, inspect
22
from 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

55
LOG_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
125135
def suppress_errors(fallback: type[Exception]) -> Callable[[Callable[..., R]], Callable[..., Union[R, Exception]]]: ...
126136
@overload
127137
def suppress_errors(fallback: T) -> Callable[[Callable[..., R]], Callable[..., Union[R, T]]]: ...
128-
129138
def 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

Comments
 (0)