CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-wrapt

Module for decorators, wrappers and monkey patching.

Overview
Eval results
Files

utilities.mddocs/

Utilities

Import hooks, weak references, and utility functions for advanced wrapping scenarios and compatibility. These utilities provide additional functionality for complex use cases and maintain compatibility with different Python versions.

Capabilities

Import Hooks

Import hooks allow you to automatically apply patches or perform actions when specific modules are imported.

register_post_import_hook

Registers a hook function to be called when a specific module is imported.

def register_post_import_hook(hook, name):
    """
    Register hook to be called when module is imported.
    
    Args:
        hook: Callback function or string in format 'module:function'
        name: Module name to watch for
    """

Usage Example:

import wrapt

def patch_requests(module):
    """Hook function called when requests module is imported."""
    print("Requests module imported, applying patches...")
    
    def log_wrapper(wrapped, instance, args, kwargs):
        print(f"HTTP request to: {args[0] if args else 'unknown'}")
        return wrapped(*args, **kwargs)
    
    wrapt.wrap_function_wrapper(module, 'get', log_wrapper)

# Register hook before requests is imported
wrapt.register_post_import_hook(patch_requests, 'requests')

# Now when requests is imported, it will be automatically patched
import requests  # Triggers the hook
response = requests.get('https://httpbin.org/get')  # Will be logged

when_imported

Decorator for marking functions as post-import hooks. More convenient than register_post_import_hook for simple cases.

def when_imported(name):
    """
    Decorator to register function as post-import hook.
    
    Args:
        name: Module name to watch for
        
    Returns:
        Decorator that registers the function as a hook
    """

Usage Example:

import wrapt

@wrapt.when_imported('json')
def patch_json(module):
    """Automatically patch json module when imported."""
    print("Patching json module...")
    
    def timing_wrapper(wrapped, instance, args, kwargs):
        import time
        start = time.time()
        result = wrapped(*args, **kwargs)
        print(f"JSON operation took {time.time() - start:.4f}s")
        return result
    
    wrapt.wrap_function_wrapper(module, 'loads', timing_wrapper)
    wrapt.wrap_function_wrapper(module, 'dumps', timing_wrapper)

# Import json - triggers automatic patching
import json

data = json.loads('{"key": "value"}')  # Will show timing
result = json.dumps(data)              # Will show timing

notify_module_loaded

Manually triggers post-import hooks for a module. Useful for testing or when you need to trigger hooks programmatically.

def notify_module_loaded(module):
    """
    Manually trigger post-import hooks for a module.
    
    Args:
        module: Module object that was loaded
    """

discover_post_import_hooks

Discovers and registers post-import hooks from package entry points. Requires pkg_resources to be available.

def discover_post_import_hooks(group):
    """
    Discover and register hooks from package entry points.
    
    Args:
        group: Entry point group name to search
    """

Weak References

WeakFunctionProxy

A weak reference proxy for functions that properly handles bound methods, class methods, static methods, and regular functions.

class WeakFunctionProxy:
    def __init__(self, wrapped, callback=None):
        """
        Create weak reference proxy for function.
        
        Args:
            wrapped: Function to create weak reference for  
            callback: Optional callback when reference expires
        """

    @property
    def _self_expired(self):
        """Boolean flag indicating if reference has expired."""

    @property
    def _self_instance(self):
        """Weak reference to instance (for bound methods)."""

    def __call__(self, *args, **kwargs):
        """
        Call the weakly referenced function.
        
        Args:
            *args: Positional arguments
            **kwargs: Keyword arguments
            
        Returns:
            Result from function call
            
        Raises:
            ReferenceError: If the weak reference has expired
        """

Usage Example:

import wrapt
import gc

class MyClass:
    def method(self):
        return "method called"

obj = MyClass()

# Create weak proxy to bound method
weak_method = wrapt.WeakFunctionProxy(obj.method)

# Call through weak proxy
result = weak_method()  # "method called"

# Delete original object
del obj
gc.collect()

# Weak reference is now expired
try:
    weak_method()
except ReferenceError:
    print("Weak reference expired")

Callback on Expiration:

import wrapt

def cleanup_callback(proxy):
    print("Weak reference expired, cleaning up...")

class MyClass:
    def method(self):
        return "result"

obj = MyClass()
weak_proxy = wrapt.WeakFunctionProxy(obj.method, cleanup_callback)

del obj  # Triggers callback

Compatibility Functions

formatargspec

Forward-compatible implementation of inspect.formatargspec() which was removed in Python 3.11.

def formatargspec(args, varargs=None, varkw=None, defaults=None, 
                  kwonlyargs=(), kwonlydefaults={}, annotations={}):
    """
    Format function argument specification as string.
    
    Args:
        args: List of argument names
        varargs: Name of *args parameter (optional)
        varkw: Name of **kwargs parameter (optional)  
        defaults: Tuple of default values (optional)
        kwonlyargs: Tuple of keyword-only argument names (optional)
        kwonlydefaults: Dict of keyword-only defaults (optional)
        annotations: Dict of type annotations (optional)
        
    Returns:
        String representation of function signature
    """

Usage Example:

import wrapt

# Format a function signature
signature = wrapt.formatargspec(
    args=['a', 'b', 'c'],
    defaults=[None, 'default'], 
    varargs='args',
    varkw='kwargs',
    annotations={'a': 'int', 'b': 'str', 'return': 'bool'}
)
print(signature)  # (a: int, b: str = 'default', c=None, *args, **kwargs) -> bool

getcallargs

Re-exported from inspect module for backward compatibility. Maps function arguments to their parameter names.

def getcallargs(func, *positional, **named):
    """
    Get mapping of function parameters to argument values.
    
    Args:
        func: Function to analyze
        *positional: Positional arguments
        **named: Named arguments
        
    Returns:
        Dict mapping parameter names to argument values
    """

Usage Example:

import wrapt

def example_function(a, b=10, *args, **kwargs):
    pass

# Map arguments to parameters
mapping = wrapt.getcallargs(example_function, 1, 2, 3, x=4, y=5)
print(mapping)  # {'a': 1, 'b': 2, 'args': (3,), 'kwargs': {'x': 4, 'y': 5}}

Advanced Usage

Complex Import Hook Scenarios

Handle complex module import scenarios:

import wrapt

# Track when multiple related modules are imported
modules_to_watch = ['requests', 'urllib3', 'http.client']
imported_modules = set()

def track_http_modules(module):
    imported_modules.add(module.__name__)
    print(f"HTTP-related module imported: {module.__name__}")
    
    if len(imported_modules) == len(modules_to_watch):
        print("All HTTP modules loaded, applying comprehensive patches...")
        apply_comprehensive_http_patches()

for module_name in modules_to_watch:
    wrapt.register_post_import_hook(track_http_modules, module_name)

def apply_comprehensive_http_patches():
    # Apply patches that require multiple modules to be loaded
    pass

Weak Reference Cleanup Patterns

Use weak references for automatic cleanup:

import wrapt
import weakref

class ResourceManager:
    def __init__(self):
        self.resources = weakref.WeakSet()
        self.cleanup_callbacks = []
    
    def register_resource(self, resource):
        self.resources.add(resource)
        
        # Create weak proxy with cleanup callback
        def cleanup(proxy):
            print(f"Resource {resource} cleaned up")
            
        weak_proxy = wrapt.WeakFunctionProxy(
            resource.cleanup if hasattr(resource, 'cleanup') else lambda: None,
            cleanup
        )
        self.cleanup_callbacks.append(weak_proxy)
    
    def cleanup_all(self):
        for callback in self.cleanup_callbacks:
            try:
                callback()
            except ReferenceError:
                pass  # Already cleaned up

# Usage
manager = ResourceManager()

class Resource:
    def cleanup(self):
        print("Resource cleanup called")

resource = Resource()
manager.register_resource(resource)

del resource  # Triggers automatic cleanup

Custom Hook Discovery

Implement custom hook discovery mechanisms:

import wrapt
import importlib
import pkgutil

def discover_plugin_hooks(plugin_namespace):
    """Discover hooks from plugin packages."""
    try:
        namespace_pkg = importlib.import_module(plugin_namespace)
        for finder, name, ispkg in pkgutil.iter_modules(
            namespace_pkg.__path__, 
            namespace_pkg.__name__ + "."
        ):
            try:
                plugin_module = importlib.import_module(name)
                if hasattr(plugin_module, 'register_hooks'):
                    plugin_module.register_hooks()
                    print(f"Registered hooks from {name}")
            except ImportError as e:
                print(f"Failed to load plugin {name}: {e}")
    except ImportError:
        print(f"Plugin namespace {plugin_namespace} not found")

# Example plugin structure:
# myapp_plugins/
#   __init__.py
#   database_plugin.py  # Contains register_hooks() function
#   cache_plugin.py     # Contains register_hooks() function

discover_plugin_hooks('myapp_plugins')

Performance Considerations

Efficient Hook Management

For applications with many hooks, consider efficient management:

import wrapt
from collections import defaultdict

class HookManager:
    def __init__(self):
        self.hooks_by_module = defaultdict(list)
        self.registered_modules = set()
    
    def register_hook(self, module_name, hook_func):
        """Register hook with batching for efficiency."""
        self.hooks_by_module[module_name].append(hook_func)
        
        if module_name not in self.registered_modules:
            wrapt.register_post_import_hook(
                self._execute_hooks_for_module, 
                module_name
            )
            self.registered_modules.add(module_name)
    
    def _execute_hooks_for_module(self, module):
        """Execute all hooks for a module efficiently."""
        hooks = self.hooks_by_module[module.__name__]
        for hook in hooks:
            try:
                hook(module)
            except Exception as e:
                print(f"Hook failed for {module.__name__}: {e}")

# Global hook manager
hook_manager = HookManager()

# Register multiple hooks efficiently
hook_manager.register_hook('requests', lambda m: print("Hook 1"))
hook_manager.register_hook('requests', lambda m: print("Hook 2")) 
hook_manager.register_hook('requests', lambda m: print("Hook 3"))

import requests  # All hooks execute together

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