from __future__ import annotations from collections.abc import Awaitable, Callable from typing import Any from yuxi.channels.pipeline.context import PipelineContext from yuxi.utils.logging_config import logger PipelineStage = Callable[ ["BaseInboundPipeline", PipelineContext], Awaitable[PipelineContext | None], ] class BaseInboundPipeline: def __init__(self, adapter: Any): self._adapter = adapter self._stages: list[PipelineStage] = [] async def initialize(self) -> None: self._stages = await self._build_stages() logger.info( f"[Pipeline] {self.__class__.__name__} initialized with " f"{len(self._stages)} stages: " f"{[getattr(s, '__name__', s.__class__.__name__) for s in self._stages]}" ) async def _build_stages(self) -> list[PipelineStage]: return [] async def process( self, event_type: str, event_data: dict[str, Any], ) -> PipelineContext | None: ctx = self._build_context(event_type, event_data) for idx, stage in enumerate(self._stages, start=1): stage_name = self._resolve_stage_name(stage, idx) try: result = await stage(self, ctx) except Exception as e: logger.error( f"[Pipeline::{self.__class__.__name__}] Stage [{idx}] '{stage_name}' raised exception: {e}", exc_info=True, ) return None if result is None: reason = ctx._stop_reason or f"stage_{idx}:{stage_name}" logger.debug( f"[Pipeline] Message dropped at [{idx}] '{stage_name}': {reason} | ctx={ctx.debug_summary()}" ) return None ctx = result if ctx.stopped: logger.debug(f"[Pipeline] Message stopped at [{idx}] '{stage_name}': {ctx._stop_reason}") return None return ctx def _build_context(self, event_type: str, event_data: dict[str, Any]) -> PipelineContext: return PipelineContext(event_type=event_type, event_data=event_data) @staticmethod def _resolve_stage_name(stage: PipelineStage, idx: int) -> str: name = getattr(stage, "__name__", "") if name and not name.startswith("_"): return name if hasattr(stage, "__self__"): return type(stage.__self__).__name__ return f"stage_{idx}" @property def adapter(self) -> Any: return self._adapter