Source code for sm_bluesky.common.utils.decorators

import inspect
from collections.abc import Callable
from functools import wraps
from typing import Any, TypeVar, cast, get_type_hints

from bluesky.utils import MsgGenerator

TCallable = TypeVar("TCallable", bound=Callable[..., MsgGenerator])


[docs] def add_default_metadata( func: TCallable, extra_metadata: dict[str, Any] | None = None ) -> TCallable: """ Decorator to add default metadata to the 'md' keyword argument. The defaults can be overridden at run time: any key supplied in 'md' takes precedence over the same key in extra_metadata. If 'md' is not provided, it will be set to a copy of extra_metadata. If 'md' is provided and not None, extra_metadata is used for the keys that 'md' does not define. If 'md' is provided and is None, it will be set to a copy of extra_metadata. """ @wraps(func) def inner( *args, **kwargs, ) -> MsgGenerator: md = kwargs.get("md") if extra_metadata: if md is None: kwargs["md"] = dict(extra_metadata) elif isinstance(md, dict): kwargs["md"] = {**extra_metadata, **md} else: raise ValueError("md is reserved for meta data.") elif md is None: kwargs["md"] = {} return func(*args, **kwargs) return cast(TCallable, inner)
def add_extra_names_to_meta( md: dict[str, Any], key: str, names: list[str] ) -> dict[str, Any]: if key in md: md[key] = md[key] + names return md md[key] = names return md
[docs] def auto_type_cast(func: Callable) -> Callable: """ Casts positional byte arguments to hinted types. Skips 'self' and handles empty strings gracefully. """ @wraps(func) def wrapper(*args) -> Callable: sig = inspect.signature(func) hints = get_type_hints(func) bound_args = sig.bind(*args) bound_args.apply_defaults() for name, value in bound_args.arguments.items(): if isinstance(value, bytes) and name in hints: target_type = hints[name] try: str_val = value.decode("utf-8").strip() if not str_val: default_val = sig.parameters[name].default if default_val is not inspect.Parameter.empty: bound_args.arguments[name] = default_val continue if target_type in (int, float, str): bound_args.arguments[name] = target_type(str_val) except (ValueError, UnicodeDecodeError) as e: raise TypeError(f"Argument '{name}' casting failed: {e}") from e return func(*bound_args.args) return wrapper