CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-wrapt

Module for decorators, wrappers and monkey patching.

Overview
Eval results
Files

function-wrappers.mddocs/

Function Wrappers

Specialized wrappers for functions that handle method binding, descriptor protocol, and function-specific behavior with proper signature preservation. These wrappers are designed specifically for wrapping functions and methods while maintaining their introspection capabilities and binding behavior.

Capabilities

FunctionWrapper

A specialized wrapper for functions that properly handles method binding, the descriptor protocol, and function-specific behavior. This is the core wrapper used by the @decorator function and provides the foundation for creating robust function decorators.

class FunctionWrapper(ObjectProxy):
    def __init__(self, wrapped, wrapper, enabled=None):
        """
        Create a function wrapper with proper binding support.
        
        Args:
            wrapped: The function to wrap
            wrapper: Wrapper function with signature (wrapped, instance, args, kwargs) -> result
            enabled: Boolean or callable to enable/disable wrapper (optional)
        """

    @property
    def _self_wrapper(self):
        """The wrapper function."""

    @property
    def _self_enabled(self):
        """Enable/disable flag or callable."""

    @property
    def _self_instance(self):
        """Instance the function is bound to (if any)."""

    @property
    def _self_binding(self):
        """Type of binding ('function', 'method', 'classmethod', etc.)."""

    @property
    def _self_parent(self):
        """Parent wrapper (for bound functions)."""

    def __get__(self, instance, owner):
        """
        Descriptor protocol for method binding.
        
        Args:
            instance: Instance object (None for class access)
            owner: Owner class
            
        Returns:
            BoundFunctionWrapper or self
        """

    def __set_name__(self, owner, name):
        """
        Support for descriptor naming (Python 3.6+).
        
        Args:
            owner: Owner class
            name: Attribute name
        """

    def __call__(self, *args, **kwargs):
        """
        Call the wrapped function through the wrapper.
        
        Args:
            *args: Positional arguments
            **kwargs: Keyword arguments
            
        Returns:
            Result from wrapper function
        """

    def __instancecheck__(self, instance):
        """Support for isinstance() checks."""

    def __subclasscheck__(self, subclass):
        """Support for issubclass() checks."""

Usage Example:

import wrapt

def logging_wrapper(wrapped, instance, args, kwargs):
    print(f"Calling {wrapped.__name__} with args={args}, kwargs={kwargs}")
    if instance is not None:
        print(f"Instance: {instance}")
    result = wrapped(*args, **kwargs)
    print(f"Result: {result}")
    return result

# Wrap a function
def add(a, b):
    return a + b

wrapped_add = wrapt.FunctionWrapper(add, logging_wrapper)
result = wrapped_add(2, 3)  # Logs call details

# Wrap a method
class Calculator:
    def multiply(self, a, b):
        return a * b

Calculator.multiply = wrapt.FunctionWrapper(Calculator.multiply, logging_wrapper)
calc = Calculator()
result = calc.multiply(4, 5)  # Logs with instance information

BoundFunctionWrapper

Created automatically when a FunctionWrapper is accessed as a method. Handles the complexities of method calls, instance binding, and argument handling. You typically don't create these directly.

class BoundFunctionWrapper:
    """
    Wrapper for bound functions (methods). Created automatically by 
    FunctionWrapper.__get__() when accessing wrapped methods on instances.
    """

    def __call__(self, *args, **kwargs):
        """
        Call the bound method with proper instance binding.
        
        Args:
            *args: Positional arguments  
            **kwargs: Keyword arguments
            
        Returns:
            Result from wrapper function
        """

Example of automatic binding:

import wrapt

def method_wrapper(wrapped, instance, args, kwargs):
    print(f"Method {wrapped.__name__} called on {type(instance).__name__}")
    return wrapped(*args, **kwargs)

class MyClass:
    def __init__(self, name):
        self.name = name
    
    def greet(self, message):
        return f"{self.name}: {message}"

# Wrap the method
MyClass.greet = wrapt.FunctionWrapper(MyClass.greet, method_wrapper)

# Create instance and call method
obj = MyClass("Alice")
result = obj.greet("Hello!")  # Creates BoundFunctionWrapper automatically
# Prints: "Method greet called on MyClass" 
# Returns: "Alice: Hello!"

Advanced Usage

Conditional Wrappers

Use the enabled parameter to conditionally enable/disable wrapping:

import wrapt
import os

def debug_wrapper(wrapped, instance, args, kwargs):
    print(f"DEBUG: Calling {wrapped.__name__}")
    return wrapped(*args, **kwargs)

# Enable only when DEBUG environment variable is set
debug_enabled = lambda: os.environ.get('DEBUG') == '1'

@wrapt.FunctionWrapper(enabled=debug_enabled)
def my_function():
    return "result"

# Wrapper only executes if DEBUG=1 is set
result = my_function()

Wrapper Chains

FunctionWrapper supports chaining multiple wrappers:

import wrapt

def timing_wrapper(wrapped, instance, args, kwargs):
    import time
    start = time.time()
    result = wrapped(*args, **kwargs)
    print(f"Time: {time.time() - start:.4f}s")
    return result

def logging_wrapper(wrapped, instance, args, kwargs):
    print(f"Calling: {wrapped.__name__}")
    result = wrapped(*args, **kwargs)
    print(f"Result: {result}")
    return result

def slow_function():
    import time
    time.sleep(0.1)
    return "done"

# Chain wrappers (inner wrapper applied first)
wrapped_once = wrapt.FunctionWrapper(slow_function, timing_wrapper)
wrapped_twice = wrapt.FunctionWrapper(wrapped_once, logging_wrapper)

wrapped_twice()  # Logs then times the function

Custom Wrapper Classes

Extend FunctionWrapper for specialized behavior:

import wrapt

class CachingWrapper(wrapt.FunctionWrapper):
    def __init__(self, wrapped, wrapper):
        super().__init__(wrapped, wrapper)
        self._self_cache = {}
    
    def __call__(self, *args, **kwargs):
        # Create cache key from arguments
        key = (args, tuple(sorted(kwargs.items())))
        
        if key in self._self_cache:
            print(f"Cache hit for {self.__wrapped__.__name__}")
            return self._self_cache[key]
        
        result = super().__call__(*args, **kwargs)
        self._self_cache[key] = result
        return result

def identity_wrapper(wrapped, instance, args, kwargs):
    return wrapped(*args, **kwargs)

def expensive_function(n):
    import time
    time.sleep(0.5)  # Simulate expensive operation
    return n * 2

cached_function = CachingWrapper(expensive_function, identity_wrapper)
print(cached_function(5))  # Slow first call
print(cached_function(5))  # Fast cached call

Descriptor Protocol

FunctionWrapper fully implements the descriptor protocol, making it work correctly with class methods, static methods, and properties:

import wrapt

def method_wrapper(wrapped, instance, args, kwargs):
    print(f"Wrapper called with instance: {instance}")
    return wrapped(*args, **kwargs)

class MyClass:
    @wrapt.FunctionWrapper(method_wrapper)
    def instance_method(self):
        return "instance"
    
    @classmethod
    @wrapt.FunctionWrapper(method_wrapper)  
    def class_method(cls):
        return "class"
    
    @staticmethod
    @wrapt.FunctionWrapper(method_wrapper)
    def static_method():
        return "static"

obj = MyClass()
obj.instance_method()  # instance != None
MyClass.class_method()  # instance = MyClass
MyClass.static_method()  # instance = None

Error Handling

FunctionWrapper preserves exception information and stack traces:

import wrapt

def error_wrapper(wrapped, instance, args, kwargs):
    try:
        return wrapped(*args, **kwargs)
    except Exception as e:
        print(f"Exception in {wrapped.__name__}: {e}")
        raise  # Re-raise preserving original traceback

def failing_function():
    raise ValueError("Something went wrong")

wrapped_function = wrapt.FunctionWrapper(failing_function, error_wrapper)

try:
    wrapped_function()
except ValueError as e:
    # Original traceback is preserved
    import traceback
    traceback.print_exc()

Install with Tessl CLI

npx tessl i tessl/pypi-wrapt

docs

decorators.md

function-wrappers.md

index.md

patching.md

proxy-objects.md

utilities.md

tile.json