Skip to content

Commit 4d18ae5

Browse files
feat: Add @with_logging decorator to automate logging setup/teardown. This solves the issue where manual logging setup/teardown was
error-prone and easy to forget for new public functions.
1 parent ef5225b commit 4d18ae5

File tree

1 file changed

+89
-0
lines changed

1 file changed

+89
-0
lines changed

datashuttle/utils/decorators.py

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from functools import wraps
2+
from typing import Optional
23

34
from datashuttle.utils.custom_exceptions import ConfigError
45
from datashuttle.utils.utils import log_and_raise_error
@@ -88,3 +89,91 @@ def wrapper(*args, **kwargs):
8889
return func(*args, **kwargs)
8990

9091
return wrapper
92+
93+
94+
def with_logging(
95+
command_name: Optional[str] = None,
96+
store_in_temp_folder: bool = False,
97+
conditional_param: Optional[str] = None,
98+
):
99+
"""Automatically handle logging for DataShuttle methods.
100+
101+
This decorator:
102+
1. Starts logging at the beginning of the function
103+
2. Captures local variables for logging
104+
3. Ensures logging is closed even if an exception occurs
105+
106+
Parameters
107+
----------
108+
command_name
109+
Name of the command for logging. If None, uses the function name
110+
with underscores replaced by hyphens.
111+
store_in_temp_folder
112+
If True, store logs in temp folder instead of project logging path.
113+
conditional_param
114+
Name of parameter that controls whether logging occurs (e.g., "log").
115+
If specified and that parameter is False, logging is skipped.
116+
117+
Examples
118+
--------
119+
@check_configs_set
120+
@with_logging()
121+
def upload_rawdata(self, ...):
122+
...
123+
124+
@with_logging(conditional_param="log")
125+
def create_folders(self, ..., log: bool = True):
126+
...
127+
128+
"""
129+
130+
def decorator(func):
131+
@wraps(func)
132+
def wrapper(*args, **kwargs):
133+
import inspect
134+
135+
from datashuttle.utils import ds_logger
136+
137+
# Get the DataShuttle instance (first argument)
138+
self = args[0]
139+
140+
# Check if logging should be skipped based on conditional parameter
141+
if conditional_param:
142+
sig = inspect.signature(func)
143+
bound_args = sig.bind(*args, **kwargs)
144+
bound_args.apply_defaults()
145+
if not bound_args.arguments.get(conditional_param, True):
146+
# Skip logging - just run the function
147+
return func(*args, **kwargs)
148+
149+
# Determine command name
150+
log_command_name = (
151+
command_name
152+
if command_name
153+
else func.__name__.replace("_", "-")
154+
)
155+
156+
# Capture local variables for logging
157+
sig = inspect.signature(func)
158+
bound_args = sig.bind(*args, **kwargs)
159+
bound_args.apply_defaults()
160+
local_vars = dict(bound_args.arguments)
161+
162+
# Start logging
163+
self._start_log(
164+
log_command_name,
165+
local_vars=local_vars,
166+
store_in_temp_folder=store_in_temp_folder,
167+
)
168+
169+
try:
170+
# Execute the function
171+
result = func(*args, **kwargs)
172+
return result
173+
finally:
174+
# Always close logging, even if exception occurs
175+
ds_logger.close_log_filehandler()
176+
177+
return wrapper
178+
179+
return decorator

0 commit comments

Comments
 (0)