Source code for smartseeds.decorators
"""
Decorators for SmartSeeds.
Provides utilities for extracting and grouping keyword arguments.
"""
from collections.abc import Callable
from functools import wraps
from typing import Any, TypeVar
from .dict_utils import dictExtract
F = TypeVar("F", bound=Callable[..., Any])
# Constants to avoid recreating dicts
_DEFAULT_EXTRACT_OPTIONS = {"slice_prefix": True, "pop": False, "is_list": False}
_POP_EXTRACT_OPTIONS = {"slice_prefix": True, "pop": True, "is_list": False}
[docs]
def extract_kwargs(
_adapter: str | None = None, _dictkwargs: dict[str, Any] | None = None, **extraction_specs: Any
) -> Callable[[F], F]:
"""A decorator that extracts ``**kwargs`` into sub-families by prefix.
This decorator allows methods to accept kwargs with prefixes (e.g., `logging_level`,
`cache_ttl`) and automatically groups them into separate kwargs dictionaries
(e.g., `logging_kwargs`, `cache_kwargs`).
Args:
_adapter: Optional name of a method on self that will pre-process kwargs.
The adapter method receives kwargs dict and can modify it in-place.
_dictkwargs: Optional dict to use instead of ``**extraction_specs``.
Useful for dynamic extraction specifications.
**extraction_specs: Extraction specifications where keys are prefix names.
Values can be:
- True: Extract and remove (pop=True)
- dict: Custom options (slice_prefix, pop, is_list)
Returns:
Decorated function that extracts kwargs by prefix.
Example:
>>> @extract_kwargs(palette=True, dialog=True, default=True)
... def my_method(self, pane, table=None,
... palette_kwargs=None, dialog_kwargs=None, default_kwargs=None,
... **kwargs):
... pass
...
>>> # Call with prefixed parameters
>>> obj.my_method(palette_height='200px', palette_width='300px',
... dialog_height='250px')
>>> # palette_kwargs={'height': '200px', 'width': '300px'}
>>> # dialog_kwargs={'height': '250px'}
Notes:
- The decorated function MUST have `{prefix}_kwargs` parameters for each prefix
- Reserved keyword 'class' is automatically renamed to '_class'
- Works with both class methods (with self) and standalone functions
- Maintains 100% compatibility with original Genropy implementation
"""
# Use _dictkwargs if provided, otherwise use extraction_specs
# Note: We use a different variable name to avoid shadowing the parameter
specs_to_use = _dictkwargs if _dictkwargs is not None else extraction_specs
def decorator(func: F) -> F:
@wraps(func)
def wrapper(*args: Any, **kwargs: Any) -> Any:
# Check if this is a method (has self) or function
# For methods: self is args[0]
# For functions: no self
has_self = len(args) > 0 and hasattr(args[0].__class__, func.__name__)
self_arg = args[0] if has_self else None
# Call adapter if specified and this is a method
if _adapter and self_arg is not None:
adapter_method = getattr(self_arg, _adapter, None)
if adapter_method is not None:
adapter_method(kwargs)
# Process each extraction specification
for extract_key, extract_value in specs_to_use.items():
grp_key = f"{extract_key}_kwargs"
# Get existing grouped kwargs (if explicitly passed)
current = kwargs.pop(grp_key, None)
if current is None:
current = {}
elif not isinstance(current, dict):
# Edge case: someone passed non-dict, convert to dict
current = {}
# Determine extraction options based on extract_value
if extract_value is True:
# True means: extract and remove from source
extract_options = _POP_EXTRACT_OPTIONS
elif isinstance(extract_value, dict):
# Dict means: custom options
extract_options = {**_DEFAULT_EXTRACT_OPTIONS, **extract_value}
else:
# Default: extract but don't remove from source
extract_options = _DEFAULT_EXTRACT_OPTIONS
# Extract prefixed kwargs
prefix = f"{extract_key}_"
extracted = dictExtract(kwargs, prefix, **extract_options)
# Merge extracted kwargs with current
current.update(extracted)
# Set the grouped kwargs back
# Always set as dict (never None), matching original behavior
kwargs[grp_key] = current
return func(*args, **kwargs)
return wrapper # type: ignore
return decorator