Source code for r2x_core.getters
"""Registry for rules getters."""
from __future__ import annotations
from collections.abc import Callable
from typing import TYPE_CHECKING, Any, TypeAlias, TypeVar
from loguru import logger
from rust_ok import Err, Ok
if TYPE_CHECKING:
from rust_ok import Result
GetterFunc: TypeAlias = Callable[..., Any]
F = TypeVar("F", bound=GetterFunc)
GETTER_REGISTRY: dict[str, GetterFunc] = {}
[docs]
def getter(func: F | None = None, *, name: str | None = None) -> F | Callable[[F], F]:
"""Decorate a callable getter by name.
Can be used in three ways:
1. @getter - Uses function name as registry key
2. @getter() - Uses function name as registry key (parentheses optional)
3. @getter(name="custom_name") - Uses custom name as registry key (name kwarg required when parentheses used)
When using parentheses, the name parameter must be provided as a keyword argument.
"""
def _decorator(f: F) -> F:
"""Decorate function."""
key = name or f.__name__
if key in GETTER_REGISTRY:
raise ValueError(f"Getter '{key}' already registered")
GETTER_REGISTRY[key] = f
logger.debug("Registered getter '{}'", key)
return f
if func is not None:
if not callable(func):
raise TypeError(f"getter() first argument must be callable or None, got {type(func).__name__}")
if name is not None:
raise TypeError(
"Cannot specify 'name' when using @getter without parentheses. Use @getter(name='...') instead"
)
return _decorator(func)
return _decorator
def _preprocess_rule_getters(getters_dict: dict[str, Any]) -> Result[dict[str, Any], TypeError]:
"""Convert string-based getters in a rule into callables."""
from .utils import build_attr_getter
resolved: dict[str, GetterFunc] = {}
for field, getter in getters_dict.items():
if callable(getter):
resolved[field] = getter
elif isinstance(getter, str):
if getter in GETTER_REGISTRY:
resolved[field] = GETTER_REGISTRY[getter]
else:
if "." not in getter:
logger.warning(
"Getter '{}' for field '{}' not found in registry; ensure it is imported before loading rules. "
"Falling back to attribute lookup.",
getter,
field,
)
resolved[field] = build_attr_getter(getter.split("."))
else:
return Err(TypeError(f"Invalid getter type for '{field}': {type(getter).__name__}"))
return Ok(resolved)