schedule_store.py 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. """定时任务定义与执行记录的存取。"""
  2. from __future__ import annotations
  3. from typing import Any
  4. from db import db_conn
  5. from obs import get_logger
  6. logger = get_logger(__name__)
  7. def create_schedule(
  8. user_name: str,
  9. title: str,
  10. cron: str,
  11. prompt: str,
  12. mode: str = "full",
  13. enabled: bool = True,
  14. ) -> dict[str, Any]:
  15. """新建定时任务定义。"""
  16. schedule_id = db_conn.new_id()
  17. stamp = db_conn.fmt_dt()
  18. with db_conn.transaction():
  19. db_conn.execute(
  20. "INSERT INTO schedules (id, user_name, title, cron, prompt, mode, enabled, created_at, updated_at)"
  21. " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
  22. (
  23. schedule_id,
  24. user_name,
  25. title,
  26. cron,
  27. prompt,
  28. mode,
  29. 1 if enabled else 0,
  30. stamp,
  31. stamp,
  32. ),
  33. )
  34. logger.info("新建定时任务 id=%s cron=%s user=%s", schedule_id, cron, user_name)
  35. return get_schedule(schedule_id) or {}
  36. def get_schedule(schedule_id: str) -> dict[str, Any] | None:
  37. """按 ID 取任务定义。"""
  38. row = db_conn.query_one("SELECT * FROM schedules WHERE id = ?", (schedule_id,))
  39. return _hydrate(row) if row else None
  40. def list_schedules(
  41. user_name: str | None = None, enabled_only: bool = False, limit: int = 200
  42. ) -> list[dict[str, Any]]:
  43. """列出任务定义。``user_name=None`` 表示不限用户(调度器装载用)。"""
  44. clauses: list[str] = []
  45. params: list[Any] = []
  46. if user_name is not None:
  47. clauses.append("user_name = ?")
  48. params.append(user_name)
  49. if enabled_only:
  50. clauses.append("enabled = 1")
  51. where = f" WHERE {' AND '.join(clauses)}" if clauses else ""
  52. params.append(limit)
  53. rows = db_conn.query(
  54. f"SELECT * FROM schedules{where} ORDER BY created_at DESC LIMIT ?", tuple(params)
  55. )
  56. return [_hydrate(row) for row in rows]
  57. def update_schedule(schedule_id: str, **fields: Any) -> dict[str, Any] | None:
  58. """更新任务定义的指定字段(白名单过滤,未知字段忽略)。"""
  59. allowed = {"title", "cron", "prompt", "mode", "enabled"}
  60. updates = {k: v for k, v in fields.items() if k in allowed and v is not None}
  61. if not updates:
  62. return get_schedule(schedule_id)
  63. if "enabled" in updates:
  64. updates["enabled"] = 1 if updates["enabled"] else 0
  65. assignments = ", ".join(f"{key} = ?" for key in updates)
  66. params = list(updates.values()) + [db_conn.fmt_dt(), schedule_id]
  67. db_conn.execute(
  68. f"UPDATE schedules SET {assignments}, updated_at = ? WHERE id = ?", tuple(params)
  69. )
  70. return get_schedule(schedule_id)
  71. def set_enabled(schedule_id: str, enabled: bool) -> dict[str, Any] | None:
  72. """启停任务。"""
  73. return update_schedule(schedule_id, enabled=enabled)
  74. def delete_schedule(schedule_id: str) -> bool:
  75. """删除任务定义及其执行记录。"""
  76. with db_conn.transaction():
  77. db_conn.execute("DELETE FROM schedule_runs WHERE schedule_id = ?", (schedule_id,))
  78. affected = db_conn.execute("DELETE FROM schedules WHERE id = ?", (schedule_id,))
  79. return affected > 0
  80. # ============================================================
  81. # 执行记录
  82. # ============================================================
  83. def start_run(schedule_id: str, thread_id: str) -> str:
  84. """登记一次执行开始,返回 run_id。"""
  85. run_id = db_conn.new_id()
  86. db_conn.execute(
  87. "INSERT INTO schedule_runs (id, schedule_id, thread_id, status, started_at)"
  88. " VALUES (?, ?, ?, 'running', ?)",
  89. (run_id, schedule_id, thread_id, db_conn.fmt_dt()),
  90. )
  91. return run_id
  92. def finish_run(
  93. run_id: str, status: str, error: str | None = None, output: str | None = None
  94. ) -> None:
  95. """登记一次执行结束。status: success | waiting_input | failed | cancelled。"""
  96. db_conn.execute(
  97. "UPDATE schedule_runs SET status = ?, finished_at = ?, error = ?, output = ? WHERE id = ?",
  98. (status, None if status == "waiting_input" else db_conn.fmt_dt(), error, output, run_id),
  99. )
  100. def list_runs(schedule_id: str, limit: int = 20) -> list[dict[str, Any]]:
  101. """列出某任务的执行记录(最近优先)。"""
  102. return db_conn.query(
  103. "SELECT * FROM schedule_runs WHERE schedule_id = ?"
  104. " ORDER BY started_at DESC, id DESC LIMIT ?",
  105. (schedule_id, limit),
  106. )
  107. def latest_run(schedule_id: str) -> dict[str, Any] | None:
  108. """取最近一次执行记录。"""
  109. runs = list_runs(schedule_id, limit=1)
  110. return runs[0] if runs else None
  111. def _hydrate(row: dict[str, Any]) -> dict[str, Any]:
  112. schedule = dict(row)
  113. schedule["enabled"] = bool(schedule.get("enabled"))
  114. return schedule