0
# Proxy Objects
1
2
Transparent proxy objects that wrap other objects and delegate all operations to the wrapped object while allowing interception and modification of behavior. These proxies preserve the interface of the wrapped object while providing hooks for customization.
3
4
## Capabilities
5
6
### ObjectProxy
7
8
A transparent proxy object that wraps another object and delegates all operations to the wrapped object. The proxy preserves all attributes, methods, and special behaviors of the wrapped object.
9
10
```python { .api }
11
class ObjectProxy:
12
def __init__(self, wrapped):
13
"""
14
Create a transparent proxy for the wrapped object.
15
16
Args:
17
wrapped: The object to wrap
18
"""
19
20
@property
21
def __wrapped__(self):
22
"""Reference to the wrapped object."""
23
24
def __self_setattr__(self, name, value):
25
"""
26
Set attribute on the proxy itself rather than the wrapped object.
27
28
Args:
29
name (str): Attribute name
30
value: Attribute value
31
"""
32
33
# Properties that delegate to wrapped object
34
@property
35
def __module__(self): ...
36
37
@property
38
def __doc__(self): ...
39
40
@property
41
def __dict__(self): ...
42
43
@property
44
def __name__(self): ...
45
46
@property
47
def __class__(self): ...
48
```
49
50
**Usage Example:**
51
52
```python
53
import wrapt
54
55
class LoggingProxy(wrapt.ObjectProxy):
56
def __init__(self, wrapped):
57
super().__init__(wrapped)
58
self._self_call_count = 0
59
60
def __getattribute__(self, name):
61
if name.startswith('_self_'):
62
return object.__getattribute__(self, name)
63
64
self._self_call_count += 1
65
print(f"Accessing attribute: {name} (call #{self._self_call_count})")
66
return super().__getattribute__(name)
67
68
# Usage
69
my_list = [1, 2, 3]
70
proxy_list = LoggingProxy(my_list)
71
proxy_list.append(4) # Logs: "Accessing attribute: append (call #1)"
72
print(len(proxy_list)) # Logs: "Accessing attribute: __len__ (call #2)"
73
```
74
75
### CallableObjectProxy
76
77
Extends ObjectProxy to support callable objects by implementing the call protocol. Use this when wrapping functions, methods, or other callable objects.
78
79
```python { .api }
80
class CallableObjectProxy(ObjectProxy):
81
def __init__(self, wrapped):
82
"""
83
Create a transparent proxy for a callable object.
84
85
Args:
86
wrapped: The callable object to wrap (must be callable)
87
"""
88
89
def __call__(self, *args, **kwargs):
90
"""
91
Make the proxy callable by delegating to wrapped object.
92
93
Args:
94
*args: Positional arguments
95
**kwargs: Keyword arguments
96
97
Returns:
98
Result of calling the wrapped object
99
"""
100
```
101
102
**Usage Example:**
103
104
```python
105
import wrapt
106
107
class TimingProxy(wrapt.CallableObjectProxy):
108
def __call__(self, *args, **kwargs):
109
import time
110
start = time.time()
111
result = super().__call__(*args, **kwargs)
112
end = time.time()
113
print(f"Function took {end - start:.4f} seconds")
114
return result
115
116
def slow_function(n):
117
import time
118
time.sleep(n)
119
return f"Slept for {n} seconds"
120
121
# Wrap the function
122
timed_function = TimingProxy(slow_function)
123
result = timed_function(1) # Prints timing information
124
```
125
126
### PartialCallableObjectProxy
127
128
Similar to `functools.partial` but implemented as a proxy object. Pre-fills some arguments and keyword arguments for a callable, creating a new callable with fewer parameters.
129
130
```python { .api }
131
class PartialCallableObjectProxy(ObjectProxy):
132
def __init__(self, wrapped, *args, **kwargs):
133
"""
134
Create a partial application of a callable.
135
136
Args:
137
wrapped: The callable to wrap (must be callable)
138
*args: Positional arguments to pre-fill
139
**kwargs: Keyword arguments to pre-fill
140
"""
141
142
@property
143
def _self_args(self):
144
"""Tuple of stored positional arguments."""
145
146
@property
147
def _self_kwargs(self):
148
"""Dictionary of stored keyword arguments."""
149
150
def __call__(self, *args, **kwargs):
151
"""
152
Call the wrapped function with pre-filled and new arguments.
153
154
Args:
155
*args: Additional positional arguments
156
**kwargs: Additional keyword arguments
157
158
Returns:
159
Result of calling wrapped function with combined arguments
160
"""
161
```
162
163
**Usage Example:**
164
165
```python
166
import wrapt
167
168
def greet(greeting, name, punctuation="!"):
169
return f"{greeting}, {name}{punctuation}"
170
171
# Create partial applications
172
say_hello = wrapt.PartialCallableObjectProxy(greet, "Hello")
173
say_goodbye = wrapt.PartialCallableObjectProxy(greet, "Goodbye", punctuation=".")
174
175
# Use the partial functions
176
print(say_hello("Alice")) # "Hello, Alice!"
177
print(say_goodbye("Bob")) # "Goodbye, Bob."
178
print(say_hello("Charlie", "?")) # "Hello, Charlie?"
179
```
180
181
## Advanced Usage
182
183
### Custom Proxy Classes
184
185
You can subclass ObjectProxy to create specialized proxy behaviors:
186
187
```python
188
import wrapt
189
190
class ValidationProxy(wrapt.ObjectProxy):
191
def __init__(self, wrapped, validator=None):
192
super().__init__(wrapped)
193
self._self_validator = validator or (lambda x: True)
194
195
def __setattr__(self, name, value):
196
if not name.startswith('_self_') and not self._self_validator(value):
197
raise ValueError(f"Invalid value for {name}: {value}")
198
super().__setattr__(name, value)
199
200
# Usage with validation
201
class Person:
202
def __init__(self, name, age):
203
self.name = name
204
self.age = age
205
206
def age_validator(value):
207
return isinstance(value, int) and 0 <= value <= 150
208
209
person = Person("Alice", 30)
210
validated_person = ValidationProxy(person, age_validator)
211
212
validated_person.age = 25 # OK
213
# validated_person.age = -5 # Raises ValueError
214
```
215
216
### Proxy Introspection
217
218
All proxy objects maintain access to the wrapped object and preserve introspection capabilities:
219
220
```python
221
import wrapt
222
223
def my_function():
224
"""A test function."""
225
return "Hello"
226
227
proxy = wrapt.CallableObjectProxy(my_function)
228
229
# Access wrapped object
230
assert proxy.__wrapped__ is my_function
231
232
# Introspection works
233
assert proxy.__name__ == "my_function"
234
assert proxy.__doc__ == "A test function."
235
assert callable(proxy)
236
237
# isinstance and hasattr work correctly
238
assert isinstance(proxy, type(my_function))
239
assert hasattr(proxy, '__call__')
240
```
241
242
## Error Handling
243
244
Proxy objects raise `NotImplementedError` for operations that cannot be safely proxied:
245
246
- `__copy__()` and `__deepcopy__()`: Copying behavior is complex and object-specific
247
- `__reduce__()` and `__reduce_ex__()`: Pickling support requires custom implementation
248
249
For these operations, either implement them in your proxy subclass or handle them explicitly in your code.