0
# Enhanced Wrapping
1
2
Advanced function wrapping capabilities that extend `functools.wraps` with signature modification, parameter addition/removal, and better introspection support. These utilities provide precise control over wrapper function signatures while maintaining proper metadata inheritance.
3
4
```python
5
from typing import Union, Optional, Callable, Iterable, Any
6
from inspect import Signature, Parameter
7
```
8
9
## Capabilities
10
11
### Enhanced Function Wrapping
12
13
Enhanced alternative to `functools.wraps` with signature modification capabilities and better introspection support.
14
15
```python { .api }
16
def wraps(wrapped_fun: Callable,
17
new_sig: Optional[Union[str, Signature]] = None,
18
prepend_args: Optional[Union[str, Parameter, Iterable[Union[str, Parameter]]]] = None,
19
append_args: Optional[Union[str, Parameter, Iterable[Union[str, Parameter]]]] = None,
20
remove_args: Optional[Union[str, Iterable[str]]] = None,
21
func_name: Optional[str] = None,
22
co_name: Optional[str] = None,
23
inject_as_first_arg: bool = False,
24
add_source: bool = True,
25
add_impl: bool = True,
26
doc: Optional[str] = None,
27
qualname: Optional[str] = None,
28
module_name: Optional[str] = None,
29
**attrs: Any) -> Callable[[Callable], Callable]:
30
"""
31
Enhanced functools.wraps with signature modification capabilities.
32
33
Parameters:
34
- wrapped_fun: Callable
35
Function to wrap, used as default source for metadata
36
- new_sig: Optional[Union[str, Signature]], default None
37
Complete replacement signature. Cannot be used with other signature modifications
38
- prepend_args: Optional[Union[str, Parameter, Iterable[Union[str, Parameter]]]], default None
39
Arguments to add at the beginning of wrapped_fun's signature
40
- append_args: Optional[Union[str, Parameter, Iterable[Union[str, Parameter]]]], default None
41
Arguments to add at the end of wrapped_fun's signature
42
- remove_args: Optional[Union[str, Iterable[str]]], default None
43
Argument names to remove from wrapped_fun's signature
44
- func_name: Optional[str], default None
45
Override function name (defaults to wrapped_fun.__name__)
46
- co_name: Optional[str], default None
47
Name for compiled code object
48
- inject_as_first_arg: bool, default False
49
Inject created function as first positional argument to wrapper
50
- add_source: bool, default True
51
Add __source__ attribute with generated function source
52
- add_impl: bool, default True
53
Add __func_impl__ attribute pointing to wrapper implementation
54
- doc: Optional[str], default None
55
Docstring override (defaults to wrapped_fun.__doc__)
56
- qualname: Optional[str], default None
57
Qualified name override (defaults to wrapped_fun.__qualname__)
58
- module_name: Optional[str], default None
59
Module name override (defaults to wrapped_fun.__module__)
60
- **attrs: Any
61
Additional attributes to set (wrapped_fun.__dict__ is automatically copied)
62
63
Returns:
64
Callable[[Callable], Callable]: Decorator function that takes a callable and returns a callable
65
66
Note:
67
Sets __wrapped__ attribute for PEP 362 compliance.
68
If signature is modified, sets __signature__ attribute.
69
"""
70
```
71
72
### Direct Wrapper Creation
73
74
Function that directly creates wrapper without requiring decorator syntax.
75
76
```python { .api }
77
def create_wrapper(wrapped: Callable,
78
wrapper: Callable,
79
new_sig: Optional[Union[str, Signature]] = None,
80
prepend_args: Optional[Union[str, Parameter, Iterable[Union[str, Parameter]]]] = None,
81
append_args: Optional[Union[str, Parameter, Iterable[Union[str, Parameter]]]] = None,
82
remove_args: Optional[Union[str, Iterable[str]]] = None,
83
func_name: Optional[str] = None,
84
inject_as_first_arg: bool = False,
85
add_source: bool = True,
86
add_impl: bool = True,
87
doc: Optional[str] = None,
88
qualname: Optional[str] = None,
89
co_name: Optional[str] = None,
90
module_name: Optional[str] = None,
91
**attrs: Any) -> Callable:
92
"""
93
Creates signature-preserving wrapper function.
94
Equivalent to wraps(wrapped, **kwargs)(wrapper).
95
96
Parameters: Same as wraps() function
97
98
Returns:
99
Callable: Generated wrapper function with preserved/modified signature
100
"""
101
```
102
103
### Usage Examples
104
105
#### Basic Function Wrapping
106
107
```python
108
from makefun import wraps
109
import time
110
111
def target_function(x: int, y: str = "default") -> str:
112
"""Original function that does important work."""
113
return f"Result: {x}, {y}"
114
115
@wraps(target_function)
116
def wrapper(x, y):
117
print(f"Calling with x={x}, y={y}")
118
result = target_function(x, y)
119
print(f"Got result: {result}")
120
return result
121
122
# Wrapper preserves original signature and metadata
123
print(wrapper.__name__) # "target_function"
124
print(wrapper.__doc__) # "Original function that does important work."
125
print(wrapper(42)) # Calls with proper signature
126
```
127
128
#### Signature Modification - Adding Parameters
129
130
```python
131
from makefun import wraps
132
from inspect import signature
133
134
def original_func(data: list) -> int:
135
"""Process a list of data."""
136
return len(data)
137
138
@wraps(original_func, prepend_args="verbose: bool = False", append_args="log_level: str = 'INFO'")
139
def enhanced_wrapper(verbose, data, log_level):
140
if verbose:
141
print(f"Processing {len(data)} items at {log_level} level")
142
result = original_func(data)
143
if verbose:
144
print(f"Processed {result} items")
145
return result
146
147
print(signature(enhanced_wrapper)) # (verbose: bool = False, data: list, log_level: str = 'INFO') -> int
148
print(enhanced_wrapper([1, 2, 3])) # 3 (non-verbose)
149
print(enhanced_wrapper(True, [1, 2, 3, 4], "DEBUG")) # Verbose output + 4
150
```
151
152
#### Signature Modification - Removing Parameters
153
154
```python
155
from makefun import wraps
156
157
def complex_function(data: list, mode: str, debug: bool = False, timeout: int = 30) -> dict:
158
"""Complex function with many parameters."""
159
return {
160
"data_size": len(data),
161
"mode": mode,
162
"debug": debug,
163
"timeout": timeout
164
}
165
166
@wraps(complex_function, remove_args=["debug", "timeout"])
167
def simplified_wrapper(data, mode):
168
# Always use specific values for removed parameters
169
return complex_function(data, mode, debug=True, timeout=60)
170
171
# Simplified signature: (data: list, mode: str) -> dict
172
print(simplified_wrapper([1, 2, 3], "fast"))
173
```
174
175
#### Complete Signature Replacement
176
177
```python
178
from makefun import wraps
179
180
def legacy_function(a, b, c=None):
181
"""Legacy function with old-style signature."""
182
return f"Legacy: a={a}, b={b}, c={c}"
183
184
@wraps(legacy_function, new_sig="modern_api(data: dict, options: dict = None) -> str")
185
def modernized_wrapper(data, options):
186
# Transform modern parameters to legacy format
187
a = data.get("value1", 0)
188
b = data.get("value2", "")
189
c = options.get("extra") if options else None
190
return legacy_function(a, b, c)
191
192
# New signature: (data: dict, options: dict = None) -> str
193
result = modernized_wrapper({"value1": 42, "value2": "hello"}, {"extra": "world"})
194
print(result) # "Legacy: a=42, b=hello, c=world"
195
```
196
197
#### Timing Decorator Example
198
199
```python
200
from makefun import wraps
201
import time
202
import functools
203
204
def timing_decorator(func):
205
@wraps(func)
206
def wrapper(*args, **kwargs):
207
start_time = time.time()
208
result = func(*args, **kwargs)
209
end_time = time.time()
210
print(f"{func.__name__} took {end_time - start_time:.4f} seconds")
211
return result
212
return wrapper
213
214
@timing_decorator
215
def slow_computation(n: int) -> int:
216
"""Compute sum of squares up to n."""
217
return sum(i * i for i in range(n))
218
219
# Wrapper preserves original signature and metadata
220
print(slow_computation.__name__) # "slow_computation"
221
print(slow_computation.__doc__) # "Compute sum of squares up to n."
222
result = slow_computation(1000) # Prints timing info
223
```
224
225
#### Caching with Parameter Modification
226
227
```python
228
from makefun import wraps
229
import functools
230
231
def cached_function(x: int, y: int, use_cache: bool = True) -> int:
232
"""Expensive computation."""
233
print(f"Computing {x} + {y}")
234
return x + y
235
236
@wraps(cached_function, remove_args="use_cache")
237
@functools.lru_cache(maxsize=128)
238
def cached_wrapper(x, y):
239
# Always use caching, remove cache control parameter
240
return cached_function(x, y, use_cache=True)
241
242
# Simplified signature without use_cache parameter
243
print(cached_wrapper(5, 3)) # Computes and caches
244
print(cached_wrapper(5, 3)) # Uses cache
245
```
246
247
#### Validation Decorator with Parameter Addition
248
249
```python
250
from makefun import wraps
251
252
def unsafe_divide(x: float, y: float) -> float:
253
"""Divide x by y."""
254
return x / y
255
256
@wraps(unsafe_divide, append_args="validate: bool = True")
257
def safe_wrapper(x, y, validate):
258
if validate and y == 0:
259
raise ValueError("Division by zero not allowed")
260
if validate and not isinstance(x, (int, float)) or not isinstance(y, (int, float)):
261
raise TypeError("Arguments must be numeric")
262
return unsafe_divide(x, y)
263
264
# Enhanced signature: (x: float, y: float, validate: bool = True) -> float
265
print(safe_wrapper(10.0, 2.0)) # 5.0
266
print(safe_wrapper(10.0, 2.0, False)) # 5.0 (no validation)
267
```
268
269
#### Multiple Parameter Modifications
270
271
```python
272
from makefun import wraps
273
from typing import List, Optional
274
275
def basic_processor(items: List[str]) -> List[str]:
276
"""Basic item processing."""
277
return [item.upper() for item in items]
278
279
@wraps(basic_processor,
280
prepend_args="log_prefix: str = '[PROC]'",
281
append_args=["max_items: int = 100", "filter_empty: bool = True"])
282
def enhanced_processor(log_prefix, items, max_items, filter_empty):
283
print(f"{log_prefix} Processing up to {max_items} items")
284
285
if filter_empty:
286
items = [item for item in items if item.strip()]
287
288
items = items[:max_items]
289
result = basic_processor(items)
290
291
print(f"{log_prefix} Processed {len(result)} items")
292
return result
293
294
# New signature: (log_prefix: str = '[PROC]', items: List[str], max_items: int = 100, filter_empty: bool = True) -> List[str]
295
result = enhanced_processor(items=["hello", "", "world", "test"])
296
```
297
298
#### Error Handling
299
300
```python
301
from makefun import wraps
302
303
def target_func(x: int) -> int:
304
return x * 2
305
306
# Cannot combine new_sig with other signature modifications
307
try:
308
@wraps(target_func, new_sig="(y: int)", prepend_args="z: int")
309
def invalid_wrapper(z, y):
310
return target_func(y)
311
except ValueError as e:
312
print(f"Configuration error: {e}")
313
314
# Invalid parameter name to remove
315
try:
316
@wraps(target_func, remove_args="nonexistent_param")
317
def another_wrapper(x):
318
return target_func(x)
319
except KeyError as e:
320
print(f"Parameter error: {e}")
321
```
322
323
### Direct Wrapper Creation
324
325
Alternative to decorator syntax for programmatic wrapper creation:
326
327
```python
328
from makefun import create_wrapper
329
330
def original(x: int, y: str) -> str:
331
return f"{x}: {y}"
332
333
def my_wrapper(x, y):
334
print(f"Wrapper called with {x}, {y}")
335
return original(x, y)
336
337
# Create wrapper directly
338
wrapped_func = create_wrapper(original, my_wrapper)
339
340
# Equivalent to:
341
# @wraps(original)
342
# def wrapped_func(x, y):
343
# print(f"Wrapper called with {x}, {y}")
344
# return original(x, y)
345
346
print(wrapped_func(42, "hello"))
347
```
348
349
### Comparison with functools.wraps
350
351
Key advantages over `functools.wraps`:
352
353
1. **Signature Validation**: Arguments are validated against the signature before entering wrapper
354
2. **Signature Modification**: Can add, remove, or completely replace function signatures
355
3. **Better Introspection**: Maintains proper `inspect.signature()` support
356
4. **Keyword Arguments**: Arguments passed as keywords when possible for better flexibility
357
5. **PEP 362 Compliance**: Proper `__wrapped__` and `__signature__` attributes
358
359
```python
360
import functools
361
from makefun import wraps
362
363
def original(x: int, y: str = "default") -> str:
364
return f"{x}: {y}"
365
366
# functools.wraps: basic wrapping
367
@functools.wraps(original)
368
def basic_wrapper(*args, **kwargs):
369
return original(*args, **kwargs)
370
371
# makefun.wraps: enhanced wrapping with signature preservation
372
@wraps(original)
373
def enhanced_wrapper(x, y):
374
return original(x, y)
375
376
# enhanced_wrapper validates arguments before calling wrapper
377
# enhanced_wrapper receives arguments as keywords when possible
378
# enhanced_wrapper maintains exact signature introspection
379
```