from __future__ import annotations import argparse import json import logging from pathlib import Path from typing import Any from supply_infra.db.repositories.pipeline_run_repo import PipelineRunRepository from supply_infra.db.repositories.pipeline_step_run_repo import PipelineStepRunRepository from supply_infra.db.session import dispose_engine, get_session from supply_infra.pipeline.contracts import StepContext from supply_infra.pipeline.registry import execute_registered_step logger = logging.getLogger(__name__) def execute_step_run(step_run_id: str) -> dict[str, Any]: with get_session() as session: step = PipelineStepRunRepository(session).get(step_run_id) if step is None: raise ValueError(f"step_run_id not found: {step_run_id}") run = PipelineRunRepository(session).get(step.run_id) if run is None: raise ValueError(f"pipeline run not found: {step.run_id}") context = StepContext( run_id=run.run_id, step_run_id=step.step_run_id, step_key=step.step_key, biz_dt=run.biz_dt, date_snapshot=dict(run.date_snapshot_json or {}), config_snapshot=dict(run.config_snapshot_json or {}), input_snapshot=dict(step.input_snapshot_json or {}), ) return execute_registered_step(context) def main() -> None: parser = argparse.ArgumentParser(description="Execute one durable pipeline step") parser.add_argument("step_run_id") parser.add_argument("--result-file", required=True) args = parser.parse_args() logging.basicConfig( level=logging.INFO, format="%(asctime)s [%(levelname)s] %(name)s: %(message)s", ) result: dict[str, Any] exit_code = 0 try: result = execute_step_run(args.step_run_id) if result.get("success") is False: exit_code = 1 except Exception as exc: logger.exception("Pipeline step failed: step_run_id=%s", args.step_run_id) result = {"success": False, "error_code": "step_exception", "error": str(exc)} exit_code = 1 finally: dispose_engine() Path(args.result_file).write_text( json.dumps(result, ensure_ascii=False, default=str), encoding="utf-8", ) raise SystemExit(exit_code) if __name__ == "__main__": main()