feat(pipeline): add unified pipeline architecture with Stage abstraction
- Add engine/pipeline/ module with Stage ABC, PipelineContext, PipelineParams - Stage provides unified interface for sources, effects, displays, cameras - Pipeline class handles DAG-based execution with dependency resolution - PipelinePreset for pre-configured pipelines (demo, poetry, pipeline, etc.) - Add PipelineParams as params layer for animation-driven config - Add StageRegistry for unified stage registration - Add sources_v2.py with DataSource.is_dynamic property - Add animation.py with Preset and AnimationController - Skip ntfy integration tests by default (require -m integration) - Skip e2e tests by default (require -m e2e) - Update pipeline.py with comprehensive introspection methods
This commit is contained in:
127
engine/pipeline/registry.py
Normal file
127
engine/pipeline/registry.py
Normal file
@@ -0,0 +1,127 @@
|
||||
"""
|
||||
Stage registry - Unified registration for all pipeline stages.
|
||||
|
||||
Provides a single registry for sources, effects, displays, and cameras.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from engine.pipeline.core import Stage
|
||||
|
||||
|
||||
class StageRegistry:
|
||||
"""Unified registry for all pipeline stage types."""
|
||||
|
||||
_categories: dict[str, dict[str, type[Stage]]] = {}
|
||||
_discovered: bool = False
|
||||
_instances: dict[str, Stage] = {}
|
||||
|
||||
@classmethod
|
||||
def register(cls, category: str, stage_class: type[Stage]) -> None:
|
||||
"""Register a stage class in a category.
|
||||
|
||||
Args:
|
||||
category: Category name (source, effect, display, camera)
|
||||
stage_class: Stage subclass to register
|
||||
"""
|
||||
if category not in cls._categories:
|
||||
cls._categories[category] = {}
|
||||
|
||||
# Use class name as key
|
||||
key = stage_class.__name__
|
||||
cls._categories[category][key] = stage_class
|
||||
|
||||
@classmethod
|
||||
def get(cls, category: str, name: str) -> type[Stage] | None:
|
||||
"""Get a stage class by category and name."""
|
||||
return cls._categories.get(category, {}).get(name)
|
||||
|
||||
@classmethod
|
||||
def list(cls, category: str) -> list[str]:
|
||||
"""List all stage names in a category."""
|
||||
return list(cls._categories.get(category, {}).keys())
|
||||
|
||||
@classmethod
|
||||
def list_categories(cls) -> list[str]:
|
||||
"""List all registered categories."""
|
||||
return list(cls._categories.keys())
|
||||
|
||||
@classmethod
|
||||
def create(cls, category: str, name: str, **kwargs) -> Stage | None:
|
||||
"""Create a stage instance by category and name."""
|
||||
stage_class = cls.get(category, name)
|
||||
if stage_class:
|
||||
return stage_class(**kwargs)
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def create_instance(cls, stage: Stage | type[Stage], **kwargs) -> Stage:
|
||||
"""Create an instance from a stage class or return as-is."""
|
||||
if isinstance(stage, Stage):
|
||||
return stage
|
||||
if isinstance(stage, type) and issubclass(stage, Stage):
|
||||
return stage(**kwargs)
|
||||
raise TypeError(f"Expected Stage class or instance, got {type(stage)}")
|
||||
|
||||
@classmethod
|
||||
def register_instance(cls, name: str, stage: Stage) -> None:
|
||||
"""Register a stage instance by name."""
|
||||
cls._instances[name] = stage
|
||||
|
||||
@classmethod
|
||||
def get_instance(cls, name: str) -> Stage | None:
|
||||
"""Get a registered stage instance by name."""
|
||||
return cls._instances.get(name)
|
||||
|
||||
|
||||
def discover_stages() -> None:
|
||||
"""Auto-discover and register all stage implementations."""
|
||||
if StageRegistry._discovered:
|
||||
return
|
||||
|
||||
# Import and register all stage implementations
|
||||
try:
|
||||
from engine.sources_v2 import (
|
||||
HeadlinesDataSource,
|
||||
PipelineDataSource,
|
||||
PoetryDataSource,
|
||||
)
|
||||
|
||||
StageRegistry.register("source", HeadlinesDataSource)
|
||||
StageRegistry.register("source", PoetryDataSource)
|
||||
StageRegistry.register("source", PipelineDataSource)
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
try:
|
||||
from engine.effects.types import EffectPlugin # noqa: F401
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
try:
|
||||
from engine.display import Display # noqa: F401
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
StageRegistry._discovered = True
|
||||
|
||||
|
||||
# Convenience functions
|
||||
def register_source(stage_class: type[Stage]) -> None:
|
||||
"""Register a source stage."""
|
||||
StageRegistry.register("source", stage_class)
|
||||
|
||||
|
||||
def register_effect(stage_class: type[Stage]) -> None:
|
||||
"""Register an effect stage."""
|
||||
StageRegistry.register("effect", stage_class)
|
||||
|
||||
|
||||
def register_display(stage_class: type[Stage]) -> None:
|
||||
"""Register a display stage."""
|
||||
StageRegistry.register("display", stage_class)
|
||||
|
||||
|
||||
def register_camera(stage_class: type[Stage]) -> None:
|
||||
"""Register a camera stage."""
|
||||
StageRegistry.register("camera", stage_class)
|
||||
Reference in New Issue
Block a user