104 lines
5.4 KiB
Python
104 lines
5.4 KiB
Python
"""Команды модуля wiki: wiki_sync (синхронизация дерева проекта) и wiki_fetch (файл вложения). Обе читают Eva."""
|
||
import re
|
||
|
||
from ...core import queue
|
||
from ...core.errors import CommandError
|
||
from ...core.modules import CommandHooks
|
||
from . import attachments, sync
|
||
|
||
TYPES = {"wiki_sync", "wiki_fetch"}
|
||
SPEC = {"wiki_sync": {"required": ["label", "root"], "optional": []},
|
||
"wiki_fetch": {"required": ["attachment_id", "url"], "optional": []}}
|
||
_ROOT = re.compile(r"^DOC-\d+$")
|
||
|
||
|
||
def validate(cmds) -> list[dict]:
|
||
if not isinstance(cmds, list) or not cmds:
|
||
raise CommandError("пакет без команд")
|
||
out = []
|
||
for i, cmd in enumerate(cmds, 1):
|
||
if not isinstance(cmd, dict) or "type" not in cmd:
|
||
raise CommandError(f"команда {i}: нет type")
|
||
ctype, params = cmd["type"], cmd.get("params") or {}
|
||
if ctype not in SPEC:
|
||
raise CommandError(f"команда {i}: неизвестный тип «{ctype}»")
|
||
if not isinstance(params, dict):
|
||
raise CommandError(f"команда {i}: params не объект")
|
||
spec = SPEC[ctype]
|
||
missing = [f for f in spec["required"] if params.get(f) in (None, "")]
|
||
if missing:
|
||
raise CommandError(f"команда {i} ({ctype}): нет {', '.join(missing)}; ожидаются {', '.join(spec['required'])}")
|
||
unknown = set(params) - set(spec["required"])
|
||
if unknown:
|
||
raise CommandError(f"команда {i} ({ctype}): лишние параметры {', '.join(sorted(unknown))}; "
|
||
f"ожидаются {', '.join(spec['required'])}")
|
||
if ctype == "wiki_sync":
|
||
if not isinstance(params["label"], str):
|
||
raise CommandError(f"команда {i}: label — строка")
|
||
if not isinstance(params["root"], str) or not _ROOT.match(params["root"]):
|
||
raise CommandError(f"команда {i}: root — код DOC-<цифры>")
|
||
else:
|
||
a = params["attachment_id"]
|
||
if not isinstance(a, int) or isinstance(a, bool):
|
||
raise CommandError(f"команда {i}: attachment_id — целое")
|
||
if not isinstance(params["url"], str):
|
||
raise CommandError(f"команда {i}: url — строка")
|
||
out.append({"type": ctype, "params": params})
|
||
return out
|
||
|
||
|
||
def check_batch(conn, cmds, project, files_dir) -> list[dict]:
|
||
"""wiki_sync живёт в пакете своего проекта: project пакета равен label команды."""
|
||
for i, c in enumerate(cmds, 1):
|
||
if c["type"] == "wiki_sync" and c["params"]["label"] != project:
|
||
raise CommandError(f"команда {i}: проект пакета ({project}) не равен label ({c['params']['label']})")
|
||
return cmds
|
||
|
||
|
||
def enqueue_sync(conn, label: str, root: str, author: str) -> dict:
|
||
"""Пакет «Вики <метка>» из одной команды wiki_sync."""
|
||
return queue.create_batch(conn, f"Вики {label}", author, [{"type": "wiki_sync", "params": {"label": label, "root": root}}],
|
||
project=label)
|
||
|
||
|
||
def active_sync(conn, label: str) -> dict | None:
|
||
"""Пакет wiki_sync проекта не в терминальном статусе (есть команда queued или claimed) или None."""
|
||
row = conn.execute(
|
||
"SELECT b.id FROM batch b WHERE b.project = ? AND EXISTS (SELECT 1 FROM command c WHERE c.batch_id = b.id "
|
||
"AND c.type = 'wiki_sync' AND c.status IN ('queued', 'claimed')) ORDER BY b.id DESC LIMIT 1", (label,)).fetchone()
|
||
return queue.batch_view(conn, row["id"], with_commands=False) if row else None
|
||
|
||
|
||
def on_failed(conn, cmd, now) -> None:
|
||
(attachments if cmd["type"] == "wiki_fetch" else sync).on_failed(conn, cmd, now)
|
||
|
||
|
||
def check_result(conn, cmd, status, result, files_dir) -> tuple[str, str | None]:
|
||
if cmd["type"] == "wiki_fetch":
|
||
return attachments.check_result(conn, cmd, status, result, files_dir)
|
||
return sync.check_result(conn, cmd, status, result, files_dir)
|
||
|
||
|
||
def on_result(conn, cmd, status, result, jira_calls, rate_limited, now, files_dir) -> None:
|
||
if cmd["type"] == "wiki_fetch":
|
||
attachments.on_result(conn, cmd, status, result, now)
|
||
else:
|
||
sync.on_result(conn, cmd, status, result, jira_calls, rate_limited, now, files_dir)
|
||
|
||
|
||
def after_result(conn, cmd, status) -> None:
|
||
if cmd["type"] == "wiki_sync":
|
||
sync.after_result(conn, cmd, status)
|
||
|
||
|
||
# wiki_sync — sync.py (хуки before/abort/after только у него), wiki_fetch — attachments.py
|
||
HOOKS = CommandHooks(
|
||
types=frozenset(TYPES), read_types=frozenset(TYPES), validate=validate, check_batch=check_batch,
|
||
tail=lambda conn, cmds, project: None, pause=lambda conn, ctype: 0, deliver_params=lambda conn, cmd, files_dir: cmd["params"],
|
||
temp_definer=None, temp_keys_used=lambda cmd: [], substitute=lambda ctype, params, mapping: params,
|
||
drop_unresolved=lambda cmd, unresolved: unresolved, retry_params=lambda cmd: cmd["params"],
|
||
check_confirmed=lambda conn, cmd: (False, None), on_claim=sync.on_claim, on_failed=on_failed,
|
||
check_result=check_result, on_result=on_result, after_result=after_result,
|
||
on_late_result=lambda *a: False, before_result=sync.before_result,
|
||
abort_result=sync.abort_result)
|