0
# Decorator Creation
1
2
Universal decorator factory and synchronization utilities for creating robust decorators with proper signature preservation. These tools enable the creation of decorators that work correctly across functions, methods, classes, and other callable objects while preserving introspection capabilities.
3
4
## Capabilities
5
6
### Universal Decorator Factory
7
8
#### decorator
9
10
The main decorator factory for creating robust decorators with proper signature preservation. This is the recommended way to create decorators in wrapt.
11
12
```python { .api }
13
def decorator(wrapper=None, enabled=None, adapter=None, proxy=FunctionWrapper):
14
"""
15
Universal decorator factory for creating robust decorators.
16
17
Args:
18
wrapper: Wrapper function with signature (wrapped, instance, args, kwargs) -> result
19
enabled: Boolean or callable to enable/disable decorator (optional)
20
adapter: Function or signature spec for signature adaptation (optional)
21
proxy: Proxy class to use, defaults to FunctionWrapper (optional)
22
23
Returns:
24
Decorator function or partial decorator if wrapper not provided
25
"""
26
```
27
28
**Basic Usage:**
29
30
```python
31
import wrapt
32
33
@wrapt.decorator
34
def my_decorator(wrapped, instance, args, kwargs):
35
print(f"Calling {wrapped.__name__}")
36
result = wrapped(*args, **kwargs)
37
print(f"Result: {result}")
38
return result
39
40
@my_decorator
41
def greet(name):
42
return f"Hello, {name}!"
43
44
result = greet("Alice") # Logs call and result
45
```
46
47
**Partial Application:**
48
49
```python
50
import wrapt
51
52
# Create reusable decorator factory
53
def timing_decorator(enabled=True):
54
@wrapt.decorator(enabled=enabled)
55
def wrapper(wrapped, instance, args, kwargs):
56
import time
57
start = time.time()
58
result = wrapped(*args, **kwargs)
59
end = time.time()
60
print(f"{wrapped.__name__} took {end - start:.4f}s")
61
return result
62
return wrapper
63
64
# Use with different configurations
65
@timing_decorator(enabled=True)
66
def slow_function():
67
import time
68
time.sleep(0.1)
69
return "done"
70
71
@timing_decorator(enabled=False)
72
def fast_function():
73
return "instant"
74
75
slow_function() # Shows timing
76
fast_function() # No timing output
77
```
78
79
### Adapter Factories
80
81
#### AdapterFactory
82
83
Base class for creating adapter factories that modify function signatures or behavior.
84
85
```python { .api }
86
class AdapterFactory:
87
"""
88
Base class for adapter factories.
89
"""
90
91
def __call__(self, wrapped):
92
"""
93
Abstract method to be implemented by subclasses.
94
95
Args:
96
wrapped: The function being adapted
97
98
Returns:
99
Adapter function or signature specification
100
"""
101
```
102
103
#### adapter_factory
104
105
Alias for `DelegatedAdapterFactory` class - a factory that delegates to another factory function.
106
107
```python { .api }
108
adapter_factory = DelegatedAdapterFactory
109
# Usage: adapter_factory(factory_function)
110
```
111
112
**Usage Example:**
113
114
```python
115
import wrapt
116
import inspect
117
118
def signature_adapter_factory(wrapped):
119
"""Factory that creates signature adapter."""
120
sig = inspect.signature(wrapped)
121
122
def adapter(*args, **kwargs):
123
# Validate arguments against signature
124
bound = sig.bind(*args, **kwargs)
125
bound.apply_defaults()
126
return wrapped(**bound.arguments)
127
128
return adapter
129
130
@wrapt.decorator(adapter=wrapt.adapter_factory(signature_adapter_factory))
131
def validating_decorator(wrapped, instance, args, kwargs):
132
print(f"Validated call to {wrapped.__name__}")
133
return wrapped(*args, **kwargs)
134
135
@validating_decorator
136
def add_numbers(a: int, b: int = 10) -> int:
137
return a + b
138
139
result = add_numbers(5) # Uses default b=10, validated
140
```
141
142
### Synchronization
143
144
#### synchronized
145
146
Decorator and context manager for thread synchronization using locks. Provides automatic thread safety for functions and methods.
147
148
```python { .api }
149
def synchronized(wrapped):
150
"""
151
Decorator/context manager for thread synchronization.
152
153
Args:
154
wrapped: Function to synchronize OR lock object with acquire()/release()
155
156
Returns:
157
Synchronized decorator or context manager
158
"""
159
```
160
161
**As Decorator:**
162
163
```python
164
import wrapt
165
import threading
166
import time
167
168
class Counter:
169
def __init__(self):
170
self.value = 0
171
172
@wrapt.synchronized
173
def increment(self):
174
# This method is thread-safe
175
temp = self.value
176
time.sleep(0.001) # Simulate work
177
self.value = temp + 1
178
179
@wrapt.synchronized
180
def get_value(self):
181
return self.value
182
183
counter = Counter()
184
185
def worker():
186
for _ in range(100):
187
counter.increment()
188
189
threads = [threading.Thread(target=worker) for _ in range(10)]
190
for t in threads:
191
t.start()
192
for t in threads:
193
t.join()
194
195
print(counter.get_value()) # Should be 1000, not less due to race conditions
196
```
197
198
**As Context Manager:**
199
200
```python
201
import wrapt
202
import threading
203
204
# Using custom lock
205
my_lock = threading.RLock()
206
207
@wrapt.synchronized(my_lock)
208
def critical_section():
209
print("In critical section")
210
time.sleep(0.1)
211
212
# Or as context manager
213
with wrapt.synchronized(my_lock):
214
print("Also in critical section")
215
time.sleep(0.1)
216
```
217
218
## Advanced Usage
219
220
### Conditional Decorators
221
222
Create decorators that are conditionally applied:
223
224
```python
225
import wrapt
226
import os
227
228
def debug_decorator(enabled=None):
229
if enabled is None:
230
enabled = lambda: os.environ.get('DEBUG') == '1'
231
232
@wrapt.decorator(enabled=enabled)
233
def wrapper(wrapped, instance, args, kwargs):
234
print(f"DEBUG: {wrapped.__name__} called with {args}, {kwargs}")
235
result = wrapped(*args, **kwargs)
236
print(f"DEBUG: {wrapped.__name__} returned {result}")
237
return result
238
return wrapper
239
240
@debug_decorator()
241
def my_function(x, y):
242
return x + y
243
244
# Only logs when DEBUG=1 environment variable is set
245
result = my_function(2, 3)
246
```
247
248
### Class Decorators
249
250
The decorator function works with classes as well as functions:
251
252
```python
253
import wrapt
254
255
@wrapt.decorator
256
def class_wrapper(wrapped, instance, args, kwargs):
257
print(f"Creating instance of {wrapped.__name__}")
258
instance = wrapped(*args, **kwargs)
259
print(f"Created: {instance}")
260
return instance
261
262
@class_wrapper
263
class MyClass:
264
def __init__(self, name):
265
self.name = name
266
267
def __repr__(self):
268
return f"MyClass({self.name})"
269
270
obj = MyClass("test") # Logs creation
271
```
272
273
### Method-Aware Decorators
274
275
Decorators that behave differently for functions vs methods:
276
277
```python
278
import wrapt
279
280
@wrapt.decorator
281
def smart_decorator(wrapped, instance, args, kwargs):
282
if instance is None:
283
print(f"Function call: {wrapped.__name__}")
284
else:
285
print(f"Method call: {wrapped.__name__} on {type(instance).__name__}")
286
287
return wrapped(*args, **kwargs)
288
289
@smart_decorator
290
def standalone_function():
291
return "function result"
292
293
class MyClass:
294
@smart_decorator
295
def method(self):
296
return "method result"
297
298
standalone_function() # Logs: "Function call: standalone_function"
299
300
obj = MyClass()
301
obj.method() # Logs: "Method call: method on MyClass"
302
```
303
304
### Decorator Chains
305
306
Multiple decorators work correctly with wrapt:
307
308
```python
309
import wrapt
310
311
@wrapt.decorator
312
def decorator1(wrapped, instance, args, kwargs):
313
print("Decorator 1 - before")
314
result = wrapped(*args, **kwargs)
315
print("Decorator 1 - after")
316
return result
317
318
@wrapt.decorator
319
def decorator2(wrapped, instance, args, kwargs):
320
print("Decorator 2 - before")
321
result = wrapped(*args, **kwargs)
322
print("Decorator 2 - after")
323
return result
324
325
@decorator1
326
@decorator2
327
def my_function():
328
print("Function executing")
329
return "result"
330
331
my_function()
332
# Output:
333
# Decorator 1 - before
334
# Decorator 2 - before
335
# Function executing
336
# Decorator 2 - after
337
# Decorator 1 - after
338
```
339
340
### Custom Proxy Classes
341
342
Use custom proxy classes with the decorator:
343
344
```python
345
import wrapt
346
347
class CustomProxy(wrapt.FunctionWrapper):
348
def __init__(self, wrapped, wrapper):
349
super().__init__(wrapped, wrapper)
350
self._self_call_count = 0
351
352
def __call__(self, *args, **kwargs):
353
self._self_call_count += 1
354
print(f"Call #{self._self_call_count}")
355
return super().__call__(*args, **kwargs)
356
357
@wrapt.decorator(proxy=CustomProxy)
358
def counting_decorator(wrapped, instance, args, kwargs):
359
return wrapped(*args, **kwargs)
360
361
@counting_decorator
362
def my_function():
363
return "result"
364
365
my_function() # Call #1
366
my_function() # Call #2
367
```
368
369
## Signature Preservation
370
371
The decorator function preserves function signatures for introspection:
372
373
```python
374
import wrapt
375
import inspect
376
377
@wrapt.decorator
378
def preserving_decorator(wrapped, instance, args, kwargs):
379
return wrapped(*args, **kwargs)
380
381
@preserving_decorator
382
def example_function(a: int, b: str = "default") -> str:
383
"""Example function with type hints."""
384
return f"{a}: {b}"
385
386
# Signature is preserved
387
sig = inspect.signature(example_function)
388
print(sig) # (a: int, b: str = 'default') -> str
389
390
# Docstring is preserved
391
print(example_function.__doc__) # "Example function with type hints."
392
393
# Name is preserved
394
print(example_function.__name__) # "example_function"
395
```
396
397
## Error Handling
398
399
Decorators created with wrapt properly preserve exception information:
400
401
```python
402
import wrapt
403
404
@wrapt.decorator
405
def error_handling_decorator(wrapped, instance, args, kwargs):
406
try:
407
return wrapped(*args, **kwargs)
408
except Exception as e:
409
print(f"Exception in {wrapped.__name__}: {e}")
410
raise # Re-raise preserving traceback
411
412
@error_handling_decorator
413
def failing_function():
414
raise ValueError("Something went wrong")
415
416
try:
417
failing_function()
418
except ValueError:
419
import traceback
420
traceback.print_exc() # Shows original traceback
421
```