python-wrap 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of python-wrap might be problematic. Click here for more details.
- python_wrap/__init__.py +56 -0
- python_wrap/decorators.py +702 -0
- python_wrap-0.1.0.dist-info/LICENSE +21 -0
- python_wrap-0.1.0.dist-info/METADATA +189 -0
- python_wrap-0.1.0.dist-info/RECORD +6 -0
- python_wrap-0.1.0.dist-info/WHEEL +4 -0
python_wrap/__init__.py
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""
|
|
2
|
+
PythonWrap - A comprehensive collection of Python decorators and utility functions.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from .decorators import (
|
|
6
|
+
timing,
|
|
7
|
+
retry,
|
|
8
|
+
memoize,
|
|
9
|
+
log_args,
|
|
10
|
+
log_return,
|
|
11
|
+
validate_args,
|
|
12
|
+
cache,
|
|
13
|
+
once,
|
|
14
|
+
deprecate,
|
|
15
|
+
check_type,
|
|
16
|
+
retry_on_exception,
|
|
17
|
+
measure_memory,
|
|
18
|
+
profile,
|
|
19
|
+
rate_limit,
|
|
20
|
+
mock_data,
|
|
21
|
+
benchmark,
|
|
22
|
+
run_in_thread,
|
|
23
|
+
no_debug,
|
|
24
|
+
transactional,
|
|
25
|
+
revert_on_failure,
|
|
26
|
+
audit,
|
|
27
|
+
trace,
|
|
28
|
+
timeout,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
__version__ = "0.1.0"
|
|
32
|
+
__all__ = [
|
|
33
|
+
"timing",
|
|
34
|
+
"retry",
|
|
35
|
+
"memoize",
|
|
36
|
+
"log_args",
|
|
37
|
+
"log_return",
|
|
38
|
+
"validate_args",
|
|
39
|
+
"cache",
|
|
40
|
+
"once",
|
|
41
|
+
"deprecate",
|
|
42
|
+
"check_type",
|
|
43
|
+
"retry_on_exception",
|
|
44
|
+
"measure_memory",
|
|
45
|
+
"profile",
|
|
46
|
+
"rate_limit",
|
|
47
|
+
"mock_data",
|
|
48
|
+
"benchmark",
|
|
49
|
+
"run_in_thread",
|
|
50
|
+
"no_debug",
|
|
51
|
+
"transactional",
|
|
52
|
+
"revert_on_failure",
|
|
53
|
+
"audit",
|
|
54
|
+
"trace",
|
|
55
|
+
"timeout",
|
|
56
|
+
]
|
|
@@ -0,0 +1,702 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Core decorators module containing all decorator implementations.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import time
|
|
6
|
+
import logging
|
|
7
|
+
import functools
|
|
8
|
+
import threading
|
|
9
|
+
import warnings
|
|
10
|
+
import inspect
|
|
11
|
+
import traceback
|
|
12
|
+
from typing import Any, Callable, Dict, List, Type, Union
|
|
13
|
+
from datetime import datetime, timedelta
|
|
14
|
+
import threading
|
|
15
|
+
import queue
|
|
16
|
+
import signal
|
|
17
|
+
import resource
|
|
18
|
+
import cProfile
|
|
19
|
+
import pstats
|
|
20
|
+
import io
|
|
21
|
+
from contextlib import contextmanager
|
|
22
|
+
|
|
23
|
+
# Configure logging
|
|
24
|
+
logging.basicConfig(level=logging.INFO)
|
|
25
|
+
logger = logging.getLogger(__name__)
|
|
26
|
+
|
|
27
|
+
def _timing_impl(func, *args, **kwargs):
|
|
28
|
+
"""Implementation of timing functionality."""
|
|
29
|
+
start_time = time.time()
|
|
30
|
+
result = func(*args, **kwargs)
|
|
31
|
+
end_time = time.time()
|
|
32
|
+
logger.info(f"{func.__name__} took {end_time - start_time:.4f} seconds to execute")
|
|
33
|
+
return result
|
|
34
|
+
|
|
35
|
+
def timing(func: Union[Callable, None] = None) -> Callable:
|
|
36
|
+
"""
|
|
37
|
+
Measures and logs the execution time of a function.
|
|
38
|
+
Can be used as a decorator or function:
|
|
39
|
+
|
|
40
|
+
@timing
|
|
41
|
+
def func(): pass
|
|
42
|
+
|
|
43
|
+
# or
|
|
44
|
+
result = timing(func)()
|
|
45
|
+
"""
|
|
46
|
+
if func is None:
|
|
47
|
+
return timing
|
|
48
|
+
|
|
49
|
+
@functools.wraps(func)
|
|
50
|
+
def wrapper(*args, **kwargs):
|
|
51
|
+
return _timing_impl(func, *args, **kwargs)
|
|
52
|
+
return wrapper
|
|
53
|
+
|
|
54
|
+
def _retry_impl(func, max_attempts, delay, *args, **kwargs):
|
|
55
|
+
"""Implementation of retry functionality."""
|
|
56
|
+
attempts = 0
|
|
57
|
+
while attempts < max_attempts:
|
|
58
|
+
try:
|
|
59
|
+
return func(*args, **kwargs)
|
|
60
|
+
except Exception as e:
|
|
61
|
+
attempts += 1
|
|
62
|
+
if attempts == max_attempts:
|
|
63
|
+
raise
|
|
64
|
+
logger.warning(f"Attempt {attempts} failed, retrying in {delay} seconds...")
|
|
65
|
+
time.sleep(delay)
|
|
66
|
+
return None
|
|
67
|
+
|
|
68
|
+
def retry(func: Union[Callable, None] = None, *, max_attempts: int = 3, delay: float = 1.0) -> Callable:
|
|
69
|
+
"""
|
|
70
|
+
Retries a function in case of failure.
|
|
71
|
+
Can be used as a decorator or function:
|
|
72
|
+
|
|
73
|
+
@retry(max_attempts=3)
|
|
74
|
+
def func(): pass
|
|
75
|
+
|
|
76
|
+
# or
|
|
77
|
+
result = retry(func, max_attempts=3)()
|
|
78
|
+
"""
|
|
79
|
+
if func is None:
|
|
80
|
+
return lambda f: retry(f, max_attempts=max_attempts, delay=delay)
|
|
81
|
+
|
|
82
|
+
@functools.wraps(func)
|
|
83
|
+
def wrapper(*args, **kwargs):
|
|
84
|
+
return _retry_impl(func, max_attempts, delay, *args, **kwargs)
|
|
85
|
+
return wrapper
|
|
86
|
+
|
|
87
|
+
def _memoize_impl(func, cache, *args, **kwargs):
|
|
88
|
+
"""Implementation of memoize functionality."""
|
|
89
|
+
key = str(args) + str(kwargs)
|
|
90
|
+
if key not in cache:
|
|
91
|
+
cache[key] = func(*args, **kwargs)
|
|
92
|
+
return cache[key]
|
|
93
|
+
|
|
94
|
+
def memoize(func: Union[Callable, None] = None) -> Callable:
|
|
95
|
+
"""
|
|
96
|
+
Caches the result of a function to prevent redundant calculations.
|
|
97
|
+
Can be used as a decorator or function:
|
|
98
|
+
|
|
99
|
+
@memoize
|
|
100
|
+
def func(): pass
|
|
101
|
+
|
|
102
|
+
# or
|
|
103
|
+
result = memoize(func)()
|
|
104
|
+
"""
|
|
105
|
+
if func is None:
|
|
106
|
+
return memoize
|
|
107
|
+
|
|
108
|
+
cache = {}
|
|
109
|
+
|
|
110
|
+
@functools.wraps(func)
|
|
111
|
+
def wrapper(*args, **kwargs):
|
|
112
|
+
return _memoize_impl(func, cache, *args, **kwargs)
|
|
113
|
+
return wrapper
|
|
114
|
+
|
|
115
|
+
def _log_args_impl(func, *args, **kwargs):
|
|
116
|
+
"""Implementation of log_args functionality."""
|
|
117
|
+
logger.info(f"Calling {func.__name__} with args: {args}, kwargs: {kwargs}")
|
|
118
|
+
return func(*args, **kwargs)
|
|
119
|
+
|
|
120
|
+
def log_args(func: Union[Callable, None] = None) -> Callable:
|
|
121
|
+
"""
|
|
122
|
+
Logs the arguments passed to a function.
|
|
123
|
+
Can be used as a decorator or function:
|
|
124
|
+
|
|
125
|
+
@log_args
|
|
126
|
+
def func(): pass
|
|
127
|
+
|
|
128
|
+
# or
|
|
129
|
+
result = log_args(func)()
|
|
130
|
+
"""
|
|
131
|
+
if func is None:
|
|
132
|
+
return log_args
|
|
133
|
+
|
|
134
|
+
@functools.wraps(func)
|
|
135
|
+
def wrapper(*args, **kwargs):
|
|
136
|
+
return _log_args_impl(func, *args, **kwargs)
|
|
137
|
+
return wrapper
|
|
138
|
+
|
|
139
|
+
def _log_return_impl(func, *args, **kwargs):
|
|
140
|
+
"""Implementation of log_return functionality."""
|
|
141
|
+
result = func(*args, **kwargs)
|
|
142
|
+
logger.info(f"{func.__name__} returned: {result}")
|
|
143
|
+
return result
|
|
144
|
+
|
|
145
|
+
def log_return(func: Union[Callable, None] = None) -> Callable:
|
|
146
|
+
"""
|
|
147
|
+
Logs the return value of a function after execution.
|
|
148
|
+
Can be used as a decorator or function:
|
|
149
|
+
|
|
150
|
+
@log_return
|
|
151
|
+
def func(): pass
|
|
152
|
+
|
|
153
|
+
# or
|
|
154
|
+
result = log_return(func)()
|
|
155
|
+
"""
|
|
156
|
+
if func is None:
|
|
157
|
+
return log_return
|
|
158
|
+
|
|
159
|
+
@functools.wraps(func)
|
|
160
|
+
def wrapper(*args, **kwargs):
|
|
161
|
+
return _log_return_impl(func, *args, **kwargs)
|
|
162
|
+
return wrapper
|
|
163
|
+
|
|
164
|
+
def _validate_args_impl(func, validators, *args, **kwargs):
|
|
165
|
+
"""Implementation of validate_args functionality."""
|
|
166
|
+
sig = inspect.signature(func)
|
|
167
|
+
bound_args = sig.bind(*args, **kwargs)
|
|
168
|
+
for param_name, validator in validators.items():
|
|
169
|
+
if param_name in bound_args.arguments:
|
|
170
|
+
value = bound_args.arguments[param_name]
|
|
171
|
+
if not validator(value):
|
|
172
|
+
raise ValueError(f"Invalid value for {param_name}: {value}")
|
|
173
|
+
return func(*args, **kwargs)
|
|
174
|
+
|
|
175
|
+
def validate_args(**validators):
|
|
176
|
+
"""
|
|
177
|
+
Validates the arguments passed to a function based on custom rules.
|
|
178
|
+
Can be used as a decorator or function:
|
|
179
|
+
|
|
180
|
+
@validate_args(x=lambda x: x > 0)
|
|
181
|
+
def func(): pass
|
|
182
|
+
|
|
183
|
+
# or
|
|
184
|
+
result = validate_args(x=lambda x: x > 0)(func)()
|
|
185
|
+
"""
|
|
186
|
+
def decorator(func):
|
|
187
|
+
@functools.wraps(func)
|
|
188
|
+
def wrapper(*args, **kwargs):
|
|
189
|
+
return _validate_args_impl(func, validators, *args, **kwargs)
|
|
190
|
+
return wrapper
|
|
191
|
+
return decorator
|
|
192
|
+
|
|
193
|
+
def _cache_impl(func, duration, cache_data, *args, **kwargs):
|
|
194
|
+
"""Implementation of cache functionality."""
|
|
195
|
+
key = str(args) + str(kwargs)
|
|
196
|
+
now = datetime.now()
|
|
197
|
+
if key in cache_data:
|
|
198
|
+
result, timestamp = cache_data[key]
|
|
199
|
+
if now - timestamp < duration:
|
|
200
|
+
return result
|
|
201
|
+
result = func(*args, **kwargs)
|
|
202
|
+
cache_data[key] = (result, now)
|
|
203
|
+
return result
|
|
204
|
+
|
|
205
|
+
def cache(duration: timedelta):
|
|
206
|
+
"""
|
|
207
|
+
Caches the result of a function call for a specified duration.
|
|
208
|
+
Can be used as a decorator or function:
|
|
209
|
+
|
|
210
|
+
@cache(duration=timedelta(minutes=5))
|
|
211
|
+
def func(): pass
|
|
212
|
+
|
|
213
|
+
# or
|
|
214
|
+
result = cache(duration=timedelta(minutes=5))(func)()
|
|
215
|
+
"""
|
|
216
|
+
cache_data = {}
|
|
217
|
+
|
|
218
|
+
def decorator(func):
|
|
219
|
+
@functools.wraps(func)
|
|
220
|
+
def wrapper(*args, **kwargs):
|
|
221
|
+
return _cache_impl(func, duration, cache_data, *args, **kwargs)
|
|
222
|
+
return wrapper
|
|
223
|
+
return decorator
|
|
224
|
+
|
|
225
|
+
def once(func: Union[Callable, None] = None) -> Callable:
|
|
226
|
+
"""
|
|
227
|
+
Ensures the function runs only once, even across multiple calls.
|
|
228
|
+
Can be used as a decorator or function:
|
|
229
|
+
|
|
230
|
+
@once
|
|
231
|
+
def func(): pass
|
|
232
|
+
|
|
233
|
+
# or
|
|
234
|
+
result = once(func)()
|
|
235
|
+
"""
|
|
236
|
+
if func is None:
|
|
237
|
+
return once
|
|
238
|
+
|
|
239
|
+
# Store state in a dictionary to maintain closure state
|
|
240
|
+
state = {'has_run': False, 'result': None}
|
|
241
|
+
|
|
242
|
+
@functools.wraps(func)
|
|
243
|
+
def wrapper(*args, **kwargs):
|
|
244
|
+
if not state['has_run']:
|
|
245
|
+
state['result'] = func(*args, **kwargs)
|
|
246
|
+
state['has_run'] = True
|
|
247
|
+
return state['result']
|
|
248
|
+
|
|
249
|
+
return wrapper
|
|
250
|
+
|
|
251
|
+
def _deprecate_impl(func, message, *args, **kwargs):
|
|
252
|
+
"""Implementation of deprecate functionality."""
|
|
253
|
+
warn_msg = message or f"{func.__name__} is deprecated and will be removed in a future version."
|
|
254
|
+
warnings.warn(warn_msg, DeprecationWarning, stacklevel=2)
|
|
255
|
+
return func(*args, **kwargs)
|
|
256
|
+
|
|
257
|
+
def deprecate(message: str = None):
|
|
258
|
+
"""
|
|
259
|
+
Marks a function as deprecated and provides a warning when used.
|
|
260
|
+
Can be used as a decorator or function:
|
|
261
|
+
|
|
262
|
+
@deprecate("Use new_func instead")
|
|
263
|
+
def func(): pass
|
|
264
|
+
|
|
265
|
+
# or
|
|
266
|
+
result = deprecate("Use new_func instead")(func)()
|
|
267
|
+
"""
|
|
268
|
+
def decorator(func):
|
|
269
|
+
@functools.wraps(func)
|
|
270
|
+
def wrapper(*args, **kwargs):
|
|
271
|
+
return _deprecate_impl(func, message, *args, **kwargs)
|
|
272
|
+
return wrapper
|
|
273
|
+
return decorator
|
|
274
|
+
|
|
275
|
+
def _check_type_impl(func, type_hints, *args, **kwargs):
|
|
276
|
+
"""Implementation of check_type functionality."""
|
|
277
|
+
sig = inspect.signature(func)
|
|
278
|
+
bound_args = sig.bind(*args, **kwargs)
|
|
279
|
+
for param_name, expected_type in type_hints.items():
|
|
280
|
+
if param_name in bound_args.arguments:
|
|
281
|
+
value = bound_args.arguments[param_name]
|
|
282
|
+
if not isinstance(value, expected_type):
|
|
283
|
+
raise TypeError(f"Parameter {param_name} must be of type {expected_type}")
|
|
284
|
+
return func(*args, **kwargs)
|
|
285
|
+
|
|
286
|
+
def check_type(**type_hints):
|
|
287
|
+
"""
|
|
288
|
+
Verifies that function arguments are of the correct type.
|
|
289
|
+
Can be used as a decorator or function:
|
|
290
|
+
|
|
291
|
+
@check_type(x=int, y=str)
|
|
292
|
+
def func(): pass
|
|
293
|
+
|
|
294
|
+
# or
|
|
295
|
+
result = check_type(x=int, y=str)(func)()
|
|
296
|
+
"""
|
|
297
|
+
def decorator(func):
|
|
298
|
+
@functools.wraps(func)
|
|
299
|
+
def wrapper(*args, **kwargs):
|
|
300
|
+
return _check_type_impl(func, type_hints, *args, **kwargs)
|
|
301
|
+
return wrapper
|
|
302
|
+
return decorator
|
|
303
|
+
|
|
304
|
+
def _retry_on_exception_impl(func, exceptions, *args, **kwargs):
|
|
305
|
+
"""Implementation of retry_on_exception functionality."""
|
|
306
|
+
while True:
|
|
307
|
+
try:
|
|
308
|
+
return func(*args, **kwargs)
|
|
309
|
+
except exceptions as e:
|
|
310
|
+
logger.warning(f"Caught exception {type(e).__name__}, retrying...")
|
|
311
|
+
continue
|
|
312
|
+
except Exception as e:
|
|
313
|
+
raise
|
|
314
|
+
|
|
315
|
+
def retry_on_exception(*exceptions):
|
|
316
|
+
"""
|
|
317
|
+
Retries the function when specific exceptions are raised.
|
|
318
|
+
Can be used as a decorator or function:
|
|
319
|
+
|
|
320
|
+
@retry_on_exception(ValueError, KeyError)
|
|
321
|
+
def func(): pass
|
|
322
|
+
|
|
323
|
+
# or
|
|
324
|
+
result = retry_on_exception(ValueError, KeyError)(func)()
|
|
325
|
+
"""
|
|
326
|
+
if not exceptions:
|
|
327
|
+
exceptions = (Exception,)
|
|
328
|
+
|
|
329
|
+
def decorator(func):
|
|
330
|
+
@functools.wraps(func)
|
|
331
|
+
def wrapper(*args, **kwargs):
|
|
332
|
+
return _retry_on_exception_impl(func, exceptions, *args, **kwargs)
|
|
333
|
+
return wrapper
|
|
334
|
+
return decorator
|
|
335
|
+
|
|
336
|
+
def _measure_memory_impl(func, *args, **kwargs):
|
|
337
|
+
"""Implementation of measure_memory functionality."""
|
|
338
|
+
initial_memory = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss
|
|
339
|
+
result = func(*args, **kwargs)
|
|
340
|
+
final_memory = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss
|
|
341
|
+
memory_used = final_memory - initial_memory
|
|
342
|
+
logger.info(f"{func.__name__} used {memory_used} KB of memory")
|
|
343
|
+
return result
|
|
344
|
+
|
|
345
|
+
def measure_memory(func: Union[Callable, None] = None) -> Callable:
|
|
346
|
+
"""
|
|
347
|
+
Measures the memory usage of a function while it runs.
|
|
348
|
+
Can be used as a decorator or function:
|
|
349
|
+
|
|
350
|
+
@measure_memory
|
|
351
|
+
def func(): pass
|
|
352
|
+
|
|
353
|
+
# or
|
|
354
|
+
result = measure_memory(func)()
|
|
355
|
+
"""
|
|
356
|
+
if func is None:
|
|
357
|
+
return measure_memory
|
|
358
|
+
|
|
359
|
+
@functools.wraps(func)
|
|
360
|
+
def wrapper(*args, **kwargs):
|
|
361
|
+
return _measure_memory_impl(func, *args, **kwargs)
|
|
362
|
+
return wrapper
|
|
363
|
+
|
|
364
|
+
def _profile_impl(func, *args, **kwargs):
|
|
365
|
+
"""Implementation of profile functionality."""
|
|
366
|
+
profiler = cProfile.Profile()
|
|
367
|
+
try:
|
|
368
|
+
return profiler.runcall(func, *args, **kwargs)
|
|
369
|
+
finally:
|
|
370
|
+
s = io.StringIO()
|
|
371
|
+
stats = pstats.Stats(profiler, stream=s).sort_stats('cumulative')
|
|
372
|
+
stats.print_stats()
|
|
373
|
+
logger.info(f"Profile for {func.__name__}:\n{s.getvalue()}")
|
|
374
|
+
|
|
375
|
+
def profile(func: Union[Callable, None] = None) -> Callable:
|
|
376
|
+
"""
|
|
377
|
+
Profiles the performance of a function to aid in optimization.
|
|
378
|
+
Can be used as a decorator or function:
|
|
379
|
+
|
|
380
|
+
@profile
|
|
381
|
+
def func(): pass
|
|
382
|
+
|
|
383
|
+
# or
|
|
384
|
+
result = profile(func)()
|
|
385
|
+
"""
|
|
386
|
+
if func is None:
|
|
387
|
+
return profile
|
|
388
|
+
|
|
389
|
+
@functools.wraps(func)
|
|
390
|
+
def wrapper(*args, **kwargs):
|
|
391
|
+
return _profile_impl(func, *args, **kwargs)
|
|
392
|
+
return wrapper
|
|
393
|
+
|
|
394
|
+
def _rate_limit_impl(func, calls, period, call_times, *args, **kwargs):
|
|
395
|
+
"""Implementation of rate_limit functionality."""
|
|
396
|
+
now = time.time()
|
|
397
|
+
call_times[:] = [t for t in call_times if now - t < period]
|
|
398
|
+
if len(call_times) >= calls:
|
|
399
|
+
raise Exception(f"Rate limit exceeded: {calls} calls per {period} seconds")
|
|
400
|
+
call_times.append(now)
|
|
401
|
+
return func(*args, **kwargs)
|
|
402
|
+
|
|
403
|
+
def rate_limit(calls: int, period: float):
|
|
404
|
+
"""
|
|
405
|
+
Limits the frequency of function calls to avoid overload.
|
|
406
|
+
Can be used as a decorator or function:
|
|
407
|
+
|
|
408
|
+
@rate_limit(calls=100, period=60)
|
|
409
|
+
def func(): pass
|
|
410
|
+
|
|
411
|
+
# or
|
|
412
|
+
result = rate_limit(calls=100, period=60)(func)()
|
|
413
|
+
"""
|
|
414
|
+
call_times = []
|
|
415
|
+
|
|
416
|
+
def decorator(func):
|
|
417
|
+
@functools.wraps(func)
|
|
418
|
+
def wrapper(*args, **kwargs):
|
|
419
|
+
return _rate_limit_impl(func, calls, period, call_times, *args, **kwargs)
|
|
420
|
+
return wrapper
|
|
421
|
+
return decorator
|
|
422
|
+
|
|
423
|
+
def _mock_data_impl(func, data, *args, **kwargs):
|
|
424
|
+
"""Implementation of mock_data functionality."""
|
|
425
|
+
return data() if callable(data) else data
|
|
426
|
+
|
|
427
|
+
def mock_data(data: Any):
|
|
428
|
+
"""
|
|
429
|
+
Replaces a function's output with mock data, useful for testing.
|
|
430
|
+
Can be used as a decorator or function:
|
|
431
|
+
|
|
432
|
+
@mock_data(42)
|
|
433
|
+
def func(): pass
|
|
434
|
+
|
|
435
|
+
# or
|
|
436
|
+
result = mock_data(42)(func)()
|
|
437
|
+
"""
|
|
438
|
+
def decorator(func):
|
|
439
|
+
@functools.wraps(func)
|
|
440
|
+
def wrapper(*args, **kwargs):
|
|
441
|
+
return _mock_data_impl(func, data, *args, **kwargs)
|
|
442
|
+
return wrapper
|
|
443
|
+
return decorator
|
|
444
|
+
|
|
445
|
+
def _benchmark_impl(func, *args, **kwargs):
|
|
446
|
+
"""Implementation of benchmark functionality."""
|
|
447
|
+
times = []
|
|
448
|
+
for _ in range(3): # Run 3 times for averaging
|
|
449
|
+
start_time = time.time()
|
|
450
|
+
result = func(*args, **kwargs)
|
|
451
|
+
end_time = time.time()
|
|
452
|
+
times.append(end_time - start_time)
|
|
453
|
+
avg_time = sum(times) / len(times)
|
|
454
|
+
logger.info(f"Benchmark {func.__name__}: avg={avg_time:.4f}s over {len(times)} runs")
|
|
455
|
+
return result
|
|
456
|
+
|
|
457
|
+
def benchmark(func: Union[Callable, None] = None) -> Callable:
|
|
458
|
+
"""
|
|
459
|
+
Compares the execution times of different functions.
|
|
460
|
+
Can be used as a decorator or function:
|
|
461
|
+
|
|
462
|
+
@benchmark
|
|
463
|
+
def func(): pass
|
|
464
|
+
|
|
465
|
+
# or
|
|
466
|
+
result = benchmark(func)()
|
|
467
|
+
"""
|
|
468
|
+
if func is None:
|
|
469
|
+
return benchmark
|
|
470
|
+
|
|
471
|
+
@functools.wraps(func)
|
|
472
|
+
def wrapper(*args, **kwargs):
|
|
473
|
+
return _benchmark_impl(func, *args, **kwargs)
|
|
474
|
+
return wrapper
|
|
475
|
+
|
|
476
|
+
def _run_in_thread_impl(func, *args, **kwargs):
|
|
477
|
+
"""Implementation of run_in_thread functionality."""
|
|
478
|
+
thread = threading.Thread(target=func, args=args, kwargs=kwargs)
|
|
479
|
+
thread.start()
|
|
480
|
+
return thread
|
|
481
|
+
|
|
482
|
+
def run_in_thread(func: Union[Callable, None] = None) -> Callable:
|
|
483
|
+
"""
|
|
484
|
+
Executes a function asynchronously in a separate thread.
|
|
485
|
+
Can be used as a decorator or function:
|
|
486
|
+
|
|
487
|
+
@run_in_thread
|
|
488
|
+
def func(): pass
|
|
489
|
+
|
|
490
|
+
# or
|
|
491
|
+
result = run_in_thread(func)()
|
|
492
|
+
"""
|
|
493
|
+
if func is None:
|
|
494
|
+
return run_in_thread
|
|
495
|
+
|
|
496
|
+
@functools.wraps(func)
|
|
497
|
+
def wrapper(*args, **kwargs):
|
|
498
|
+
return _run_in_thread_impl(func, *args, **kwargs)
|
|
499
|
+
return wrapper
|
|
500
|
+
|
|
501
|
+
def _no_debug_impl(func, *args, **kwargs):
|
|
502
|
+
"""Implementation of no_debug functionality."""
|
|
503
|
+
current_level = logger.getEffectiveLevel()
|
|
504
|
+
logger.setLevel(logging.INFO)
|
|
505
|
+
try:
|
|
506
|
+
return func(*args, **kwargs)
|
|
507
|
+
finally:
|
|
508
|
+
logger.setLevel(current_level)
|
|
509
|
+
|
|
510
|
+
def no_debug(func: Union[Callable, None] = None) -> Callable:
|
|
511
|
+
"""
|
|
512
|
+
Disables debug output in production environments.
|
|
513
|
+
Can be used as a decorator or function:
|
|
514
|
+
|
|
515
|
+
@no_debug
|
|
516
|
+
def func(): pass
|
|
517
|
+
|
|
518
|
+
# or
|
|
519
|
+
result = no_debug(func)()
|
|
520
|
+
"""
|
|
521
|
+
if func is None:
|
|
522
|
+
return no_debug
|
|
523
|
+
|
|
524
|
+
@functools.wraps(func)
|
|
525
|
+
def wrapper(*args, **kwargs):
|
|
526
|
+
return _no_debug_impl(func, *args, **kwargs)
|
|
527
|
+
return wrapper
|
|
528
|
+
|
|
529
|
+
def _transactional_impl(func, changes, *args, **kwargs):
|
|
530
|
+
"""Implementation of transactional functionality."""
|
|
531
|
+
try:
|
|
532
|
+
result = func(*args, **kwargs)
|
|
533
|
+
return result
|
|
534
|
+
except Exception:
|
|
535
|
+
for change in reversed(changes):
|
|
536
|
+
change.rollback()
|
|
537
|
+
raise
|
|
538
|
+
|
|
539
|
+
def transactional(func: Union[Callable, None] = None) -> Callable:
|
|
540
|
+
"""
|
|
541
|
+
Ensures that function changes are committed or rolled back atomically.
|
|
542
|
+
Can be used as a decorator or function:
|
|
543
|
+
|
|
544
|
+
@transactional
|
|
545
|
+
def func(): pass
|
|
546
|
+
|
|
547
|
+
# or
|
|
548
|
+
result = transactional(func)()
|
|
549
|
+
"""
|
|
550
|
+
if func is None:
|
|
551
|
+
return transactional
|
|
552
|
+
|
|
553
|
+
@functools.wraps(func)
|
|
554
|
+
def wrapper(*args, **kwargs):
|
|
555
|
+
changes = []
|
|
556
|
+
return _transactional_impl(func, changes, *args, **kwargs)
|
|
557
|
+
return wrapper
|
|
558
|
+
|
|
559
|
+
def _revert_on_failure_impl(func, state, *args, **kwargs):
|
|
560
|
+
"""Implementation of revert_on_failure functionality."""
|
|
561
|
+
try:
|
|
562
|
+
return func(*args, **kwargs)
|
|
563
|
+
except Exception:
|
|
564
|
+
# Restore initial state
|
|
565
|
+
for key, value in state.items():
|
|
566
|
+
setattr(args[0], key, value)
|
|
567
|
+
raise
|
|
568
|
+
|
|
569
|
+
def revert_on_failure(func: Union[Callable, None] = None) -> Callable:
|
|
570
|
+
"""
|
|
571
|
+
Reverts all changes made by the function if an exception occurs.
|
|
572
|
+
Can be used as a decorator or function:
|
|
573
|
+
|
|
574
|
+
@revert_on_failure
|
|
575
|
+
def func(): pass
|
|
576
|
+
|
|
577
|
+
# or
|
|
578
|
+
result = revert_on_failure(func)()
|
|
579
|
+
"""
|
|
580
|
+
if func is None:
|
|
581
|
+
return revert_on_failure
|
|
582
|
+
|
|
583
|
+
@functools.wraps(func)
|
|
584
|
+
def wrapper(*args, **kwargs):
|
|
585
|
+
state = {} # Store initial state
|
|
586
|
+
return _revert_on_failure_impl(func, state, *args, **kwargs)
|
|
587
|
+
return wrapper
|
|
588
|
+
|
|
589
|
+
def _audit_impl(func, *args, **kwargs):
|
|
590
|
+
"""Implementation of audit functionality."""
|
|
591
|
+
timestamp = datetime.now()
|
|
592
|
+
caller_frame = inspect.currentframe().f_back
|
|
593
|
+
caller_info = f"{caller_frame.f_code.co_filename}:{caller_frame.f_lineno}"
|
|
594
|
+
|
|
595
|
+
audit_data = {
|
|
596
|
+
"function": func.__name__,
|
|
597
|
+
"timestamp": timestamp,
|
|
598
|
+
"caller": caller_info,
|
|
599
|
+
"args": args,
|
|
600
|
+
"kwargs": kwargs
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
try:
|
|
604
|
+
result = func(*args, **kwargs)
|
|
605
|
+
audit_data["status"] = "success"
|
|
606
|
+
audit_data["result"] = result
|
|
607
|
+
return result
|
|
608
|
+
except Exception as e:
|
|
609
|
+
audit_data["status"] = "error"
|
|
610
|
+
audit_data["error"] = str(e)
|
|
611
|
+
raise
|
|
612
|
+
finally:
|
|
613
|
+
logger.info(f"Audit log: {audit_data}")
|
|
614
|
+
|
|
615
|
+
def audit(func: Union[Callable, None] = None) -> Callable:
|
|
616
|
+
"""
|
|
617
|
+
Audits and logs function calls with metadata.
|
|
618
|
+
Can be used as a decorator or function:
|
|
619
|
+
|
|
620
|
+
@audit
|
|
621
|
+
def func(): pass
|
|
622
|
+
|
|
623
|
+
# or
|
|
624
|
+
result = audit(func)()
|
|
625
|
+
"""
|
|
626
|
+
if func is None:
|
|
627
|
+
return audit
|
|
628
|
+
|
|
629
|
+
@functools.wraps(func)
|
|
630
|
+
def wrapper(*args, **kwargs):
|
|
631
|
+
return _audit_impl(func, *args, **kwargs)
|
|
632
|
+
return wrapper
|
|
633
|
+
|
|
634
|
+
def _trace_impl(func, *args, **kwargs):
|
|
635
|
+
"""Implementation of trace functionality."""
|
|
636
|
+
stack = traceback.extract_stack()[:-1] # Exclude current frame
|
|
637
|
+
logger.debug(f"Call trace for {func.__name__}:")
|
|
638
|
+
for filename, lineno, name, line in stack:
|
|
639
|
+
logger.debug(f" File {filename}, line {lineno}, in {name}")
|
|
640
|
+
if line:
|
|
641
|
+
logger.debug(f" {line.strip()}")
|
|
642
|
+
return func(*args, **kwargs)
|
|
643
|
+
|
|
644
|
+
def trace(func: Union[Callable, None] = None) -> Callable:
|
|
645
|
+
"""
|
|
646
|
+
Logs function calls, including call stack traces for debugging.
|
|
647
|
+
Can be used as a decorator or function:
|
|
648
|
+
|
|
649
|
+
@trace
|
|
650
|
+
def func(): pass
|
|
651
|
+
|
|
652
|
+
# or
|
|
653
|
+
result = trace(func)()
|
|
654
|
+
"""
|
|
655
|
+
if func is None:
|
|
656
|
+
return trace
|
|
657
|
+
|
|
658
|
+
@functools.wraps(func)
|
|
659
|
+
def wrapper(*args, **kwargs):
|
|
660
|
+
return _trace_impl(func, *args, **kwargs)
|
|
661
|
+
return wrapper
|
|
662
|
+
|
|
663
|
+
def _timeout_impl(func, seconds, *args, **kwargs):
|
|
664
|
+
"""Implementation of timeout functionality."""
|
|
665
|
+
result = queue.Queue()
|
|
666
|
+
|
|
667
|
+
def target():
|
|
668
|
+
try:
|
|
669
|
+
result.put(func(*args, **kwargs))
|
|
670
|
+
except Exception as e:
|
|
671
|
+
result.put(e)
|
|
672
|
+
|
|
673
|
+
thread = threading.Thread(target=target)
|
|
674
|
+
thread.daemon = True
|
|
675
|
+
thread.start()
|
|
676
|
+
thread.join(seconds)
|
|
677
|
+
|
|
678
|
+
if thread.is_alive():
|
|
679
|
+
raise TimeoutError(f"Function {func.__name__} timed out after {seconds} seconds")
|
|
680
|
+
|
|
681
|
+
value = result.get()
|
|
682
|
+
if isinstance(value, Exception):
|
|
683
|
+
raise value
|
|
684
|
+
return value
|
|
685
|
+
|
|
686
|
+
def timeout(seconds: int):
|
|
687
|
+
"""
|
|
688
|
+
Sets a timeout limit on function execution.
|
|
689
|
+
Can be used as a decorator or function:
|
|
690
|
+
|
|
691
|
+
@timeout(5)
|
|
692
|
+
def func(): pass
|
|
693
|
+
|
|
694
|
+
# or
|
|
695
|
+
result = timeout(5)(func)()
|
|
696
|
+
"""
|
|
697
|
+
def decorator(func):
|
|
698
|
+
@functools.wraps(func)
|
|
699
|
+
def wrapper(*args, **kwargs):
|
|
700
|
+
return _timeout_impl(func, seconds, *args, **kwargs)
|
|
701
|
+
return wrapper
|
|
702
|
+
return decorator
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 PythonWrap Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: python-wrap
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A comprehensive collection of Python decorators and utility functions for enhanced development workflows
|
|
5
|
+
Home-page: https://github.com/Pythondeveloper6/PythonWrap
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: decorators,utilities,python,development,tools,python
|
|
8
|
+
Author: Mahmoud Ahmed
|
|
9
|
+
Author-email: contact.mahmoudahmed@gmail.com
|
|
10
|
+
Maintainer: Mahmoud Ahmed
|
|
11
|
+
Maintainer-email: contact.mahmoudahmed@gmail.com
|
|
12
|
+
Requires-Python: >=3.8,<4.0
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
24
|
+
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
|
25
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
26
|
+
Classifier: Typing :: Typed
|
|
27
|
+
Requires-Dist: typing-extensions (>=4.7.1,<5.0.0)
|
|
28
|
+
Requires-Dist: wrapt (>=1.15.0,<2.0.0)
|
|
29
|
+
Project-URL: Bug Tracker, https://github.com/Pythondeveloper6/PythonWrap/issues
|
|
30
|
+
Project-URL: Changelog, https://github.com/Pythondeveloper6/PythonWrap/blob/main/CHANGELOG.md
|
|
31
|
+
Project-URL: Documentation, https://python-wrap.readthedocs.io
|
|
32
|
+
Project-URL: Repository, https://github.com/Pythondeveloper6/PythonWrap
|
|
33
|
+
Description-Content-Type: text/markdown
|
|
34
|
+
|
|
35
|
+
# PythonWrap
|
|
36
|
+
|
|
37
|
+
A comprehensive collection of Python decorators and utility functions to enhance your development workflow.
|
|
38
|
+
|
|
39
|
+
## Installation
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
pip install python-wrap
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Features
|
|
46
|
+
|
|
47
|
+
PythonWrap provides a rich set of decorators that can be used either as decorators or as standalone functions:
|
|
48
|
+
|
|
49
|
+
### Performance & Profiling
|
|
50
|
+
- `@timing`: Measure function execution time
|
|
51
|
+
- `@profile`: Profile function performance
|
|
52
|
+
- `@benchmark`: Compare execution times
|
|
53
|
+
- `@measure_memory`: Track memory usage
|
|
54
|
+
|
|
55
|
+
### Error Handling & Reliability
|
|
56
|
+
- `@retry`: Retry failed operations
|
|
57
|
+
- `@retry_on_exception`: Retry on specific exceptions
|
|
58
|
+
- `@timeout`: Set execution time limits
|
|
59
|
+
- `@revert_on_failure`: Automatic state rollback on failure
|
|
60
|
+
|
|
61
|
+
### Caching & Optimization
|
|
62
|
+
- `@memoize`: Cache function results
|
|
63
|
+
- `@cache`: Time-based result caching
|
|
64
|
+
- `@once`: Single execution guarantee
|
|
65
|
+
|
|
66
|
+
### Debugging & Logging
|
|
67
|
+
- `@log_args`: Log function arguments
|
|
68
|
+
- `@log_return`: Log return values
|
|
69
|
+
- `@trace`: Log call stack traces
|
|
70
|
+
- `@audit`: Comprehensive function call auditing
|
|
71
|
+
|
|
72
|
+
### Type Safety & Validation
|
|
73
|
+
- `@check_type`: Runtime type checking
|
|
74
|
+
- `@validate_args`: Custom argument validation
|
|
75
|
+
|
|
76
|
+
### Development Tools
|
|
77
|
+
- `@deprecate`: Mark deprecated functions
|
|
78
|
+
- `@no_debug`: Disable debug output
|
|
79
|
+
- `@mock_data`: Easy data mocking
|
|
80
|
+
- `@rate_limit`: Control execution frequency
|
|
81
|
+
|
|
82
|
+
### Concurrency
|
|
83
|
+
- `@run_in_thread`: Asynchronous execution
|
|
84
|
+
- `@transactional`: Atomic operations
|
|
85
|
+
|
|
86
|
+
## Usage Examples
|
|
87
|
+
|
|
88
|
+
### Using as Decorators
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
from python_wrap import timing, retry, memoize
|
|
92
|
+
|
|
93
|
+
# As a decorator
|
|
94
|
+
@timing
|
|
95
|
+
def slow_operation():
|
|
96
|
+
time.sleep(1)
|
|
97
|
+
return "Done"
|
|
98
|
+
|
|
99
|
+
# As a decorator with parameters
|
|
100
|
+
@retry(max_attempts=3, delay=1.0)
|
|
101
|
+
def unreliable_operation():
|
|
102
|
+
return api_call()
|
|
103
|
+
|
|
104
|
+
# Simple decorator
|
|
105
|
+
@memoize
|
|
106
|
+
def fibonacci(n):
|
|
107
|
+
if n < 2:
|
|
108
|
+
return n
|
|
109
|
+
return fibonacci(n-1) + fibonacci(n-2)
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Using as Functions
|
|
113
|
+
|
|
114
|
+
```python
|
|
115
|
+
from python_wrap import timing, retry, memoize
|
|
116
|
+
|
|
117
|
+
# Using timing as a function
|
|
118
|
+
def slow_operation():
|
|
119
|
+
time.sleep(1)
|
|
120
|
+
return "Done"
|
|
121
|
+
|
|
122
|
+
result = timing(slow_operation)()
|
|
123
|
+
|
|
124
|
+
# Using retry as a function
|
|
125
|
+
def unreliable_operation():
|
|
126
|
+
return api_call()
|
|
127
|
+
|
|
128
|
+
result = retry(unreliable_operation, max_attempts=3, delay=1.0)()
|
|
129
|
+
|
|
130
|
+
# Using memoize as a function
|
|
131
|
+
def fibonacci(n):
|
|
132
|
+
if n < 2:
|
|
133
|
+
return n
|
|
134
|
+
return fibonacci(n-1) + fibonacci(n-2)
|
|
135
|
+
|
|
136
|
+
memoized_fib = memoize(fibonacci)
|
|
137
|
+
result = memoized_fib(10)
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Advanced Usage
|
|
141
|
+
|
|
142
|
+
```python
|
|
143
|
+
from python_wrap import validate_args, check_type, timeout
|
|
144
|
+
|
|
145
|
+
# As decorators
|
|
146
|
+
@validate_args(x=lambda x: x > 0, y=lambda y: y < 100)
|
|
147
|
+
def process_numbers(x, y):
|
|
148
|
+
return x + y
|
|
149
|
+
|
|
150
|
+
@check_type(name=str, age=int)
|
|
151
|
+
def create_user(name, age):
|
|
152
|
+
return {"name": name, "age": age}
|
|
153
|
+
|
|
154
|
+
# As functions
|
|
155
|
+
def long_running_task():
|
|
156
|
+
process_large_dataset()
|
|
157
|
+
|
|
158
|
+
timed_task = timeout(5)(long_running_task)
|
|
159
|
+
result = timed_task()
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Combining Multiple Functions
|
|
163
|
+
|
|
164
|
+
```python
|
|
165
|
+
from python_wrap import timing, retry, log_args
|
|
166
|
+
|
|
167
|
+
# As decorators
|
|
168
|
+
@timing
|
|
169
|
+
@retry(max_attempts=3)
|
|
170
|
+
@log_args
|
|
171
|
+
def complex_operation(x, y):
|
|
172
|
+
return expensive_calculation(x, y)
|
|
173
|
+
|
|
174
|
+
# As functions
|
|
175
|
+
def complex_operation(x, y):
|
|
176
|
+
return expensive_calculation(x, y)
|
|
177
|
+
|
|
178
|
+
# Compose functions
|
|
179
|
+
monitored_op = timing(retry(log_args(complex_operation), max_attempts=3))
|
|
180
|
+
result = monitored_op(1, 2)
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## Contributing
|
|
184
|
+
|
|
185
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
186
|
+
|
|
187
|
+
## License
|
|
188
|
+
|
|
189
|
+
This project is licensed under the MIT License - see the LICENSE file for details.
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
python_wrap/__init__.py,sha256=xvyiwUL9n5ku3djrXovpZmY6phs0a4W3eVdElLEpgDA,903
|
|
2
|
+
python_wrap/decorators.py,sha256=MCnS59f7sT8-SbrTq-WQdeebru_HYwGrDj_sgKjS92k,19866
|
|
3
|
+
python_wrap-0.1.0.dist-info/LICENSE,sha256=HJx7meo3u1AM4Ms6Q1QNNdwSSdstdmEpo_AAx_TycH0,1079
|
|
4
|
+
python_wrap-0.1.0.dist-info/METADATA,sha256=QkjTI7DH9ZzDh3DS8eOlqNOMK8mOido3xLKeGriVqJ8,5094
|
|
5
|
+
python_wrap-0.1.0.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
|
|
6
|
+
python_wrap-0.1.0.dist-info/RECORD,,
|