forked from genewildish/Mainline
fix: Update imports to use engine.pipeline instead of engine.pipeline.core
The old engine/pipeline/core.py file was removed as part of the Sideline/Mainline split. All imports that referenced engine.pipeline.core have been updated to use engine.pipeline which re-exports from sideline.pipeline.core. This ensures consistency and avoids duplicate DataType enum instances.
This commit is contained in:
@@ -67,13 +67,13 @@ class PipelineIntrospectionSource(DataSource):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def inlet_types(self) -> set:
|
def inlet_types(self) -> set:
|
||||||
from engine.pipeline.core import DataType
|
from engine.pipeline import DataType
|
||||||
|
|
||||||
return {DataType.NONE}
|
return {DataType.NONE}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def outlet_types(self) -> set:
|
def outlet_types(self) -> set:
|
||||||
from engine.pipeline.core import DataType
|
from engine.pipeline import DataType
|
||||||
|
|
||||||
return {DataType.SOURCE_ITEMS}
|
return {DataType.SOURCE_ITEMS}
|
||||||
|
|
||||||
|
|||||||
@@ -20,13 +20,13 @@ class TintEffect(EffectPlugin):
|
|||||||
# Define inlet types for PureData-style typing
|
# Define inlet types for PureData-style typing
|
||||||
@property
|
@property
|
||||||
def inlet_types(self) -> set:
|
def inlet_types(self) -> set:
|
||||||
from engine.pipeline.core import DataType
|
from engine.pipeline import DataType
|
||||||
|
|
||||||
return {DataType.TEXT_BUFFER}
|
return {DataType.TEXT_BUFFER}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def outlet_types(self) -> set:
|
def outlet_types(self) -> set:
|
||||||
from engine.pipeline.core import DataType
|
from engine.pipeline import DataType
|
||||||
|
|
||||||
return {DataType.TEXT_BUFFER}
|
return {DataType.TEXT_BUFFER}
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ from engine.effects.types import (
|
|||||||
apply_param_bindings,
|
apply_param_bindings,
|
||||||
create_effect_context,
|
create_effect_context,
|
||||||
)
|
)
|
||||||
from engine.pipeline.core import (
|
from engine.pipeline import (
|
||||||
DataType,
|
DataType,
|
||||||
Stage,
|
Stage,
|
||||||
StageConfig,
|
StageConfig,
|
||||||
|
|||||||
@@ -26,6 +26,11 @@ from sideline.pipeline import (
|
|||||||
register_source,
|
register_source,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Also re-export from sideline.core for compatibility
|
||||||
|
from sideline.pipeline.core import (
|
||||||
|
DataType,
|
||||||
|
)
|
||||||
|
|
||||||
# Re-export from engine.pipeline.presets (Mainline-specific)
|
# Re-export from engine.pipeline.presets (Mainline-specific)
|
||||||
from engine.pipeline.presets import (
|
from engine.pipeline.presets import (
|
||||||
DEMO_PRESET,
|
DEMO_PRESET,
|
||||||
@@ -91,4 +96,6 @@ __all__ = [
|
|||||||
"register_effect",
|
"register_effect",
|
||||||
"register_display",
|
"register_display",
|
||||||
"register_camera",
|
"register_camera",
|
||||||
|
# Core types (from sideline)
|
||||||
|
"DataType",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import time
|
import time
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from engine.pipeline.core import DataType, PipelineContext, Stage
|
from engine.pipeline import DataType, PipelineContext, Stage
|
||||||
|
|
||||||
|
|
||||||
class CameraClockStage(Stage):
|
class CameraClockStage(Stage):
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ This module provides adapters that wrap existing components
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from engine.data_sources import SourceItem
|
from engine.data_sources import SourceItem
|
||||||
from engine.pipeline.core import DataType, PipelineContext, Stage
|
from engine.pipeline import DataType, PipelineContext, Stage
|
||||||
|
|
||||||
|
|
||||||
class DataSourceStage(Stage):
|
class DataSourceStage(Stage):
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from engine.pipeline.core import PipelineContext, Stage
|
from engine.pipeline import PipelineContext, Stage
|
||||||
|
|
||||||
|
|
||||||
class DisplayStage(Stage):
|
class DisplayStage(Stage):
|
||||||
@@ -59,13 +59,13 @@ class DisplayStage(Stage):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def inlet_types(self) -> set:
|
def inlet_types(self) -> set:
|
||||||
from engine.pipeline.core import DataType
|
from engine.pipeline import DataType
|
||||||
|
|
||||||
return {DataType.TEXT_BUFFER} # Display consumes rendered text
|
return {DataType.TEXT_BUFFER} # Display consumes rendered text
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def outlet_types(self) -> set:
|
def outlet_types(self) -> set:
|
||||||
from engine.pipeline.core import DataType
|
from engine.pipeline import DataType
|
||||||
|
|
||||||
return {DataType.NONE} # Display is a terminal stage (no output)
|
return {DataType.NONE} # Display is a terminal stage (no output)
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from engine.pipeline.core import PipelineContext, Stage
|
from engine.pipeline import PipelineContext, Stage
|
||||||
|
|
||||||
|
|
||||||
class EffectPluginStage(Stage):
|
class EffectPluginStage(Stage):
|
||||||
@@ -69,13 +69,13 @@ class EffectPluginStage(Stage):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def inlet_types(self) -> set:
|
def inlet_types(self) -> set:
|
||||||
from engine.pipeline.core import DataType
|
from engine.pipeline import DataType
|
||||||
|
|
||||||
return {DataType.TEXT_BUFFER}
|
return {DataType.TEXT_BUFFER}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def outlet_types(self) -> set:
|
def outlet_types(self) -> set:
|
||||||
from engine.pipeline.core import DataType
|
from engine.pipeline import DataType
|
||||||
|
|
||||||
return {DataType.TEXT_BUFFER}
|
return {DataType.TEXT_BUFFER}
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ Wraps pipeline stages to capture frames for animation report generation.
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from engine.display.backends.animation_report import AnimationReportDisplay
|
from engine.display.backends.animation_report import AnimationReportDisplay
|
||||||
from engine.pipeline.core import PipelineContext, Stage
|
from engine.pipeline import PipelineContext, Stage
|
||||||
|
|
||||||
|
|
||||||
class FrameCaptureStage(Stage):
|
class FrameCaptureStage(Stage):
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ from datetime import datetime
|
|||||||
|
|
||||||
from engine import config
|
from engine import config
|
||||||
from engine.effects.legacy import vis_trunc
|
from engine.effects.legacy import vis_trunc
|
||||||
from engine.pipeline.core import DataType, PipelineContext, Stage
|
from engine.pipeline import DataType, PipelineContext, Stage
|
||||||
from engine.render.blocks import big_wrap
|
from engine.render.blocks import big_wrap
|
||||||
from engine.render.gradient import msg_gradient
|
from engine.render.gradient import msg_gradient
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ different ANSI positioning approaches:
|
|||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from engine.pipeline.core import DataType, PipelineContext, Stage
|
from engine.pipeline import DataType, PipelineContext, Stage
|
||||||
|
|
||||||
|
|
||||||
class PositioningMode(Enum):
|
class PositioningMode(Enum):
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ from typing import Any
|
|||||||
|
|
||||||
import engine.render
|
import engine.render
|
||||||
from engine.data_sources import SourceItem
|
from engine.data_sources import SourceItem
|
||||||
from engine.pipeline.core import DataType, PipelineContext, Stage
|
from engine.pipeline import DataType, PipelineContext, Stage
|
||||||
|
|
||||||
|
|
||||||
def estimate_simple_height(text: str, width: int) -> int:
|
def estimate_simple_height(text: str, width: int) -> int:
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import time
|
|||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from engine.pipeline.core import PipelineContext, Stage, StageError, StageResult
|
from engine.pipeline import PipelineContext, Stage, StageError, StageResult
|
||||||
from engine.pipeline.params import PipelineParams
|
from engine.pipeline.params import PipelineParams
|
||||||
from engine.pipeline.registry import StageRegistry
|
from engine.pipeline.registry import StageRegistry
|
||||||
|
|
||||||
@@ -640,7 +640,7 @@ class Pipeline:
|
|||||||
|
|
||||||
Raises StageError if type mismatch is detected.
|
Raises StageError if type mismatch is detected.
|
||||||
"""
|
"""
|
||||||
from engine.pipeline.core import DataType
|
from engine.pipeline import DataType
|
||||||
|
|
||||||
errors: list[str] = []
|
errors: list[str] = []
|
||||||
|
|
||||||
|
|||||||
@@ -1,321 +0,0 @@
|
|||||||
"""
|
|
||||||
Pipeline core - Unified Stage abstraction and PipelineContext.
|
|
||||||
|
|
||||||
This module provides the foundation for a clean, dependency-managed pipeline:
|
|
||||||
- Stage: Base class for all pipeline components (sources, effects, displays, cameras)
|
|
||||||
- PipelineContext: Dependency injection context for runtime data exchange
|
|
||||||
- Capability system: Explicit capability declarations with duck-typing support
|
|
||||||
- DataType: PureData-style inlet/outlet typing for validation
|
|
||||||
"""
|
|
||||||
|
|
||||||
from abc import ABC, abstractmethod
|
|
||||||
from collections.abc import Callable
|
|
||||||
from dataclasses import dataclass, field
|
|
||||||
from enum import Enum, auto
|
|
||||||
from typing import TYPE_CHECKING, Any
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
|
||||||
from engine.pipeline.params import PipelineParams
|
|
||||||
|
|
||||||
|
|
||||||
class DataType(Enum):
|
|
||||||
"""PureData-style data types for inlet/outlet validation.
|
|
||||||
|
|
||||||
Each type represents a specific data format that flows through the pipeline.
|
|
||||||
This enables compile-time-like validation of connections.
|
|
||||||
|
|
||||||
Examples:
|
|
||||||
SOURCE_ITEMS: List[SourceItem] - raw items from sources
|
|
||||||
ITEM_TUPLES: List[tuple] - (title, source, timestamp) tuples
|
|
||||||
TEXT_BUFFER: List[str] - rendered ANSI buffer for display
|
|
||||||
RAW_TEXT: str - raw text strings
|
|
||||||
PIL_IMAGE: PIL Image object
|
|
||||||
"""
|
|
||||||
|
|
||||||
SOURCE_ITEMS = auto() # List[SourceItem] - from DataSource
|
|
||||||
ITEM_TUPLES = auto() # List[tuple] - (title, source, ts)
|
|
||||||
TEXT_BUFFER = auto() # List[str] - ANSI buffer
|
|
||||||
RAW_TEXT = auto() # str - raw text
|
|
||||||
PIL_IMAGE = auto() # PIL Image object
|
|
||||||
ANY = auto() # Accepts any type
|
|
||||||
NONE = auto() # No data (terminator)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class StageConfig:
|
|
||||||
"""Configuration for a single stage."""
|
|
||||||
|
|
||||||
name: str
|
|
||||||
category: str
|
|
||||||
enabled: bool = True
|
|
||||||
optional: bool = False
|
|
||||||
params: dict[str, Any] = field(default_factory=dict)
|
|
||||||
|
|
||||||
|
|
||||||
class Stage(ABC):
|
|
||||||
"""Abstract base class for all pipeline stages.
|
|
||||||
|
|
||||||
A Stage is a single component in the rendering pipeline. Stages can be:
|
|
||||||
- Sources: Data providers (headlines, poetry, pipeline viz)
|
|
||||||
- Effects: Post-processors (noise, fade, glitch, hud)
|
|
||||||
- Displays: Output backends (terminal, pygame, websocket)
|
|
||||||
- Cameras: Viewport controllers (vertical, horizontal, omni)
|
|
||||||
- Overlays: UI elements that compose on top (HUD)
|
|
||||||
|
|
||||||
Stages declare:
|
|
||||||
- capabilities: What they provide to other stages
|
|
||||||
- dependencies: What they need from other stages
|
|
||||||
- stage_type: Category of stage (source, effect, overlay, display)
|
|
||||||
- render_order: Execution order within category
|
|
||||||
- is_overlay: If True, output is composited on top, not passed downstream
|
|
||||||
|
|
||||||
Duck-typing is supported: any class with the required methods can act as a Stage.
|
|
||||||
"""
|
|
||||||
|
|
||||||
name: str
|
|
||||||
category: str # "source", "effect", "overlay", "display", "camera"
|
|
||||||
optional: bool = False # If True, pipeline continues even if stage fails
|
|
||||||
|
|
||||||
@property
|
|
||||||
def stage_type(self) -> str:
|
|
||||||
"""Category of stage for ordering.
|
|
||||||
|
|
||||||
Valid values: "source", "effect", "overlay", "display", "camera"
|
|
||||||
Defaults to category for backwards compatibility.
|
|
||||||
"""
|
|
||||||
return self.category
|
|
||||||
|
|
||||||
@property
|
|
||||||
def render_order(self) -> int:
|
|
||||||
"""Execution order within stage_type group.
|
|
||||||
|
|
||||||
Higher values execute later. Useful for ordering overlays
|
|
||||||
or effects that need specific execution order.
|
|
||||||
"""
|
|
||||||
return 0
|
|
||||||
|
|
||||||
@property
|
|
||||||
def is_overlay(self) -> bool:
|
|
||||||
"""If True, this stage's output is composited on top of the buffer.
|
|
||||||
|
|
||||||
Overlay stages don't pass their output to the next stage.
|
|
||||||
Instead, their output is layered on top of the final buffer.
|
|
||||||
Use this for HUD, status displays, and similar UI elements.
|
|
||||||
"""
|
|
||||||
return False
|
|
||||||
|
|
||||||
@property
|
|
||||||
def inlet_types(self) -> set[DataType]:
|
|
||||||
"""Return set of data types this stage accepts.
|
|
||||||
|
|
||||||
PureData-style inlet typing. If the connected upstream stage's
|
|
||||||
outlet_type is not in this set, the pipeline will raise an error.
|
|
||||||
|
|
||||||
Examples:
|
|
||||||
- Source stages: {DataType.NONE} (no input needed)
|
|
||||||
- Transform stages: {DataType.ITEM_TUPLES, DataType.TEXT_BUFFER}
|
|
||||||
- Display stages: {DataType.TEXT_BUFFER}
|
|
||||||
"""
|
|
||||||
return {DataType.ANY}
|
|
||||||
|
|
||||||
@property
|
|
||||||
def outlet_types(self) -> set[DataType]:
|
|
||||||
"""Return set of data types this stage produces.
|
|
||||||
|
|
||||||
PureData-style outlet typing. Downstream stages must accept
|
|
||||||
this type in their inlet_types.
|
|
||||||
|
|
||||||
Examples:
|
|
||||||
- Source stages: {DataType.SOURCE_ITEMS}
|
|
||||||
- Transform stages: {DataType.TEXT_BUFFER}
|
|
||||||
- Display stages: {DataType.NONE} (consumes data)
|
|
||||||
"""
|
|
||||||
return {DataType.ANY}
|
|
||||||
|
|
||||||
@property
|
|
||||||
def capabilities(self) -> set[str]:
|
|
||||||
"""Return set of capabilities this stage provides.
|
|
||||||
|
|
||||||
Examples:
|
|
||||||
- "source.headlines"
|
|
||||||
- "effect.noise"
|
|
||||||
- "display.output"
|
|
||||||
- "camera"
|
|
||||||
"""
|
|
||||||
return {f"{self.category}.{self.name}"}
|
|
||||||
|
|
||||||
@property
|
|
||||||
def dependencies(self) -> set[str]:
|
|
||||||
"""Return set of capability names this stage needs.
|
|
||||||
|
|
||||||
Examples:
|
|
||||||
- {"display.output"}
|
|
||||||
- {"source.headlines"}
|
|
||||||
- {"camera"}
|
|
||||||
"""
|
|
||||||
return set()
|
|
||||||
|
|
||||||
@property
|
|
||||||
def stage_dependencies(self) -> set[str]:
|
|
||||||
"""Return set of stage names this stage must connect to directly.
|
|
||||||
|
|
||||||
This allows explicit stage-to-stage dependencies, useful for enforcing
|
|
||||||
pipeline structure when capability matching alone is insufficient.
|
|
||||||
|
|
||||||
Examples:
|
|
||||||
- {"viewport_filter"} # Must connect to viewport_filter stage
|
|
||||||
- {"camera_update"} # Must connect to camera_update stage
|
|
||||||
|
|
||||||
NOTE: These are stage names (as added to pipeline), not capabilities.
|
|
||||||
"""
|
|
||||||
return set()
|
|
||||||
|
|
||||||
def init(self, ctx: "PipelineContext") -> bool:
|
|
||||||
"""Initialize stage with pipeline context.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
ctx: PipelineContext for accessing services
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
True if initialization succeeded, False otherwise
|
|
||||||
"""
|
|
||||||
return True
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def process(self, data: Any, ctx: "PipelineContext") -> Any:
|
|
||||||
"""Process input data and return output.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
data: Input data from previous stage (or initial data for first stage)
|
|
||||||
ctx: PipelineContext for accessing services and state
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Processed data for next stage
|
|
||||||
"""
|
|
||||||
...
|
|
||||||
|
|
||||||
def cleanup(self) -> None: # noqa: B027
|
|
||||||
"""Clean up resources when pipeline shuts down."""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def get_config(self) -> StageConfig:
|
|
||||||
"""Return current configuration of this stage."""
|
|
||||||
return StageConfig(
|
|
||||||
name=self.name,
|
|
||||||
category=self.category,
|
|
||||||
optional=self.optional,
|
|
||||||
)
|
|
||||||
|
|
||||||
def set_enabled(self, enabled: bool) -> None:
|
|
||||||
"""Enable or disable this stage."""
|
|
||||||
self._enabled = enabled # type: ignore[attr-defined]
|
|
||||||
|
|
||||||
def is_enabled(self) -> bool:
|
|
||||||
"""Check if stage is enabled."""
|
|
||||||
return getattr(self, "_enabled", True)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class StageResult:
|
|
||||||
"""Result of stage processing, including success/failure info."""
|
|
||||||
|
|
||||||
success: bool
|
|
||||||
data: Any
|
|
||||||
error: str | None = None
|
|
||||||
stage_name: str = ""
|
|
||||||
|
|
||||||
|
|
||||||
class PipelineContext:
|
|
||||||
"""Dependency injection context passed through the pipeline.
|
|
||||||
|
|
||||||
Provides:
|
|
||||||
- services: Named services (display, config, event_bus, etc.)
|
|
||||||
- state: Runtime state shared between stages
|
|
||||||
- params: PipelineParams for animation-driven config
|
|
||||||
|
|
||||||
Services can be injected at construction time or lazily resolved.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
services: dict[str, Any] | None = None,
|
|
||||||
initial_state: dict[str, Any] | None = None,
|
|
||||||
):
|
|
||||||
self.services: dict[str, Any] = services or {}
|
|
||||||
self.state: dict[str, Any] = initial_state or {}
|
|
||||||
self._params: PipelineParams | None = None
|
|
||||||
|
|
||||||
# Lazy resolvers for common services
|
|
||||||
self._lazy_resolvers: dict[str, Callable[[], Any]] = {
|
|
||||||
"config": self._resolve_config,
|
|
||||||
"event_bus": self._resolve_event_bus,
|
|
||||||
}
|
|
||||||
|
|
||||||
def _resolve_config(self) -> Any:
|
|
||||||
from engine.config import get_config
|
|
||||||
|
|
||||||
return get_config()
|
|
||||||
|
|
||||||
def _resolve_event_bus(self) -> Any:
|
|
||||||
from engine.eventbus import get_event_bus
|
|
||||||
|
|
||||||
return get_event_bus()
|
|
||||||
|
|
||||||
def get(self, key: str, default: Any = None) -> Any:
|
|
||||||
"""Get a service or state value by key.
|
|
||||||
|
|
||||||
First checks services, then state, then lazy resolution.
|
|
||||||
"""
|
|
||||||
if key in self.services:
|
|
||||||
return self.services[key]
|
|
||||||
if key in self.state:
|
|
||||||
return self.state[key]
|
|
||||||
if key in self._lazy_resolvers:
|
|
||||||
try:
|
|
||||||
return self._lazy_resolvers[key]()
|
|
||||||
except Exception:
|
|
||||||
return default
|
|
||||||
return default
|
|
||||||
|
|
||||||
def set(self, key: str, value: Any) -> None:
|
|
||||||
"""Set a service or state value."""
|
|
||||||
self.services[key] = value
|
|
||||||
|
|
||||||
def set_state(self, key: str, value: Any) -> None:
|
|
||||||
"""Set a runtime state value."""
|
|
||||||
self.state[key] = value
|
|
||||||
|
|
||||||
def get_state(self, key: str, default: Any = None) -> Any:
|
|
||||||
"""Get a runtime state value."""
|
|
||||||
return self.state.get(key, default)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def params(self) -> "PipelineParams | None":
|
|
||||||
"""Get current pipeline params (for animation)."""
|
|
||||||
return self._params
|
|
||||||
|
|
||||||
@params.setter
|
|
||||||
def params(self, value: "PipelineParams") -> None:
|
|
||||||
"""Set pipeline params (from animation controller)."""
|
|
||||||
self._params = value
|
|
||||||
|
|
||||||
def has_capability(self, capability: str) -> bool:
|
|
||||||
"""Check if a capability is available."""
|
|
||||||
return capability in self.services or capability in self._lazy_resolvers
|
|
||||||
|
|
||||||
|
|
||||||
class StageError(Exception):
|
|
||||||
"""Raised when a stage fails to process."""
|
|
||||||
|
|
||||||
def __init__(self, stage_name: str, message: str, is_optional: bool = False):
|
|
||||||
self.stage_name = stage_name
|
|
||||||
self.message = message
|
|
||||||
self.is_optional = is_optional
|
|
||||||
super().__init__(f"Stage '{stage_name}' failed: {message}")
|
|
||||||
|
|
||||||
|
|
||||||
def create_stage_error(
|
|
||||||
stage_name: str, error: Exception, is_optional: bool = False
|
|
||||||
) -> StageError:
|
|
||||||
"""Helper to create a StageError from an exception."""
|
|
||||||
return StageError(stage_name, str(error), is_optional)
|
|
||||||
@@ -8,10 +8,10 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from typing import TYPE_CHECKING, Any, TypeVar
|
from typing import TYPE_CHECKING, Any, TypeVar
|
||||||
|
|
||||||
from engine.pipeline.core import Stage
|
from engine.pipeline import Stage
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from engine.pipeline.core import Stage
|
from engine.pipeline import Stage
|
||||||
|
|
||||||
T = TypeVar("T")
|
T = TypeVar("T")
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ from dataclasses import dataclass
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from engine.display import _strip_ansi
|
from engine.display import _strip_ansi
|
||||||
from engine.pipeline.core import DataType, PipelineContext, Stage
|
from engine.pipeline import DataType, PipelineContext, Stage
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ from dataclasses import dataclass
|
|||||||
from typing import TYPE_CHECKING, Any
|
from typing import TYPE_CHECKING, Any
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from engine.pipeline.core import PipelineContext
|
from engine.pipeline import PipelineContext
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -166,13 +166,13 @@ class SensorStage:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def inlet_types(self) -> set:
|
def inlet_types(self) -> set:
|
||||||
from engine.pipeline.core import DataType
|
from engine.pipeline import DataType
|
||||||
|
|
||||||
return {DataType.ANY}
|
return {DataType.ANY}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def outlet_types(self) -> set:
|
def outlet_types(self) -> set:
|
||||||
from engine.pipeline.core import DataType
|
from engine.pipeline import DataType
|
||||||
|
|
||||||
return {DataType.ANY}
|
return {DataType.ANY}
|
||||||
|
|
||||||
|
|||||||
@@ -423,7 +423,7 @@ class Pipeline:
|
|||||||
List of stages that were injected
|
List of stages that were injected
|
||||||
"""
|
"""
|
||||||
from sideline.camera import Camera
|
from sideline.camera import Camera
|
||||||
from sideline.data_sources.sources import EmptyDataSource
|
from engine.data_sources.sources import EmptyDataSource
|
||||||
from sideline.display import DisplayRegistry
|
from sideline.display import DisplayRegistry
|
||||||
from sideline.pipeline.adapters import (
|
from sideline.pipeline.adapters import (
|
||||||
CameraClockStage,
|
CameraClockStage,
|
||||||
@@ -1033,7 +1033,7 @@ def create_pipeline_from_params(params: PipelineParams) -> Pipeline:
|
|||||||
|
|
||||||
def create_default_pipeline() -> Pipeline:
|
def create_default_pipeline() -> Pipeline:
|
||||||
"""Create a default pipeline with all standard components."""
|
"""Create a default pipeline with all standard components."""
|
||||||
from sideline.data_sources.sources import HeadlinesDataSource
|
from engine.data_sources.sources import HeadlinesDataSource
|
||||||
from sideline.pipeline.adapters import (
|
from sideline.pipeline.adapters import (
|
||||||
DataSourceStage,
|
DataSourceStage,
|
||||||
SourceItemsToBufferStage,
|
SourceItemsToBufferStage,
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ from engine.pipeline import (
|
|||||||
create_default_pipeline,
|
create_default_pipeline,
|
||||||
discover_stages,
|
discover_stages,
|
||||||
)
|
)
|
||||||
from engine.pipeline.core import DataType, StageError
|
from engine.pipeline import DataType, StageError
|
||||||
|
|
||||||
|
|
||||||
class TestStageRegistry:
|
class TestStageRegistry:
|
||||||
@@ -118,7 +118,7 @@ class TestPipeline:
|
|||||||
|
|
||||||
def test_build_resolves_dependencies(self):
|
def test_build_resolves_dependencies(self):
|
||||||
"""Pipeline.build resolves execution order."""
|
"""Pipeline.build resolves execution order."""
|
||||||
from engine.pipeline.core import DataType
|
from engine.pipeline import DataType
|
||||||
|
|
||||||
pipeline = Pipeline()
|
pipeline = Pipeline()
|
||||||
mock_source = MagicMock(spec=Stage)
|
mock_source = MagicMock(spec=Stage)
|
||||||
@@ -153,7 +153,7 @@ class TestPipeline:
|
|||||||
|
|
||||||
def test_execute_runs_stages(self):
|
def test_execute_runs_stages(self):
|
||||||
"""Pipeline.execute runs all stages in order."""
|
"""Pipeline.execute runs all stages in order."""
|
||||||
from engine.pipeline.core import DataType
|
from engine.pipeline import DataType
|
||||||
|
|
||||||
pipeline = Pipeline()
|
pipeline = Pipeline()
|
||||||
|
|
||||||
@@ -283,7 +283,7 @@ class TestCapabilityBasedDependencies:
|
|||||||
def test_capability_wildcard_resolution(self):
|
def test_capability_wildcard_resolution(self):
|
||||||
"""Pipeline resolves dependencies using wildcard capabilities."""
|
"""Pipeline resolves dependencies using wildcard capabilities."""
|
||||||
from engine.pipeline.controller import Pipeline
|
from engine.pipeline.controller import Pipeline
|
||||||
from engine.pipeline.core import Stage
|
from engine.pipeline import Stage
|
||||||
|
|
||||||
class SourceStage(Stage):
|
class SourceStage(Stage):
|
||||||
name = "headlines"
|
name = "headlines"
|
||||||
@@ -329,7 +329,7 @@ class TestCapabilityBasedDependencies:
|
|||||||
def test_missing_capability_raises_error(self):
|
def test_missing_capability_raises_error(self):
|
||||||
"""Pipeline raises error when capability is missing."""
|
"""Pipeline raises error when capability is missing."""
|
||||||
from engine.pipeline.controller import Pipeline
|
from engine.pipeline.controller import Pipeline
|
||||||
from engine.pipeline.core import Stage, StageError
|
from engine.pipeline import Stage, StageError
|
||||||
|
|
||||||
class RenderStage(Stage):
|
class RenderStage(Stage):
|
||||||
name = "render"
|
name = "render"
|
||||||
@@ -359,7 +359,7 @@ class TestCapabilityBasedDependencies:
|
|||||||
def test_multiple_stages_same_capability(self):
|
def test_multiple_stages_same_capability(self):
|
||||||
"""Pipeline uses first registered stage for capability."""
|
"""Pipeline uses first registered stage for capability."""
|
||||||
from engine.pipeline.controller import Pipeline
|
from engine.pipeline.controller import Pipeline
|
||||||
from engine.pipeline.core import Stage
|
from engine.pipeline import Stage
|
||||||
|
|
||||||
class SourceA(Stage):
|
class SourceA(Stage):
|
||||||
name = "headlines"
|
name = "headlines"
|
||||||
@@ -458,8 +458,15 @@ class TestPipelineContext:
|
|||||||
"""PipelineContext resolves lazy services."""
|
"""PipelineContext resolves lazy services."""
|
||||||
ctx = PipelineContext()
|
ctx = PipelineContext()
|
||||||
|
|
||||||
|
# Register a lazy service resolver
|
||||||
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
|
mock_config = MagicMock()
|
||||||
|
ctx.register_service("config", lambda: mock_config)
|
||||||
|
|
||||||
config = ctx.get("config")
|
config = ctx.get("config")
|
||||||
assert config is not None
|
assert config is not None
|
||||||
|
assert config == mock_config
|
||||||
|
|
||||||
def test_has_capability(self):
|
def test_has_capability(self):
|
||||||
"""PipelineContext.has_capability checks for services."""
|
"""PipelineContext.has_capability checks for services."""
|
||||||
@@ -608,7 +615,7 @@ class TestStageAdapters:
|
|||||||
"""DisplayStage.init initializes display."""
|
"""DisplayStage.init initializes display."""
|
||||||
from engine.display.backends.null import NullDisplay
|
from engine.display.backends.null import NullDisplay
|
||||||
from engine.pipeline.adapters import DisplayStage
|
from engine.pipeline.adapters import DisplayStage
|
||||||
from engine.pipeline.core import PipelineContext
|
from engine.pipeline import PipelineContext
|
||||||
from engine.pipeline.params import PipelineParams
|
from engine.pipeline.params import PipelineParams
|
||||||
|
|
||||||
display = NullDisplay()
|
display = NullDisplay()
|
||||||
@@ -623,7 +630,7 @@ class TestStageAdapters:
|
|||||||
"""DisplayStage.process forwards to display."""
|
"""DisplayStage.process forwards to display."""
|
||||||
from engine.display.backends.null import NullDisplay
|
from engine.display.backends.null import NullDisplay
|
||||||
from engine.pipeline.adapters import DisplayStage
|
from engine.pipeline.adapters import DisplayStage
|
||||||
from engine.pipeline.core import PipelineContext
|
from engine.pipeline import PipelineContext
|
||||||
from engine.pipeline.params import PipelineParams
|
from engine.pipeline.params import PipelineParams
|
||||||
|
|
||||||
display = NullDisplay()
|
display = NullDisplay()
|
||||||
@@ -640,7 +647,7 @@ class TestStageAdapters:
|
|||||||
"""CameraStage applies camera transform."""
|
"""CameraStage applies camera transform."""
|
||||||
from engine.camera import Camera, CameraMode
|
from engine.camera import Camera, CameraMode
|
||||||
from engine.pipeline.adapters import CameraStage
|
from engine.pipeline.adapters import CameraStage
|
||||||
from engine.pipeline.core import PipelineContext
|
from engine.pipeline import PipelineContext
|
||||||
|
|
||||||
camera = Camera(mode=CameraMode.FEED)
|
camera = Camera(mode=CameraMode.FEED)
|
||||||
stage = CameraStage(camera, name="vertical")
|
stage = CameraStage(camera, name="vertical")
|
||||||
@@ -658,7 +665,7 @@ class TestStageAdapters:
|
|||||||
"""
|
"""
|
||||||
from engine.camera import Camera, CameraMode
|
from engine.camera import Camera, CameraMode
|
||||||
from engine.pipeline.adapters import CameraStage
|
from engine.pipeline.adapters import CameraStage
|
||||||
from engine.pipeline.core import PipelineContext
|
from engine.pipeline import PipelineContext
|
||||||
from engine.pipeline.params import PipelineParams
|
from engine.pipeline.params import PipelineParams
|
||||||
|
|
||||||
camera = Camera(mode=CameraMode.FEED)
|
camera = Camera(mode=CameraMode.FEED)
|
||||||
@@ -696,7 +703,7 @@ class TestDataSourceStage:
|
|||||||
|
|
||||||
from engine.data_sources.sources import HeadlinesDataSource
|
from engine.data_sources.sources import HeadlinesDataSource
|
||||||
from engine.pipeline.adapters import DataSourceStage
|
from engine.pipeline.adapters import DataSourceStage
|
||||||
from engine.pipeline.core import PipelineContext
|
from engine.pipeline import PipelineContext
|
||||||
|
|
||||||
mock_items = [
|
mock_items = [
|
||||||
("Test Headline 1", "TestSource", "12:00"),
|
("Test Headline 1", "TestSource", "12:00"),
|
||||||
@@ -739,7 +746,7 @@ class TestEffectPluginStage:
|
|||||||
"""EffectPluginStage applies sensor param bindings."""
|
"""EffectPluginStage applies sensor param bindings."""
|
||||||
from engine.effects.types import EffectConfig, EffectPlugin
|
from engine.effects.types import EffectConfig, EffectPlugin
|
||||||
from engine.pipeline.adapters import EffectPluginStage
|
from engine.pipeline.adapters import EffectPluginStage
|
||||||
from engine.pipeline.core import PipelineContext
|
from engine.pipeline import PipelineContext
|
||||||
from engine.pipeline.params import PipelineParams
|
from engine.pipeline.params import PipelineParams
|
||||||
|
|
||||||
class SensorDrivenEffect(EffectPlugin):
|
class SensorDrivenEffect(EffectPlugin):
|
||||||
@@ -772,7 +779,7 @@ class TestFullPipeline:
|
|||||||
def test_pipeline_circular_dependency_detection(self):
|
def test_pipeline_circular_dependency_detection(self):
|
||||||
"""Pipeline detects circular dependencies."""
|
"""Pipeline detects circular dependencies."""
|
||||||
from engine.pipeline.controller import Pipeline
|
from engine.pipeline.controller import Pipeline
|
||||||
from engine.pipeline.core import Stage
|
from engine.pipeline import Stage
|
||||||
|
|
||||||
class StageA(Stage):
|
class StageA(Stage):
|
||||||
name = "a"
|
name = "a"
|
||||||
@@ -819,7 +826,7 @@ class TestPipelineMetrics:
|
|||||||
def test_metrics_collected(self):
|
def test_metrics_collected(self):
|
||||||
"""Pipeline collects metrics when enabled."""
|
"""Pipeline collects metrics when enabled."""
|
||||||
from engine.pipeline.controller import Pipeline, PipelineConfig
|
from engine.pipeline.controller import Pipeline, PipelineConfig
|
||||||
from engine.pipeline.core import Stage
|
from engine.pipeline import Stage
|
||||||
|
|
||||||
class DummyStage(Stage):
|
class DummyStage(Stage):
|
||||||
name = "dummy"
|
name = "dummy"
|
||||||
@@ -842,7 +849,7 @@ class TestPipelineMetrics:
|
|||||||
def test_metrics_disabled(self):
|
def test_metrics_disabled(self):
|
||||||
"""Pipeline skips metrics when disabled."""
|
"""Pipeline skips metrics when disabled."""
|
||||||
from engine.pipeline.controller import Pipeline, PipelineConfig
|
from engine.pipeline.controller import Pipeline, PipelineConfig
|
||||||
from engine.pipeline.core import Stage
|
from engine.pipeline import Stage
|
||||||
|
|
||||||
class DummyStage(Stage):
|
class DummyStage(Stage):
|
||||||
name = "dummy"
|
name = "dummy"
|
||||||
@@ -864,7 +871,7 @@ class TestPipelineMetrics:
|
|||||||
def test_reset_metrics(self):
|
def test_reset_metrics(self):
|
||||||
"""Pipeline.reset_metrics clears collected metrics."""
|
"""Pipeline.reset_metrics clears collected metrics."""
|
||||||
from engine.pipeline.controller import Pipeline, PipelineConfig
|
from engine.pipeline.controller import Pipeline, PipelineConfig
|
||||||
from engine.pipeline.core import Stage
|
from engine.pipeline import Stage
|
||||||
|
|
||||||
class DummyStage(Stage):
|
class DummyStage(Stage):
|
||||||
name = "dummy"
|
name = "dummy"
|
||||||
@@ -894,7 +901,7 @@ class TestOverlayStages:
|
|||||||
|
|
||||||
def test_stage_is_overlay_property(self):
|
def test_stage_is_overlay_property(self):
|
||||||
"""Stage has is_overlay property defaulting to False."""
|
"""Stage has is_overlay property defaulting to False."""
|
||||||
from engine.pipeline.core import Stage
|
from engine.pipeline import Stage
|
||||||
|
|
||||||
class TestStage(Stage):
|
class TestStage(Stage):
|
||||||
name = "test"
|
name = "test"
|
||||||
@@ -908,7 +915,7 @@ class TestOverlayStages:
|
|||||||
|
|
||||||
def test_stage_render_order_property(self):
|
def test_stage_render_order_property(self):
|
||||||
"""Stage has render_order property defaulting to 0."""
|
"""Stage has render_order property defaulting to 0."""
|
||||||
from engine.pipeline.core import Stage
|
from engine.pipeline import Stage
|
||||||
|
|
||||||
class TestStage(Stage):
|
class TestStage(Stage):
|
||||||
name = "test"
|
name = "test"
|
||||||
@@ -922,7 +929,7 @@ class TestOverlayStages:
|
|||||||
|
|
||||||
def test_stage_stage_type_property(self):
|
def test_stage_stage_type_property(self):
|
||||||
"""Stage has stage_type property defaulting to category."""
|
"""Stage has stage_type property defaulting to category."""
|
||||||
from engine.pipeline.core import Stage
|
from engine.pipeline import Stage
|
||||||
|
|
||||||
class TestStage(Stage):
|
class TestStage(Stage):
|
||||||
name = "test"
|
name = "test"
|
||||||
@@ -937,7 +944,7 @@ class TestOverlayStages:
|
|||||||
def test_pipeline_get_overlay_stages(self):
|
def test_pipeline_get_overlay_stages(self):
|
||||||
"""Pipeline.get_overlay_stages returns overlay stages sorted by render_order."""
|
"""Pipeline.get_overlay_stages returns overlay stages sorted by render_order."""
|
||||||
from engine.pipeline.controller import Pipeline
|
from engine.pipeline.controller import Pipeline
|
||||||
from engine.pipeline.core import Stage
|
from engine.pipeline import Stage
|
||||||
|
|
||||||
class OverlayStageA(Stage):
|
class OverlayStageA(Stage):
|
||||||
name = "overlay_a"
|
name = "overlay_a"
|
||||||
@@ -991,7 +998,7 @@ class TestOverlayStages:
|
|||||||
def test_pipeline_executes_overlays_after_regular(self):
|
def test_pipeline_executes_overlays_after_regular(self):
|
||||||
"""Pipeline executes overlays after regular stages."""
|
"""Pipeline executes overlays after regular stages."""
|
||||||
from engine.pipeline.controller import Pipeline
|
from engine.pipeline.controller import Pipeline
|
||||||
from engine.pipeline.core import Stage
|
from engine.pipeline import Stage
|
||||||
|
|
||||||
call_order = []
|
call_order = []
|
||||||
|
|
||||||
@@ -1071,7 +1078,7 @@ class TestOverlayStages:
|
|||||||
def test_pipeline_get_stage_type(self):
|
def test_pipeline_get_stage_type(self):
|
||||||
"""Pipeline.get_stage_type returns stage_type for a stage."""
|
"""Pipeline.get_stage_type returns stage_type for a stage."""
|
||||||
from engine.pipeline.controller import Pipeline
|
from engine.pipeline.controller import Pipeline
|
||||||
from engine.pipeline.core import Stage
|
from engine.pipeline import Stage
|
||||||
|
|
||||||
class TestStage(Stage):
|
class TestStage(Stage):
|
||||||
name = "test"
|
name = "test"
|
||||||
@@ -1093,7 +1100,7 @@ class TestOverlayStages:
|
|||||||
def test_pipeline_get_render_order(self):
|
def test_pipeline_get_render_order(self):
|
||||||
"""Pipeline.get_render_order returns render_order for a stage."""
|
"""Pipeline.get_render_order returns render_order for a stage."""
|
||||||
from engine.pipeline.controller import Pipeline
|
from engine.pipeline.controller import Pipeline
|
||||||
from engine.pipeline.core import Stage
|
from engine.pipeline import Stage
|
||||||
|
|
||||||
class TestStage(Stage):
|
class TestStage(Stage):
|
||||||
name = "test"
|
name = "test"
|
||||||
@@ -1341,7 +1348,7 @@ class TestPipelineMutation:
|
|||||||
dependencies: set | None = None,
|
dependencies: set | None = None,
|
||||||
):
|
):
|
||||||
"""Helper to create a mock stage."""
|
"""Helper to create a mock stage."""
|
||||||
from engine.pipeline.core import DataType
|
from engine.pipeline import DataType
|
||||||
|
|
||||||
mock = MagicMock(spec=Stage)
|
mock = MagicMock(spec=Stage)
|
||||||
mock.name = name
|
mock.name = name
|
||||||
@@ -1693,7 +1700,7 @@ class TestPipelineMutation:
|
|||||||
|
|
||||||
def test_mutation_preserves_execution_for_remaining_stages(self):
|
def test_mutation_preserves_execution_for_remaining_stages(self):
|
||||||
"""Removing a stage doesn't break execution of remaining stages."""
|
"""Removing a stage doesn't break execution of remaining stages."""
|
||||||
from engine.pipeline.core import DataType
|
from engine.pipeline import DataType
|
||||||
|
|
||||||
call_log = []
|
call_log = []
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user