@reducer is here
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
from .catalog import ReducerCatalog
|
||||
from .decorator import AuthoredReducer, reducer
|
||||
|
||||
__all__ = [
|
||||
"AuthoredReducer",
|
||||
"ReducerCatalog",
|
||||
"reducer",
|
||||
]
|
||||
@@ -0,0 +1,15 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Protocol, TypeVar
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
ConfigT = TypeVar("ConfigT", bound=BaseModel)
|
||||
|
||||
|
||||
class PlainReducerCallable(Protocol):
|
||||
def __call__(self, current: Any, incoming: Any, /) -> Any: ...
|
||||
|
||||
|
||||
class ConfigReducerCallable(Protocol[ConfigT]):
|
||||
def __call__(self, current: Any, incoming: Any, config: ConfigT, /) -> Any: ...
|
||||
@@ -0,0 +1,30 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
from wf_core import ReducerSpec
|
||||
from wf_core.runtime.ops.merges import ReducerDefinition
|
||||
|
||||
from .decorator import AuthoredReducer
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class ReducerCatalog:
|
||||
"""Collection of authored reducers ready for runtime and inventory use."""
|
||||
|
||||
definitions: dict[str, ReducerDefinition]
|
||||
|
||||
@classmethod
|
||||
def from_reducers(cls, *reducers: AuthoredReducer) -> "ReducerCatalog":
|
||||
return cls(
|
||||
definitions={
|
||||
reducer.definition.spec.name: reducer.definition
|
||||
for reducer in reducers
|
||||
}
|
||||
)
|
||||
|
||||
@property
|
||||
def specs(self) -> dict[str, ReducerSpec]:
|
||||
return {
|
||||
name: definition.spec for name, definition in self.definitions.items()
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable, Mapping
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, TypeVar, cast, overload
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from wf_core import ReducerSpec
|
||||
from wf_core.runtime.ops.merges import ReducerDefinition
|
||||
|
||||
from .callables import ConfigReducerCallable, ConfigT, PlainReducerCallable
|
||||
|
||||
PlainFnT = TypeVar("PlainFnT", bound=PlainReducerCallable)
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class AuthoredReducer:
|
||||
"""Authoring wrapper for one reducer implementation."""
|
||||
|
||||
definition: ReducerDefinition
|
||||
|
||||
|
||||
@overload
|
||||
def reducer(
|
||||
fn: PlainFnT,
|
||||
/,
|
||||
*,
|
||||
name: str | None = None,
|
||||
description: str | None = None,
|
||||
) -> AuthoredReducer: ...
|
||||
|
||||
|
||||
@overload
|
||||
def reducer(
|
||||
*,
|
||||
name: str,
|
||||
description: str | None = None,
|
||||
) -> Callable[[Callable[..., Any]], AuthoredReducer]: ...
|
||||
|
||||
|
||||
@overload
|
||||
def reducer(
|
||||
*,
|
||||
name: str,
|
||||
config_model: type[ConfigT],
|
||||
description: str | None = None,
|
||||
) -> Callable[[Callable[..., Any]], AuthoredReducer]: ...
|
||||
|
||||
|
||||
def reducer(
|
||||
fn: Callable[..., Any] | None = None,
|
||||
/,
|
||||
*,
|
||||
name: str | None = None,
|
||||
config_model: type[BaseModel] | None = None,
|
||||
description: str | None = None,
|
||||
) -> AuthoredReducer | Callable[[Callable[..., Any]], AuthoredReducer]:
|
||||
"""Wrap a Python reducer function as a runtime reducer definition."""
|
||||
|
||||
def decorate(raw: Callable[..., Any]) -> AuthoredReducer:
|
||||
reducer_name = name or raw.__name__
|
||||
reducer_description = description or raw.__doc__
|
||||
if config_model is None:
|
||||
return AuthoredReducer(
|
||||
ReducerDefinition(
|
||||
spec=ReducerSpec(
|
||||
name=reducer_name,
|
||||
description=_clean_doc(reducer_description),
|
||||
),
|
||||
fn=raw,
|
||||
)
|
||||
)
|
||||
|
||||
model_type = config_model
|
||||
|
||||
def runtime_fn(
|
||||
current: Any,
|
||||
incoming: Any,
|
||||
config: Mapping[str, Any],
|
||||
) -> Any:
|
||||
parsed = model_type.model_validate(config)
|
||||
return cast(ConfigReducerCallable[BaseModel], cast(object, raw))(
|
||||
current,
|
||||
incoming,
|
||||
parsed,
|
||||
)
|
||||
|
||||
return AuthoredReducer(
|
||||
ReducerDefinition(
|
||||
spec=ReducerSpec(
|
||||
name=reducer_name,
|
||||
description=_clean_doc(reducer_description),
|
||||
config_schema=config_model.model_json_schema(),
|
||||
),
|
||||
fn=runtime_fn,
|
||||
accepts_config=True,
|
||||
)
|
||||
)
|
||||
|
||||
if fn is not None:
|
||||
return decorate(fn)
|
||||
return decorate
|
||||
|
||||
|
||||
def _clean_doc(doc: str | None) -> str | None:
|
||||
if doc is None:
|
||||
return None
|
||||
cleaned = doc.strip()
|
||||
return cleaned or None
|
||||
Reference in New Issue
Block a user