""" Type definitions for Art DAG L1 server. Uses TypedDict for configuration structures to enable mypy checking. """ from typing import Any, Dict, List, Optional, TypedDict, Union from typing_extensions import NotRequired # === Node Config Types === class SourceConfig(TypedDict, total=False): """Config for SOURCE nodes.""" cid: str # Content ID (IPFS CID or SHA3-256 hash) asset: str # Asset name from registry input: bool # True if this is a variable input name: str # Human-readable name for variable inputs description: str # Description for variable inputs class EffectConfig(TypedDict, total=False): """Config for EFFECT nodes.""" effect: str # Effect name cid: str # Effect CID (for cached/IPFS effects) # Effect parameters are additional keys intensity: float level: float class SequenceConfig(TypedDict, total=False): """Config for SEQUENCE nodes.""" transition: Dict[str, Any] # Transition config class SegmentConfig(TypedDict, total=False): """Config for SEGMENT nodes.""" start: float end: float duration: float # Union of all config types NodeConfig = Union[SourceConfig, EffectConfig, SequenceConfig, SegmentConfig, Dict[str, Any]] # === Node Types === class CompiledNode(TypedDict): """Node as produced by the S-expression compiler.""" id: str type: str # "SOURCE", "EFFECT", "SEQUENCE", etc. config: Dict[str, Any] inputs: List[str] name: NotRequired[str] class TransformedNode(TypedDict): """Node after transformation for artdag execution.""" node_id: str node_type: str config: Dict[str, Any] inputs: List[str] name: NotRequired[str] # === DAG Types === class CompiledDAG(TypedDict): """DAG as produced by the S-expression compiler.""" nodes: List[CompiledNode] output: str class TransformedDAG(TypedDict): """DAG after transformation for artdag execution.""" nodes: Dict[str, TransformedNode] output_id: str metadata: NotRequired[Dict[str, Any]] # === Registry Types === class AssetEntry(TypedDict, total=False): """Asset in the recipe registry.""" cid: str url: str class EffectEntry(TypedDict, total=False): """Effect in the recipe registry.""" cid: str url: str temporal: bool class Registry(TypedDict): """Recipe registry containing assets and effects.""" assets: Dict[str, AssetEntry] effects: Dict[str, EffectEntry] # === Visualization Types === class VisNodeData(TypedDict, total=False): """Data for a visualization node (Cytoscape.js format).""" id: str label: str nodeType: str isOutput: bool class VisNode(TypedDict): """Visualization node wrapper.""" data: VisNodeData class VisEdgeData(TypedDict): """Data for a visualization edge.""" source: str target: str class VisEdge(TypedDict): """Visualization edge wrapper.""" data: VisEdgeData class VisualizationDAG(TypedDict): """DAG structure for Cytoscape.js visualization.""" nodes: List[VisNode] edges: List[VisEdge] # === Recipe Types === class Recipe(TypedDict, total=False): """Compiled recipe structure.""" name: str version: str description: str owner: str registry: Registry dag: CompiledDAG recipe_id: str ipfs_cid: str sexp: str step_count: int error: str # === API Request/Response Types === class RecipeRunInputs(TypedDict): """Mapping of input names to CIDs for recipe execution.""" # Keys are input names, values are CIDs pass # Actually just Dict[str, str] class RunResult(TypedDict, total=False): """Result of a recipe run.""" run_id: str status: str # "pending", "running", "completed", "failed" recipe: str recipe_name: str inputs: List[str] output_cid: str ipfs_cid: str provenance_cid: str error: str created_at: str completed_at: str actor_id: str celery_task_id: str output_name: str # === Helper functions for type narrowing === def is_source_node(node: TransformedNode) -> bool: """Check if node is a SOURCE node.""" return node.get("node_type") == "SOURCE" def is_effect_node(node: TransformedNode) -> bool: """Check if node is an EFFECT node.""" return node.get("node_type") == "EFFECT" def is_variable_input(config: Dict[str, Any]) -> bool: """Check if a SOURCE node config represents a variable input.""" return bool(config.get("input")) def get_effect_cid(config: Dict[str, Any]) -> Optional[str]: """Get effect CID from config, checking both 'cid' and 'hash' keys.""" return config.get("cid") or config.get("hash")