""" run_full_sync_pipeline_simple.py 极简测试脚本:演示使用工厂函数创建 Pipeline - 只需配置参数 - 调用工厂函数 - 执行 pipeline.run() """ import asyncio import sys from pathlib import Path # 项目路径 PROJECT_ROOT = Path(__file__).resolve().parent.parent if str(PROJECT_ROOT) not in sys.path: sys.path.insert(0, str(PROJECT_ROOT)) # 触发 Domain 注册 import sync_system_new.domain # noqa: F401, E402 from sync_system_new.pipeline import create_jsonl_to_api_pipeline # noqa: E402 # ========== 配置参数 ========== CONFIG = { # 路径 "local_jsonl_dir": str(PROJECT_ROOT / "filtered_datasource" / "datasource" / "ecm-2025-12-02"), "persistence_db": str(PROJECT_ROOT / "_runtime" / "test_simple.db"), # API "api_base_url": "http://localhost:8000/api/third/v2", "api_uid": "6b18c681-92d5-4c7f-802c-df413b6504c8", "api_secret": "1a28Az5dBD1Gc3URpmHwiR0XfzW7UtScQnqJQzgpzdg", # 节点类型 "node_types": [ "company", "supplier", # "user", "project", "project_detail_data", "preparation", "production", "lar", "lar_change", "units", "contract", "contract_change", "construction_task", "construction_task_detail", "material", "material_detail", "contract_settlement", "contract_settlement_detail", "personnel", "personnel_detail", ], # 项目过滤和预绑定 "target_project_ids": ["f3e02e84-e81c-4aa6-88d4-f5aa108c8227"], "project_id_bindings": { "f3e02e84-e81c-4aa6-88d4-f5aa108c8227": "f3e02e84-e81c-4aa6-88d4-f5aa108c8227", }, # Handler 自定义配置 "handler_configs": { # supplier: 从持久化加载,不调用 API(后续运行时启用) "supplier": {"load_mode": "persisted_only"}, }, # Skip/Force Sync 控制 # skip_sync_types: 跳过同步(只执行 bind,不执行 create/update) # 后续运行时可配置: "skip_sync_types": ["supplier"] "skip_sync_types": ["supplier"], # 行为 "wipe_persistence_on_start": False, # 改为 False,否则持久化数据会被清空 "enable_persistence": True, # API 调试 "api_debug": True, "poll_max_retries": 1, "poll_interval": 2.0, } async def main(): """主函数 - 返回 True 表示成功,False 表示被中断""" from datetime import datetime import sys from pathlib import Path pipeline = None interrupted = False log_file = None original_stdout = None original_stderr = None # 日志设置 log_dir = Path(PROJECT_ROOT) / "test_results" log_dir.mkdir(parents=True, exist_ok=True) log_path = log_dir / f"simple_pipeline_{datetime.now().strftime('%Y%m%d_%H%M%S')}.log" try: # 重定向输出到日志文件 log_file = open(log_path, "w", encoding="utf-8") class Tee: def __init__(self, *streams): self._streams = streams def write(self, data): for stream in self._streams: stream.write(data) stream.flush() return len(data) def flush(self): for stream in self._streams: stream.flush() original_stdout = sys.stdout original_stderr = sys.stderr sys.stdout = Tee(original_stdout, log_file) sys.stderr = Tee(original_stderr, log_file) print("\n" + "=" * 60) print("🚀 Creating Pipeline...") print(f"📝 Log file: {log_path}") print("=" * 60) # 一行创建完整 Pipeline pipeline = await create_jsonl_to_api_pipeline(**CONFIG) print(f"✅ Pipeline created!") print(f" - Node types: {len(pipeline.node_types)}") print(f" - Strategies: {len(pipeline.strategies)}") print("\n" + "=" * 60) print("🏃 Running Pipeline...") print("=" * 60) # 执行同步(print_summary 已整合到 stage4_post_check 中) stats = await pipeline.run() print("\n✅ Done!") except KeyboardInterrupt: print("\n\n⚠️ KeyboardInterrupt received, shutting down gracefully...") interrupted = True except Exception as e: print(f"\n\n❌ Error: {type(e).__name__}: {e}") import traceback traceback.print_exc() finally: if pipeline is not None: print("🧹 Cleaning up resources...") await pipeline.close() print("✅ Resources cleaned up") # 恢复原始输出 if original_stdout is not None: sys.stdout = original_stdout if original_stderr is not None: sys.stderr = original_stderr if log_file is not None: log_file.close() if not interrupted: print(f"\n📝 Log saved to: {log_path}") return not interrupted # 返回 False 表示被中断 if __name__ == "__main__": try: success = asyncio.run(main()) sys.exit(0 if success else 130) # 130 是标准的 Ctrl+C 退出码 except KeyboardInterrupt: # asyncio.run() 可能会重新抛出 KeyboardInterrupt print("\n⚠️ Program interrupted") sys.exit(130)