Files

79 lines
2.4 KiB
Python

"""流程事件:stdout NDJSON / 文件 / HTTP webhook。"""
from __future__ import annotations
import json
import time
import urllib.error
import urllib.request
from pathlib import Path
from typing import Any, Dict, List, Optional, Union
def _now_iso() -> str:
return time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
def _http_json_post(url: str, body: Dict[str, Any], timeout: float = 30.0) -> None:
data = json.dumps(body, ensure_ascii=False).encode("utf-8")
request = urllib.request.Request(
url,
data=data,
headers={"Accept": "application/json", "Content-Type": "application/json"},
method="POST",
)
with urllib.request.urlopen(request, timeout=timeout) as resp:
resp.read()
class CallbackHub:
"""向 stdout / 文件 / HTTP webhook 广播流程事件。"""
def __init__(
self,
*,
callback_url: Optional[str] = None,
callback_file: Optional[Union[str, Path]] = None,
quiet: bool = False,
) -> None:
self.callback_url = callback_url
self.callback_file = Path(callback_file) if callback_file else None
self.quiet = quiet
self.events: List[Dict[str, Any]] = []
def emit(self, event: str, **payload: Any) -> Dict[str, Any]:
record = {
"event": event,
"ts": _now_iso(),
**payload,
}
self.events.append(record)
if not self.quiet:
print(json.dumps(record, ensure_ascii=False), flush=True)
if self.callback_file:
self.callback_file.parent.mkdir(parents=True, exist_ok=True)
self.callback_file.write_text(
json.dumps(
{"latest": record, "history": self.events},
ensure_ascii=False,
indent=2,
),
encoding="utf-8",
)
if self.callback_url:
try:
_http_json_post(self.callback_url, record, timeout=30.0)
except Exception as exc: # noqa: BLE001
if not self.quiet:
warn = {
"event": "callback_error",
"ts": _now_iso(),
"error": str(exc),
"callback_url": self.callback_url,
}
print(json.dumps(warn, ensure_ascii=False), flush=True)
return record