Module for decorators, wrappers and monkey patching.
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.
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.
class ObjectProxy:
def __init__(self, wrapped):
"""
Create a transparent proxy for the wrapped object.
Args:
wrapped: The object to wrap
"""
@property
def __wrapped__(self):
"""Reference to the wrapped object."""
def __self_setattr__(self, name, value):
"""
Set attribute on the proxy itself rather than the wrapped object.
Args:
name (str): Attribute name
value: Attribute value
"""
# Properties that delegate to wrapped object
@property
def __module__(self): ...
@property
def __doc__(self): ...
@property
def __dict__(self): ...
@property
def __name__(self): ...
@property
def __class__(self): ...Usage Example:
import wrapt
class LoggingProxy(wrapt.ObjectProxy):
def __init__(self, wrapped):
super().__init__(wrapped)
self._self_call_count = 0
def __getattribute__(self, name):
if name.startswith('_self_'):
return object.__getattribute__(self, name)
self._self_call_count += 1
print(f"Accessing attribute: {name} (call #{self._self_call_count})")
return super().__getattribute__(name)
# Usage
my_list = [1, 2, 3]
proxy_list = LoggingProxy(my_list)
proxy_list.append(4) # Logs: "Accessing attribute: append (call #1)"
print(len(proxy_list)) # Logs: "Accessing attribute: __len__ (call #2)"Extends ObjectProxy to support callable objects by implementing the call protocol. Use this when wrapping functions, methods, or other callable objects.
class CallableObjectProxy(ObjectProxy):
def __init__(self, wrapped):
"""
Create a transparent proxy for a callable object.
Args:
wrapped: The callable object to wrap (must be callable)
"""
def __call__(self, *args, **kwargs):
"""
Make the proxy callable by delegating to wrapped object.
Args:
*args: Positional arguments
**kwargs: Keyword arguments
Returns:
Result of calling the wrapped object
"""Usage Example:
import wrapt
class TimingProxy(wrapt.CallableObjectProxy):
def __call__(self, *args, **kwargs):
import time
start = time.time()
result = super().__call__(*args, **kwargs)
end = time.time()
print(f"Function took {end - start:.4f} seconds")
return result
def slow_function(n):
import time
time.sleep(n)
return f"Slept for {n} seconds"
# Wrap the function
timed_function = TimingProxy(slow_function)
result = timed_function(1) # Prints timing informationSimilar 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.
class PartialCallableObjectProxy(ObjectProxy):
def __init__(self, wrapped, *args, **kwargs):
"""
Create a partial application of a callable.
Args:
wrapped: The callable to wrap (must be callable)
*args: Positional arguments to pre-fill
**kwargs: Keyword arguments to pre-fill
"""
@property
def _self_args(self):
"""Tuple of stored positional arguments."""
@property
def _self_kwargs(self):
"""Dictionary of stored keyword arguments."""
def __call__(self, *args, **kwargs):
"""
Call the wrapped function with pre-filled and new arguments.
Args:
*args: Additional positional arguments
**kwargs: Additional keyword arguments
Returns:
Result of calling wrapped function with combined arguments
"""Usage Example:
import wrapt
def greet(greeting, name, punctuation="!"):
return f"{greeting}, {name}{punctuation}"
# Create partial applications
say_hello = wrapt.PartialCallableObjectProxy(greet, "Hello")
say_goodbye = wrapt.PartialCallableObjectProxy(greet, "Goodbye", punctuation=".")
# Use the partial functions
print(say_hello("Alice")) # "Hello, Alice!"
print(say_goodbye("Bob")) # "Goodbye, Bob."
print(say_hello("Charlie", "?")) # "Hello, Charlie?"You can subclass ObjectProxy to create specialized proxy behaviors:
import wrapt
class ValidationProxy(wrapt.ObjectProxy):
def __init__(self, wrapped, validator=None):
super().__init__(wrapped)
self._self_validator = validator or (lambda x: True)
def __setattr__(self, name, value):
if not name.startswith('_self_') and not self._self_validator(value):
raise ValueError(f"Invalid value for {name}: {value}")
super().__setattr__(name, value)
# Usage with validation
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def age_validator(value):
return isinstance(value, int) and 0 <= value <= 150
person = Person("Alice", 30)
validated_person = ValidationProxy(person, age_validator)
validated_person.age = 25 # OK
# validated_person.age = -5 # Raises ValueErrorAll proxy objects maintain access to the wrapped object and preserve introspection capabilities:
import wrapt
def my_function():
"""A test function."""
return "Hello"
proxy = wrapt.CallableObjectProxy(my_function)
# Access wrapped object
assert proxy.__wrapped__ is my_function
# Introspection works
assert proxy.__name__ == "my_function"
assert proxy.__doc__ == "A test function."
assert callable(proxy)
# isinstance and hasattr work correctly
assert isinstance(proxy, type(my_function))
assert hasattr(proxy, '__call__')Proxy objects raise NotImplementedError for operations that cannot be safely proxied:
__copy__() and __deepcopy__(): Copying behavior is complex and object-specific__reduce__() and __reduce_ex__(): Pickling support requires custom implementationFor these operations, either implement them in your proxy subclass or handle them explicitly in your code.
Install with Tessl CLI
npx tessl i tessl/pypi-wrapt