CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-fastcore

Python supercharged for fastai development

56

1.36x
Overview
Eval results
Files

metaprogramming.mddocs/

Metaprogramming Tools

Advanced metaclasses, function signature manipulation, delegation patterns, and dynamic code generation utilities for building flexible and extensible APIs. The meta module provides powerful tools for creating sophisticated object-oriented designs and dynamic programming patterns.

Capabilities

Metaclasses for Enhanced Object Creation

Specialized metaclasses that modify class creation and object initialization behavior.

class FixSigMeta(type):
    """
    Metaclass that fixes function signatures on classes overriding __new__.
    
    Automatically sets __signature__ attribute based on __init__ method
    to ensure proper introspection and IDE support for classes that
    override __new__ but define parameters in __init__.
    
    Usage:
    class MyClass(metaclass=FixSigMeta):
        def __init__(self, param1, param2): ...
        def __new__(cls, *args, **kwargs): ...
    
    # MyClass.__signature__ automatically reflects __init__ signature
    """
    
    def __new__(cls, name, bases, dict): ...

class PrePostInitMeta(FixSigMeta):
    """
    Metaclass that calls optional __pre_init__ and __post_init__ methods.
    
    Automatically calls __pre_init__ before __init__ and __post_init__
    after __init__ if these methods are defined. Provides hooks for
    setup and cleanup logic around object initialization.
    
    Execution order:
    1. __new__ called
    2. __pre_init__ called (if defined)
    3. __init__ called  
    4. __post_init__ called (if defined)
    
    Usage:
    class MyClass(metaclass=PrePostInitMeta):
        def __pre_init__(self, *args, **kwargs): ...
        def __init__(self, *args, **kwargs): ...
        def __post_init__(self, *args, **kwargs): ...
    """
    
    def __call__(cls, *args, **kwargs): ...

class AutoInit(metaclass=PrePostInitMeta):
    """
    Base class that automatically calls super().__init__ in __pre_init__.
    
    Eliminates need for subclasses to remember to call super().__init__()
    by automatically handling parent class initialization.
    
    Usage:
    class Parent(AutoInit):
        def __init__(self, x): self.x = x
    
    class Child(Parent):
        def __init__(self, x, y):  # No need to call super().__init__
            self.y = y
    """
    
    def __pre_init__(self, *args, **kwargs): ...

class NewChkMeta(FixSigMeta):
    """
    Metaclass to avoid recreating objects passed to constructor.
    
    If an object of the same class is passed to the constructor
    with no additional arguments, returns the object unchanged
    rather than creating a new instance.
    
    Usage:
    class MyClass(metaclass=NewChkMeta): pass
    
    obj1 = MyClass()
    obj2 = MyClass(obj1)  # Returns obj1, not a new instance
    obj3 = MyClass(obj1, extra_arg=True)  # Creates new instance
    """
    
    def __call__(cls, x=None, *args, **kwargs): ...

class BypassNewMeta(FixSigMeta):
    """
    Metaclass that casts objects to class type instead of creating new instances.
    
    If object is of _bypass_type, changes its __class__ to the target class
    rather than creating a new instance. Useful for wrapping existing objects.
    
    Class can define:
    - _bypass_type: Type to cast from
    - _new_meta: Custom creation method
    
    Usage:
    class StringWrapper(metaclass=BypassNewMeta):
        _bypass_type = str
        def upper_method(self): return self.upper()
    
    wrapped = StringWrapper("hello")  # Casts str to StringWrapper
    print(wrapped.upper_method())     # "HELLO"
    """
    
    def __call__(cls, x=None, *args, **kwargs): ...

Function Signature Manipulation

Tools for analyzing, modifying, and working with function signatures dynamically.

def test_sig(f, expected_signature):
    """
    Test that function has expected signature string.
    
    Utility for testing that function signatures match expected format,
    useful for validating decorator effects and signature modifications.
    
    Parameters:
    - f: function to test
    - expected_signature: str, expected signature format
    
    Raises:
    AssertionError: If signatures don't match
    """

def empty2none(param):
    """
    Convert Parameter.empty to None for cleaner handling.
    
    Helper function to normalize inspect.Parameter.empty values
    to None for easier processing and comparison.
    
    Parameters:
    - param: inspect.Parameter value
    
    Returns:
    None if param is Parameter.empty, otherwise param unchanged
    """

def anno_dict(func):
    """
    Get function annotations as dict with empty values normalized.
    
    Extracts __annotations__ from function and converts Parameter.empty
    values to None for cleaner processing.
    
    Parameters:
    - func: function to get annotations from
    
    Returns:
    dict: Annotations with empty values normalized to None
    """

def use_kwargs_dict(keep=False, **kwargs):
    """
    Decorator to replace **kwargs in signature with specific parameters.
    
    Modifies function signature to show specific keyword parameters
    instead of generic **kwargs, improving IDE support and documentation.
    
    Parameters:
    - keep: bool, whether to keep **kwargs in addition to named params
    - **kwargs: parameter names and defaults to add to signature
    
    Returns:
    Decorator function
    
    Usage:
    @use_kwargs_dict(param1=None, param2="default")
    def func(**kwargs): ...
    # Signature shows func(*, param1=None, param2='default')
    """

def use_kwargs(names, keep=False):
    """
    Decorator to replace **kwargs with list of parameter names.
    
    Similar to use_kwargs_dict but takes parameter names as list
    without default values.
    
    Parameters:
    - names: list of str, parameter names to add
    - keep: bool, whether to keep **kwargs
    
    Returns:
    Decorator function
    """

def delegates(to=None, keep=False, but=None):
    """
    Decorator to delegate function parameters from another function.
    
    Copies parameters from target function to decorated function,
    enabling parameter forwarding while maintaining proper signatures
    for IDE support and introspection.
    
    Parameters:
    - to: callable, function to delegate parameters from
    - keep: bool, keep original parameters in addition to delegated ones
    - but: str|list, parameter names to exclude from delegation
    
    Returns:
    Decorator that modifies function signature
    
    Usage:
    def target_func(a, b=1, c=2): pass
    
    @delegates(target_func)
    def wrapper(**kwargs):
        return target_func(**kwargs)
    # wrapper now has signature: wrapper(a, b=1, c=2)
    """

def method(func):
    """
    Convert function to method by adding self parameter.
    
    Modifies function signature to include 'self' as first parameter,
    useful for dynamically creating methods.
    
    Parameters:
    - func: function to convert to method
    
    Returns:
    Function with method signature (including self)
    """

def funcs_kwargs(as_method=False):
    """
    Get kwargs specification for function parameters.
    
    Analyzes function parameters and returns specification
    for **kwargs handling and parameter forwarding.
    
    Parameters:
    - as_method: bool, treat as method (skip self parameter)
    
    Returns:
    dict: Parameter specification for kwargs processing
    """

Dynamic Code Generation

Utilities for generating and modifying code at runtime.

def _mk_param(name, default=None):
    """
    Create inspect.Parameter for signature construction.
    
    Helper function to create Parameter objects for building
    function signatures programmatically.
    
    Parameters:
    - name: str, parameter name
    - default: default value for parameter
    
    Returns:
    inspect.Parameter: Configured parameter object
    """

def _rm_self(signature):
    """
    Remove 'self' parameter from function signature.
    
    Utility to convert method signatures to function signatures
    by removing the self parameter.
    
    Parameters:
    - signature: inspect.Signature with self parameter
    
    Returns:
    inspect.Signature: New signature without self parameter
    """

Usage Examples

Creating Enhanced Classes with Metaclasses

from fastcore.meta import FixSigMeta, PrePostInitMeta, AutoInit

# Class with proper signature introspection
class DataContainer(metaclass=FixSigMeta):
    def __init__(self, data, transform=None, validate=True):
        self.data = data
        self.transform = transform
        self.validate = validate
    
    def __new__(cls, *args, **kwargs):
        # Custom object creation logic
        instance = super().__new__(cls)
        return instance

# Signature is properly reflected for IDE support
import inspect
print(inspect.signature(DataContainer))
# <Signature (data, transform=None, validate=True)>

# Class with initialization hooks
class ConfigurableClass(metaclass=PrePostInitMeta):
    def __pre_init__(self, *args, **kwargs):
        print("Setting up configuration...")
        self.setup_config()
    
    def __init__(self, name, value):
        self.name = name
        self.value = value
    
    def __post_init__(self, *args, **kwargs):
        print("Finalizing setup...")
        self.validate()
    
    def setup_config(self):
        self.config = {"initialized": True}
    
    def validate(self):
        assert hasattr(self, 'name')
        assert hasattr(self, 'value')

obj = ConfigurableClass("test", 42)
# Prints: Setting up configuration...
# Prints: Finalizing setup...

# Simplified inheritance with AutoInit
class BaseProcessor(AutoInit):
    def __init__(self, name):
        self.name = name
        self.processed_count = 0

class AdvancedProcessor(BaseProcessor):
    def __init__(self, name, algorithm):
        # No need to call super().__init__() - handled automatically
        self.algorithm = algorithm
        self.features = []

processor = AdvancedProcessor("nlp", "transformer")
print(processor.name)  # "nlp" - inherited properly

Object Identity and Casting Patterns

from fastcore.meta import NewChkMeta, BypassNewMeta

# Avoid unnecessary object recreation
class SmartContainer(metaclass=NewChkMeta):
    def __init__(self, data=None):
        self.data = data or []
    
    def add(self, item):
        self.data.append(item)

container1 = SmartContainer([1, 2, 3])
container2 = SmartContainer(container1)  # Returns container1, not new instance
print(container1 is container2)  # True

container3 = SmartContainer(container1, extra_data=True)  # Creates new instance
print(container1 is container3)  # False

# Type casting with BypassNewMeta
class EnhancedString(str, metaclass=BypassNewMeta):
    _bypass_type = str
    
    def word_count(self):
        return len(self.split())
    
    def title_case(self):
        return ' '.join(word.capitalize() for word in self.split())

# Cast existing string to EnhancedString
text = "hello world python programming"
enhanced = EnhancedString(text)  # Casts str to EnhancedString
print(enhanced.word_count())     # 4
print(enhanced.title_case())     # "Hello World Python Programming"
print(type(enhanced))            # <class 'EnhancedString'>

# Works with string methods too
print(enhanced.upper())          # "HELLO WORLD PYTHON PROGRAMMING"

Function Signature Delegation and Enhancement

from fastcore.meta import delegates, use_kwargs_dict, use_kwargs
import matplotlib.pyplot as plt

# Delegate parameters from matplotlib.pyplot.plot
@delegates(plt.plot)
def enhanced_plot(*args, title="Enhanced Plot", save_path=None, **kwargs):
    """Enhanced plotting with automatic title and save functionality."""
    fig, ax = plt.subplots()
    ax.plot(*args, **kwargs)
    ax.set_title(title)
    
    if save_path:
        plt.savefig(save_path)
    
    return fig, ax

# Function now has full plt.plot signature plus enhancements
import inspect
sig = inspect.signature(enhanced_plot)
print(sig)  # Shows all matplotlib plot parameters

# Use the enhanced function
fig, ax = enhanced_plot([1, 2, 3], [1, 4, 9], 
                       linestyle='--', color='red', 
                       title="My Data", save_path="plot.png")

# Replace **kwargs with specific parameters for better IDE support
@use_kwargs_dict(method='GET', timeout=30, headers=None)
def api_request(url, **kwargs):
    """Make API request with specified parameters."""
    import requests
    return requests.request(**kwargs, url=url)

# Signature now shows: api_request(url, *, method='GET', timeout=30, headers=None)

# Replace **kwargs with parameter names from list
@use_kwargs(['host', 'port', 'database', 'username', 'password'])
def connect_db(**kwargs):
    """Connect to database with specified parameters."""
    # Connection logic here
    return f"Connected with {kwargs}"

# Signature shows all database connection parameters

Advanced Delegation Patterns

from fastcore.meta import delegates, method
from functools import partial

# Complex delegation with exclusions
class DataFrameWrapper:
    def __init__(self, df):
        self.df = df
    
    @delegates(lambda self: self.df.groupby, but=['by'])
    def smart_groupby(self, columns, **kwargs):
        """Enhanced groupby with automatic column handling."""
        if isinstance(columns, str):
            columns = [columns]
        return self.df.groupby(columns, **kwargs)

# Delegate from multiple sources
class MLPipeline:
    @delegates(lambda: sklearn.model_selection.train_test_split, 
               but=['arrays'])
    @delegates(lambda: sklearn.preprocessing.StandardScaler, 
               keep=True)
    def prepare_data(self, X, y, scale=True, **kwargs):
        """Prepare data with splitting and optional scaling."""
        from sklearn.model_selection import train_test_split
        from sklearn.preprocessing import StandardScaler
        
        # Split data
        split_kwargs = {k: v for k, v in kwargs.items() 
                       if k in ['test_size', 'random_state', 'stratify']}
        X_train, X_test, y_train, y_test = train_test_split(X, y, **split_kwargs)
        
        # Optional scaling
        if scale:
            scaler_kwargs = {k: v for k, v in kwargs.items() 
                            if k in ['copy', 'with_mean', 'with_std']}
            scaler = StandardScaler(**scaler_kwargs)
            X_train = scaler.fit_transform(X_train)
            X_test = scaler.transform(X_test)
        
        return X_train, X_test, y_train, y_test

# Dynamic method creation
def create_property_method(attr_name):
    @method
    def get_property(self):
        return getattr(self, f"_{attr_name}")
    
    return get_property

class DynamicClass:
    def __init__(self, **kwargs):
        for key, value in kwargs.items():
            setattr(self, f"_{key}", value)
            # Create getter method dynamically
            setattr(self, f"get_{key}", 
                   create_property_method(key).__get__(self, type(self)))

obj = DynamicClass(name="test", value=42, items=[1, 2, 3])
print(obj.get_name())    # "test"
print(obj.get_value())   # 42
print(obj.get_items())   # [1, 2, 3]

Signature Analysis and Testing

from fastcore.meta import test_sig, anno_dict, empty2none
import inspect

# Test function signatures
def sample_function(a: int, b: str = "default", c=None) -> bool:
    return True

# Verify signature matches expected format
test_sig(sample_function, "(a: int, b: str = 'default', c=None) -> bool")

# Extract and process annotations
annotations = anno_dict(sample_function)
print(annotations)  # {'a': <class 'int'>, 'b': <class 'str'>, 'c': None, 'return': <class 'bool'>}

# Signature modification validation
def modify_signature(func, new_params):
    """Modify function signature and validate result."""
    sig = inspect.signature(func)
    params = list(sig.parameters.values())
    
    # Add new parameters
    for name, default in new_params.items():
        param = inspect.Parameter(
            name, 
            inspect.Parameter.KEYWORD_ONLY,
            default=default
        )
        params.append(param)
    
    # Create new signature
    new_sig = sig.replace(parameters=params)
    func.__signature__ = new_sig
    return func

# Test the modification
@modify_signature
def test_func(x, y=1):
    pass

modified = modify_signature(test_func, {'extra1': 'default', 'extra2': None})
test_sig(modified, "(x, y=1, *, extra1='default', extra2=None)")

# Parameter processing
def process_parameters(func):
    """Analyze function parameters and provide summary."""
    sig = inspect.signature(func)
    summary = {
        'total_params': len(sig.parameters),
        'required_params': [],
        'optional_params': [],
        'annotations': anno_dict(func)
    }
    
    for name, param in sig.parameters.items():
        if param.default is inspect.Parameter.empty:
            summary['required_params'].append(name)
        else:
            summary['optional_params'].append((name, empty2none(param.default)))
    
    return summary

def example_func(a, b: int, c="default", d: str = None):
    pass

summary = process_parameters(example_func)
print(f"Required: {summary['required_params']}")    # ['a', 'b']
print(f"Optional: {summary['optional_params']}")    # [('c', 'default'), ('d', None)]
print(f"Annotations: {summary['annotations']}")     # {'b': int, 'd': str}

Building Flexible APIs

from fastcore.meta import delegates, use_kwargs_dict, PrePostInitMeta

# Flexible configuration system
class ConfigurableAPI(metaclass=PrePostInitMeta):
    def __pre_init__(self, *args, **kwargs):
        self.config = {}
        self.validators = []
    
    def __init__(self, base_url, **config):
        self.base_url = base_url
        self.config.update(config)
    
    def __post_init__(self, *args, **kwargs):
        self.validate_config()
    
    def validate_config(self):
        for validator in self.validators:
            validator(self.config)
    
    def add_validator(self, validator_func):
        self.validators.append(validator_func)

# API client with delegated requests parameters
import requests

class APIClient(ConfigurableAPI):
    @delegates(requests.get, but=['url'])
    def get(self, endpoint, **kwargs):
        url = f"{self.base_url}/{endpoint.lstrip('/')}"
        return requests.get(url, **kwargs)
    
    @delegates(requests.post, but=['url'])
    def post(self, endpoint, **kwargs):
        url = f"{self.base_url}/{endpoint.lstrip('/')}"
        return requests.post(url, **kwargs)

# Plugin system with dynamic method delegation
class PluginSystem:
    def __init__(self):
        self.plugins = {}
    
    def register_plugin(self, name, plugin_class):
        self.plugins[name] = plugin_class
        
        # Delegate plugin methods to main system
        for method_name in dir(plugin_class):
            if not method_name.startswith('_'):
                method = getattr(plugin_class, method_name)
                if callable(method):
                    self.add_delegated_method(name, method_name, method)
    
    @delegates(lambda: None)  # Will be updated dynamically
    def add_delegated_method(self, plugin_name, method_name, method):
        def wrapper(*args, **kwargs):
            plugin_instance = self.plugins[plugin_name]()
            return getattr(plugin_instance, method_name)(*args, **kwargs)
        
        # Add method with proper delegation
        wrapper = delegates(method)(wrapper)
        setattr(self, f"{plugin_name}_{method_name}", wrapper)

# Usage example
class DatabasePlugin:
    def connect(self, host='localhost', port=5432, database='mydb'):
        return f"Connected to {host}:{port}/{database}"
    
    def query(self, sql, params=None, timeout=30):
        return f"Executed: {sql}"

system = PluginSystem()
system.register_plugin('db', DatabasePlugin)

# Now system has db_connect and db_query methods with proper signatures
result = system.db_connect(host='production.db', port=5433)
query_result = system.db_query("SELECT * FROM users", timeout=60)

Install with Tessl CLI

npx tessl i tessl/pypi-fastcore

docs

collections.md

core-utilities.md

extended.md

index.md

metaprogramming.md

networking.md

parallel.md

system-integration.md

testing.md

xml-html.md

tile.json