Source code for laser_measles.components.utils

"""
Component utilities for the laser-measles package.

This module provides utilities for creating and managing components in the laser-measles package.
The main feature is a decorator that makes it easier to create components with parameters.
"""

from collections.abc import Callable
from functools import wraps
from typing import Any
from typing import TypeVar

from pydantic import BaseModel

from laser_measles.base import BaseComponent

T = TypeVar("T", bound=BaseComponent)
B = TypeVar("B", bound=BaseModel)


[docs] def component(cls: type[T] | None = None, **default_params): # noqa: UP047 """ Decorator for creating components with default parameters. This decorator makes it easier to create components with parameters by: 1. Allowing default parameters to be specified at class definition time 2. Creating a factory function that can be used to create component instances 3. Preserving type hints and docstrings Parameters ---------- cls : Type[BaseComponent], optional The component class to decorate. If None, returns a decorator function. **default_params Default parameters to use when creating the component instance. Returns ------- Union[Type[BaseComponent], Callable] If cls is provided, returns a factory function for creating component instances. If cls is None, returns a decorator function. Examples -------- Basic usage: >>> @component ... class MyComponent(BaseComponent): ... def __init__(self, model, verbose=False, param1=1, param2=2): ... super().__init__(model, verbose) ... self.param1 = param1 ... self.param2 = param2 With default parameters: >>> @component(param1=10, param2=20) ... class MyComponent(BaseComponent): ... def __init__(self, model, verbose=False, param1=1, param2=2): ... super().__init__(model, verbose) ... self.param1 = param1 ... self.param2 = param2 Using the factory: >>> # Create with default parameters >>> MyComponent.create(model) >>> # Create with custom parameters >>> MyComponent.create(model, param1=100, param2=200) """ def decorator(component_cls: type[T]) -> type[T]: # Store the default parameters component_cls._default_params = default_params # type: ignore # Create a factory function for creating instances @wraps(component_cls) def create(model: Any, **kwargs) -> T: # Merge default parameters with provided parameters params = {**default_params, **kwargs} return component_cls(model, **params) # Add the factory function to the class component_cls.create = staticmethod(create) # type: ignore return component_cls # If cls is provided, apply the decorator immediately if cls is not None: return decorator(cls) # Otherwise, return the decorator function return decorator
[docs] def create_component(component_class: type[T], params: type[B] | None = None) -> Callable[[Any, Any], T]: # noqa: UP047 """ Helper function to create a component instance with parameters. This function creates a callable object that will instantiate the component with the given parameters when called by the model. Parameters ---------- component_class : Type[BaseComponent] The component class to instantiate **kwargs Parameters to pass to the component constructor Returns ------- Callable[[Any, Any], BaseComponent] A function that creates the component instance when called by the model Examples -------- >>> model.components = [ ... create_component(MyComponent, params=MyComponentParams), ... AnotherComponent, ... ] """ class ComponentFactory: def __init__(self, component_class: type[T], params: BaseModel | None = None): self.component_class = component_class if params is not None: self.params = params else: self.params = None def __call__(self, model: Any, verbose: bool = False) -> T: return self.component_class(model, params=self.params, verbose=verbose) def __str__(self) -> str: return f"<{self.component_class.__name__} factory>" def __repr__(self) -> str: return f"<{self.component_class.__name__} factory>" return ComponentFactory(component_class, params)