Add minute bar feature pipeline

This commit is contained in:
Yuxuan Yan
2026-06-16 13:57:17 +08:00
parent 17fa75495d
commit 83a006bbe4
19 changed files with 1289 additions and 11 deletions
+79
View File
@@ -0,0 +1,79 @@
"""Registry and factory for daily feature plugins."""
import importlib
import importlib.util
import inspect
from pathlib import Path
from typing import Optional, Type
from pipeline.features.base import BaseFeature
_REGISTRY: dict[str, Type[BaseFeature]] = {}
_builtins_loaded = False
def register_feature(cls: Type[BaseFeature]) -> Type[BaseFeature]:
"""Class decorator that registers a feature under ``BaseFeature.name``."""
if not (isinstance(cls, type) and issubclass(cls, BaseFeature)):
raise TypeError(f"{cls!r} is not a BaseFeature subclass")
key = getattr(cls, "name", "")
if not key:
raise ValueError(f"{cls.__name__} must set a non-empty class attribute `name`")
existing = _REGISTRY.get(key)
if existing is not None and existing is not cls:
raise ValueError(
f"Feature name '{key}' already registered by {existing.__name__}"
)
_REGISTRY[key] = cls
return cls
def available_features() -> list[str]:
"""Sorted names of all registered features (built-ins are loaded lazily)."""
_ensure_builtins()
return sorted(_REGISTRY)
def get_feature(name: str, **params) -> BaseFeature:
"""Instantiate a registered feature by name.
Only parameters accepted by the feature class's ``__init__`` are forwarded.
"""
_ensure_builtins()
if name not in _REGISTRY:
raise KeyError(f"Unknown feature '{name}'. Available: {sorted(_REGISTRY)}")
cls = _REGISTRY[name]
accepted = _accepted_params(cls)
kwargs = params if accepted is None else {k: v for k, v in params.items() if k in accepted}
return cls(**kwargs)
def load_feature_module(spec: str) -> None:
"""Import an external module so its ``@register_feature`` classes register."""
looks_like_file = spec.endswith(".py") or Path(spec).expanduser().exists()
if looks_like_file:
path = Path(spec).expanduser().resolve()
if not path.exists():
raise FileNotFoundError(f"Feature module not found: {path}")
module_spec = importlib.util.spec_from_file_location(path.stem, path)
if module_spec is None or module_spec.loader is None:
raise ImportError(f"Cannot load feature module from {path}")
module = importlib.util.module_from_spec(module_spec)
module_spec.loader.exec_module(module)
else:
importlib.import_module(spec)
def _accepted_params(cls: Type[BaseFeature]) -> Optional[set[str]]:
"""Param names ``cls.__init__`` accepts, or None if it takes ``**kwargs``."""
sig = inspect.signature(cls.__init__)
if any(p.kind is p.VAR_KEYWORD for p in sig.parameters.values()):
return None
return {name for name in sig.parameters if name != "self"}
def _ensure_builtins() -> None:
global _builtins_loaded
if not _builtins_loaded:
import pipeline.features.library # noqa: F401
_builtins_loaded = True