Small library to dynamically create python functions.
—
Advanced function wrapping capabilities that extend functools.wraps with signature modification, parameter addition/removal, and better introspection support. These utilities provide precise control over wrapper function signatures while maintaining proper metadata inheritance.
from typing import Union, Optional, Callable, Iterable, Any
from inspect import Signature, ParameterEnhanced alternative to functools.wraps with signature modification capabilities and better introspection support.
def wraps(wrapped_fun: Callable,
new_sig: Optional[Union[str, Signature]] = None,
prepend_args: Optional[Union[str, Parameter, Iterable[Union[str, Parameter]]]] = None,
append_args: Optional[Union[str, Parameter, Iterable[Union[str, Parameter]]]] = None,
remove_args: Optional[Union[str, Iterable[str]]] = None,
func_name: Optional[str] = None,
co_name: Optional[str] = None,
inject_as_first_arg: bool = False,
add_source: bool = True,
add_impl: bool = True,
doc: Optional[str] = None,
qualname: Optional[str] = None,
module_name: Optional[str] = None,
**attrs: Any) -> Callable[[Callable], Callable]:
"""
Enhanced functools.wraps with signature modification capabilities.
Parameters:
- wrapped_fun: Callable
Function to wrap, used as default source for metadata
- new_sig: Optional[Union[str, Signature]], default None
Complete replacement signature. Cannot be used with other signature modifications
- prepend_args: Optional[Union[str, Parameter, Iterable[Union[str, Parameter]]]], default None
Arguments to add at the beginning of wrapped_fun's signature
- append_args: Optional[Union[str, Parameter, Iterable[Union[str, Parameter]]]], default None
Arguments to add at the end of wrapped_fun's signature
- remove_args: Optional[Union[str, Iterable[str]]], default None
Argument names to remove from wrapped_fun's signature
- func_name: Optional[str], default None
Override function name (defaults to wrapped_fun.__name__)
- co_name: Optional[str], default None
Name for compiled code object
- inject_as_first_arg: bool, default False
Inject created function as first positional argument to wrapper
- add_source: bool, default True
Add __source__ attribute with generated function source
- add_impl: bool, default True
Add __func_impl__ attribute pointing to wrapper implementation
- doc: Optional[str], default None
Docstring override (defaults to wrapped_fun.__doc__)
- qualname: Optional[str], default None
Qualified name override (defaults to wrapped_fun.__qualname__)
- module_name: Optional[str], default None
Module name override (defaults to wrapped_fun.__module__)
- **attrs: Any
Additional attributes to set (wrapped_fun.__dict__ is automatically copied)
Returns:
Callable[[Callable], Callable]: Decorator function that takes a callable and returns a callable
Note:
Sets __wrapped__ attribute for PEP 362 compliance.
If signature is modified, sets __signature__ attribute.
"""Function that directly creates wrapper without requiring decorator syntax.
def create_wrapper(wrapped: Callable,
wrapper: Callable,
new_sig: Optional[Union[str, Signature]] = None,
prepend_args: Optional[Union[str, Parameter, Iterable[Union[str, Parameter]]]] = None,
append_args: Optional[Union[str, Parameter, Iterable[Union[str, Parameter]]]] = None,
remove_args: Optional[Union[str, Iterable[str]]] = None,
func_name: Optional[str] = None,
inject_as_first_arg: bool = False,
add_source: bool = True,
add_impl: bool = True,
doc: Optional[str] = None,
qualname: Optional[str] = None,
co_name: Optional[str] = None,
module_name: Optional[str] = None,
**attrs: Any) -> Callable:
"""
Creates signature-preserving wrapper function.
Equivalent to wraps(wrapped, **kwargs)(wrapper).
Parameters: Same as wraps() function
Returns:
Callable: Generated wrapper function with preserved/modified signature
"""from makefun import wraps
import time
def target_function(x: int, y: str = "default") -> str:
"""Original function that does important work."""
return f"Result: {x}, {y}"
@wraps(target_function)
def wrapper(x, y):
print(f"Calling with x={x}, y={y}")
result = target_function(x, y)
print(f"Got result: {result}")
return result
# Wrapper preserves original signature and metadata
print(wrapper.__name__) # "target_function"
print(wrapper.__doc__) # "Original function that does important work."
print(wrapper(42)) # Calls with proper signaturefrom makefun import wraps
from inspect import signature
def original_func(data: list) -> int:
"""Process a list of data."""
return len(data)
@wraps(original_func, prepend_args="verbose: bool = False", append_args="log_level: str = 'INFO'")
def enhanced_wrapper(verbose, data, log_level):
if verbose:
print(f"Processing {len(data)} items at {log_level} level")
result = original_func(data)
if verbose:
print(f"Processed {result} items")
return result
print(signature(enhanced_wrapper)) # (verbose: bool = False, data: list, log_level: str = 'INFO') -> int
print(enhanced_wrapper([1, 2, 3])) # 3 (non-verbose)
print(enhanced_wrapper(True, [1, 2, 3, 4], "DEBUG")) # Verbose output + 4from makefun import wraps
def complex_function(data: list, mode: str, debug: bool = False, timeout: int = 30) -> dict:
"""Complex function with many parameters."""
return {
"data_size": len(data),
"mode": mode,
"debug": debug,
"timeout": timeout
}
@wraps(complex_function, remove_args=["debug", "timeout"])
def simplified_wrapper(data, mode):
# Always use specific values for removed parameters
return complex_function(data, mode, debug=True, timeout=60)
# Simplified signature: (data: list, mode: str) -> dict
print(simplified_wrapper([1, 2, 3], "fast"))from makefun import wraps
def legacy_function(a, b, c=None):
"""Legacy function with old-style signature."""
return f"Legacy: a={a}, b={b}, c={c}"
@wraps(legacy_function, new_sig="modern_api(data: dict, options: dict = None) -> str")
def modernized_wrapper(data, options):
# Transform modern parameters to legacy format
a = data.get("value1", 0)
b = data.get("value2", "")
c = options.get("extra") if options else None
return legacy_function(a, b, c)
# New signature: (data: dict, options: dict = None) -> str
result = modernized_wrapper({"value1": 42, "value2": "hello"}, {"extra": "world"})
print(result) # "Legacy: a=42, b=hello, c=world"from makefun import wraps
import time
import functools
def timing_decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
print(f"{func.__name__} took {end_time - start_time:.4f} seconds")
return result
return wrapper
@timing_decorator
def slow_computation(n: int) -> int:
"""Compute sum of squares up to n."""
return sum(i * i for i in range(n))
# Wrapper preserves original signature and metadata
print(slow_computation.__name__) # "slow_computation"
print(slow_computation.__doc__) # "Compute sum of squares up to n."
result = slow_computation(1000) # Prints timing infofrom makefun import wraps
import functools
def cached_function(x: int, y: int, use_cache: bool = True) -> int:
"""Expensive computation."""
print(f"Computing {x} + {y}")
return x + y
@wraps(cached_function, remove_args="use_cache")
@functools.lru_cache(maxsize=128)
def cached_wrapper(x, y):
# Always use caching, remove cache control parameter
return cached_function(x, y, use_cache=True)
# Simplified signature without use_cache parameter
print(cached_wrapper(5, 3)) # Computes and caches
print(cached_wrapper(5, 3)) # Uses cachefrom makefun import wraps
def unsafe_divide(x: float, y: float) -> float:
"""Divide x by y."""
return x / y
@wraps(unsafe_divide, append_args="validate: bool = True")
def safe_wrapper(x, y, validate):
if validate and y == 0:
raise ValueError("Division by zero not allowed")
if validate and not isinstance(x, (int, float)) or not isinstance(y, (int, float)):
raise TypeError("Arguments must be numeric")
return unsafe_divide(x, y)
# Enhanced signature: (x: float, y: float, validate: bool = True) -> float
print(safe_wrapper(10.0, 2.0)) # 5.0
print(safe_wrapper(10.0, 2.0, False)) # 5.0 (no validation)from makefun import wraps
from typing import List, Optional
def basic_processor(items: List[str]) -> List[str]:
"""Basic item processing."""
return [item.upper() for item in items]
@wraps(basic_processor,
prepend_args="log_prefix: str = '[PROC]'",
append_args=["max_items: int = 100", "filter_empty: bool = True"])
def enhanced_processor(log_prefix, items, max_items, filter_empty):
print(f"{log_prefix} Processing up to {max_items} items")
if filter_empty:
items = [item for item in items if item.strip()]
items = items[:max_items]
result = basic_processor(items)
print(f"{log_prefix} Processed {len(result)} items")
return result
# New signature: (log_prefix: str = '[PROC]', items: List[str], max_items: int = 100, filter_empty: bool = True) -> List[str]
result = enhanced_processor(items=["hello", "", "world", "test"])from makefun import wraps
def target_func(x: int) -> int:
return x * 2
# Cannot combine new_sig with other signature modifications
try:
@wraps(target_func, new_sig="(y: int)", prepend_args="z: int")
def invalid_wrapper(z, y):
return target_func(y)
except ValueError as e:
print(f"Configuration error: {e}")
# Invalid parameter name to remove
try:
@wraps(target_func, remove_args="nonexistent_param")
def another_wrapper(x):
return target_func(x)
except KeyError as e:
print(f"Parameter error: {e}")Alternative to decorator syntax for programmatic wrapper creation:
from makefun import create_wrapper
def original(x: int, y: str) -> str:
return f"{x}: {y}"
def my_wrapper(x, y):
print(f"Wrapper called with {x}, {y}")
return original(x, y)
# Create wrapper directly
wrapped_func = create_wrapper(original, my_wrapper)
# Equivalent to:
# @wraps(original)
# def wrapped_func(x, y):
# print(f"Wrapper called with {x}, {y}")
# return original(x, y)
print(wrapped_func(42, "hello"))Key advantages over functools.wraps:
inspect.signature() support__wrapped__ and __signature__ attributesimport functools
from makefun import wraps
def original(x: int, y: str = "default") -> str:
return f"{x}: {y}"
# functools.wraps: basic wrapping
@functools.wraps(original)
def basic_wrapper(*args, **kwargs):
return original(*args, **kwargs)
# makefun.wraps: enhanced wrapping with signature preservation
@wraps(original)
def enhanced_wrapper(x, y):
return original(x, y)
# enhanced_wrapper validates arguments before calling wrapper
# enhanced_wrapper receives arguments as keywords when possible
# enhanced_wrapper maintains exact signature introspectionInstall with Tessl CLI
npx tessl i tessl/pypi-makefun