Add REPL effect detection and input handling to pipeline runner

- Detect REPL effect in pipeline and enable interactive mode
- Enable raw terminal mode for REPL input capture
- Add keyboard input loop for REPL commands (return, up/down arrows, backspace)
- Process commands and handle pipeline mutations from REPL
- Fix lint issues in graph and REPL modules (type annotations, imports)
This commit is contained in:
2026-03-21 21:19:30 -07:00
parent fb0dd4592f
commit 6646ed78b3
7 changed files with 89 additions and 52 deletions

View File

@@ -22,8 +22,8 @@ Usage:
"""
from dataclasses import dataclass, field
from typing import Any, Dict, List, Optional, Union
from enum import Enum
from typing import Any
class NodeType(Enum):
@@ -45,7 +45,7 @@ class Node:
name: str
type: NodeType
config: Dict[str, Any] = field(default_factory=dict)
config: dict[str, Any] = field(default_factory=dict)
enabled: bool = True
optional: bool = False
@@ -59,17 +59,17 @@ class Connection:
source: str
target: str
data_type: Optional[str] = None # Optional data type constraint
data_type: str | None = None # Optional data type constraint
@dataclass
class Graph:
"""Pipeline graph representation."""
nodes: Dict[str, Node] = field(default_factory=dict)
connections: List[Connection] = field(default_factory=list)
nodes: dict[str, Node] = field(default_factory=dict)
connections: list[Connection] = field(default_factory=list)
def node(self, name: str, node_type: Union[NodeType, str], **config) -> "Graph":
def node(self, name: str, node_type: NodeType | str, **config) -> "Graph":
"""Add a node to the graph."""
if isinstance(node_type, str):
# Try to parse as NodeType
@@ -82,7 +82,7 @@ class Graph:
return self
def connect(
self, source: str, target: str, data_type: Optional[str] = None
self, source: str, target: str, data_type: str | None = None
) -> "Graph":
"""Add a connection between nodes."""
if source not in self.nodes:
@@ -99,7 +99,7 @@ class Graph:
self.connect(names[i], names[i + 1])
return self
def from_dict(self, data: Dict[str, Any]) -> "Graph":
def from_dict(self, data: dict[str, Any]) -> "Graph":
"""Load graph from dictionary (TOML-compatible)."""
# Parse nodes
nodes_data = data.get("nodes", {})
@@ -127,7 +127,7 @@ class Graph:
return self
def to_dict(self) -> Dict[str, Any]:
def to_dict(self) -> dict[str, Any]:
"""Convert graph to dictionary."""
return {
"nodes": {
@@ -140,7 +140,7 @@ class Graph:
],
}
def validate(self) -> List[str]:
def validate(self) -> list[str]:
"""Validate graph structure and return list of errors."""
errors = []
@@ -166,9 +166,8 @@ class Graph:
temp.add(node_name)
for conn in self.connections:
if conn.source == node_name:
if has_cycle(conn.target):
return True
if conn.source == node_name and has_cycle(conn.target):
return True
temp.remove(node_name)
visited.add(node_name)
return False

View File

@@ -4,12 +4,12 @@ This module bridges the new graph-based abstraction with the existing
Stage-based pipeline system for backward compatibility.
"""
from typing import Dict, Any, List, Optional
from typing import Any, Optional
from engine.pipeline.graph import Graph, NodeType
from engine.pipeline.controller import Pipeline, PipelineConfig
from engine.pipeline.core import PipelineContext
from engine.pipeline.params import PipelineParams
from engine.camera import Camera
from engine.data_sources.sources import EmptyDataSource, HeadlinesDataSource
from engine.display import DisplayRegistry
from engine.effects import get_registry
from engine.pipeline.adapters import (
CameraStage,
DataSourceStage,
@@ -18,15 +18,12 @@ from engine.pipeline.adapters import (
FontStage,
MessageOverlayStage,
PositionStage,
ViewportFilterStage,
create_stage_from_display,
create_stage_from_effect,
)
from engine.pipeline.adapters.positioning import PositioningMode
from engine.display import DisplayRegistry
from engine.effects import get_registry
from engine.data_sources.sources import EmptyDataSource, HeadlinesDataSource
from engine.camera import Camera
from engine.pipeline.controller import Pipeline, PipelineConfig
from engine.pipeline.core import PipelineContext
from engine.pipeline.graph import Graph, NodeType
from engine.pipeline.params import PipelineParams
class GraphAdapter:
@@ -34,8 +31,8 @@ class GraphAdapter:
def __init__(self, graph: Graph):
self.graph = graph
self.pipeline: Optional[Pipeline] = None
self.context: Optional[PipelineContext] = None
self.pipeline: Pipeline | None = None
self.context: PipelineContext | None = None
def build_pipeline(
self, viewport_width: int = 80, viewport_height: int = 24
@@ -154,7 +151,7 @@ def graph_to_pipeline(
def dict_to_pipeline(
data: Dict[str, Any], viewport_width: int = 80, viewport_height: int = 24
data: dict[str, Any], viewport_width: int = 80, viewport_height: int = 24
) -> Pipeline:
"""Convert a dictionary to a Pipeline."""
graph = Graph().from_dict(data)

View File

@@ -1,8 +1,9 @@
"""TOML-based graph configuration loader."""
import tomllib
from pathlib import Path
from typing import Any, Dict
from typing import Any
import tomllib
from engine.pipeline.graph import Graph, NodeType
from engine.pipeline.graph_adapter import graph_to_pipeline
@@ -23,7 +24,7 @@ def load_graph_from_toml(toml_path: str | Path) -> Graph:
return graph_from_dict(data)
def graph_from_dict(data: Dict[str, Any]) -> Graph:
def graph_from_dict(data: dict[str, Any]) -> Graph:
"""Create a graph from a dictionary (TOML-compatible structure).
Args:

View File

@@ -18,8 +18,8 @@ providing the same flexibility.
"""
from dataclasses import dataclass, field
from typing import Any, Dict, List, Optional
from pathlib import Path
from typing import Any
from engine.pipeline.graph import Graph, NodeType
from engine.pipeline.graph_adapter import graph_to_pipeline
@@ -32,7 +32,7 @@ class EffectConfig:
name: str
intensity: float = 1.0
enabled: bool = True
params: Dict[str, Any] = field(default_factory=dict)
params: dict[str, Any] = field(default_factory=dict)
@dataclass
@@ -70,9 +70,9 @@ class PipelineConfig:
"""
source: str = "headlines"
camera: Optional[CameraConfig] = None
effects: List[EffectConfig] = field(default_factory=list)
display: Optional[DisplayConfig] = None
camera: CameraConfig | None = None
effects: list[EffectConfig] = field(default_factory=list)
display: DisplayConfig | None = None
viewport_width: int = 80
viewport_height: int = 24
@@ -208,7 +208,7 @@ def load_hybrid_config(toml_path: str | Path) -> PipelineConfig:
return parse_hybrid_config(data)
def parse_hybrid_config(data: Dict[str, Any]) -> PipelineConfig:
def parse_hybrid_config(data: dict[str, Any]) -> PipelineConfig:
"""Parse hybrid configuration from dictionary.
Expected format: