CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-makefun

Small library to dynamically create python functions.

Pending
Overview
Eval results
Files

decorators.mddocs/

Signature-based Decorators

Decorators for modifying function signatures while preserving metadata and introspection capabilities. These decorators provide enhanced alternatives to standard Python decorators with precise signature control and comprehensive metadata management.

from typing import Union, Optional, Callable, Any
from inspect import Signature

Capabilities

Signature Modification Decorator

Decorator that changes function signatures, equivalent to create_function but applied as a decorator for cleaner syntax.

def with_signature(func_signature: Union[str, Signature, 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[[Callable], Callable]:
    """
    Decorator to change function signature.
    
    Parameters:
    - func_signature: Union[str, Signature, None]
        New signature specification. Can be:
        - String without 'def': "foo(a, b: int, *args, **kwargs)"
        - Signature object from inspect.signature()
        - None for metadata-only changes (no signature modification)
    - func_name: Optional[str], default None
        Override for __name__ and __qualname__ attributes
    - inject_as_first_arg: bool, default False
        If True, inject created function as first positional argument
    - add_source: bool, default True
        Add __source__ attribute containing generated function source
    - add_impl: bool, default True
        Add __func_impl__ attribute pointing to original function
    - doc: Optional[str], default None
        Docstring for generated function. Defaults to original function's doc
    - qualname: Optional[str], default None
        Qualified name for generated function
    - co_name: Optional[str], default None
        Name for compiled code object
    - module_name: Optional[str], default None
        Module name for generated function
    - **attrs: Any
        Additional attributes to set on generated function
        
    Returns:
    Callable[[Callable], Callable]: Decorator function that takes a callable and returns a callable
    
    Note:
    When func_signature=None, only metadata changes are applied without 
    creating a wrapper. In this case, add_source, add_impl, and 
    inject_as_first_arg should not be used.
    """

Usage Examples

Basic Signature Modification

from makefun import with_signature

@with_signature("greet(name: str, age: int = 25)")
def hello(name, age):
    return f"Hello {name}, you are {age} years old"

# Function now has the specified signature
print(hello("Alice"))        # "Hello Alice, you are 25 years old"
print(hello("Bob", 30))      # "Hello Bob, you are 30 years old"

# Signature is properly reflected in introspection
from inspect import signature
print(signature(hello))      # (name: str, age: int = 25)

Signature from Another Function

from makefun import with_signature
from inspect import signature

def template_func(x: int, y: str, z: float = 1.0) -> str:
    pass

@with_signature(signature(template_func))
def my_func(x, y, z):
    return f"x={x}, y={y}, z={z}"

print(my_func(42, "hello"))  # "x=42, y=hello, z=1.0"

Metadata-Only Changes

from makefun import with_signature

@with_signature(None, 
                func_name="enhanced_processor",
                doc="Enhanced data processing function",
                module_name="data_utils",
                version="2.0")
def process_data(data):
    """Original docstring"""
    return f"Processing {len(data)} items"

print(process_data.__name__)    # "enhanced_processor"
print(process_data.__doc__)     # "Enhanced data processing function"
print(process_data.__module__)  # "data_utils"
print(process_data.version)     # "2.0"

Complex Signature Examples

from makefun import with_signature

# Keyword-only parameters
@with_signature("analyze(data: list, *, method: str, verbose: bool = False)")
def data_analyzer(data, method, verbose):
    result = f"Analyzing {len(data)} items using {method}"
    if verbose:
        result += " (verbose mode)"
    return result

print(data_analyzer([1, 2, 3], method="statistical"))

# Positional-only parameters (Python 3.8+)
@with_signature("compute(x: float, y: float, /, mode: str = 'fast') -> float")
def calculator(x, y, mode):
    if mode == "fast":
        return x + y
    else:
        return x * y + y * x

print(calculator(2.5, 3.5))  # 6.0

# Variable arguments
@with_signature("handler(*args, **kwargs) -> dict")
def request_handler(*args, **kwargs):
    return {"args": args, "kwargs": kwargs}

print(request_handler(1, 2, 3, name="test", value=42))

Function Name from Signature

from makefun import with_signature

# Function name in signature overrides original name
@with_signature("custom_name(value: str) -> str")
def original_name(value):
    return f"Processed: {value}"

print(original_name.__name__)  # "custom_name"
print(original_name("test"))   # "Processed: test"

Advanced Metadata Control

from makefun import with_signature

@with_signature("api_call(endpoint: str, data: dict = None, timeout: int = 30)",
                func_name="make_api_call",
                doc="Make an HTTP API call with timeout and error handling",
                qualname="APIClient.make_api_call",
                module_name="api_client",
                add_source=True,
                add_impl=True,
                api_version="v1.2",
                deprecated=False)
def http_request(endpoint, data, timeout):
    return f"Calling {endpoint} with data={data}, timeout={timeout}"

# All metadata is properly set
print(http_request.__name__)        # "make_api_call"
print(http_request.__qualname__)    # "APIClient.make_api_call"
print(http_request.__module__)      # "api_client"
print(http_request.api_version)     # "v1.2"
print(http_request.deprecated)      # False

# Source code is available
print(http_request.__source__)      # Generated function source
print(http_request.__func_impl__)   # Original function reference

Generator and Async Function Support

from makefun import with_signature
import asyncio

@with_signature("generate_numbers(start: int, end: int, step: int = 1)")
def number_generator(start, end, step):
    for i in range(start, end, step):
        yield f"Number: {i}"

# Generator signature is preserved
for num in number_generator(1, 5):
    print(num)

@with_signature("fetch_data(url: str, headers: dict = None) -> dict")
async def async_fetcher(url, headers):
    await asyncio.sleep(0.1)  # Simulate network call
    return {"url": url, "headers": headers or {}, "status": "success"}

# Async function signature is preserved
result = asyncio.run(async_fetcher("https://api.example.com"))
print(result)

Error Handling

from makefun import with_signature

# Invalid usage: metadata-only mode with add_source=False conflicts
try:
    @with_signature(None, add_source=False, add_impl=False, inject_as_first_arg=True)
    def invalid_func():
        pass
except ValueError as e:
    print(f"Configuration error: {e}")

# Invalid signature format
try:
    @with_signature("invalid syntax here")
    def bad_signature():
        pass
except SyntaxError as e:
    print(f"Signature error: {e}")

Comparison with create_function

The @with_signature decorator is equivalent to create_function but provides cleaner syntax:

from makefun import with_signature, create_function

# These are equivalent:

# Using decorator
@with_signature("func(x: int, y: str) -> str")
def my_func1(x, y):
    return f"{x}: {y}"

# Using create_function
def my_func2_impl(x, y):
    return f"{x}: {y}"

my_func2 = create_function("func(x: int, y: str) -> str", my_func2_impl)

Integration with Type Checkers

The generated functions work properly with static type checkers like mypy:

from makefun import with_signature
from typing import List, Optional

@with_signature("process_items(items: List[str], filter_empty: bool = True) -> List[str]")
def item_processor(items, filter_empty):
    if filter_empty:
        return [item for item in items if item.strip()]
    return items

# Type checker understands the signature
result: List[str] = item_processor(["hello", "", "world"])  # Type-safe

Install with Tessl CLI

npx tessl i tessl/pypi-makefun

docs

compilation.md

decorators.md

function-creation.md

index.md

partial.md

signature-utils.md

wrapping.md

tile.json