PlanOpticon
| 0981a08… | noreply | 1 | """Callback implementations for pipeline progress reporting.""" |
| 0981a08… | noreply | 2 | |
| 0981a08… | noreply | 3 | import json |
| 0981a08… | noreply | 4 | import logging |
| 0981a08… | noreply | 5 | from typing import Optional |
| 0981a08… | noreply | 6 | |
| 0981a08… | noreply | 7 | logger = logging.getLogger(__name__) |
| 0981a08… | noreply | 8 | |
| 0981a08… | noreply | 9 | |
| 0981a08… | noreply | 10 | class WebhookCallback: |
| 0981a08… | noreply | 11 | """Posts pipeline progress as JSON to a webhook URL.""" |
| 0981a08… | noreply | 12 | |
| 0981a08… | noreply | 13 | def __init__(self, url: str, timeout: float = 10.0, headers: Optional[dict] = None): |
| 0981a08… | noreply | 14 | self.url = url |
| 0981a08… | noreply | 15 | self.timeout = timeout |
| 0981a08… | noreply | 16 | self.headers = headers or {"Content-Type": "application/json"} |
| 0981a08… | noreply | 17 | |
| 0981a08… | noreply | 18 | def _post(self, payload: dict) -> None: |
| 0981a08… | noreply | 19 | """POST JSON payload to the webhook URL. Failures are logged, not raised.""" |
| 0981a08… | noreply | 20 | try: |
| 0981a08… | noreply | 21 | import urllib.request |
| 0981a08… | noreply | 22 | |
| 0981a08… | noreply | 23 | data = json.dumps(payload).encode("utf-8") |
| 0981a08… | noreply | 24 | req = urllib.request.Request(self.url, data=data, headers=self.headers, method="POST") |
| 0981a08… | noreply | 25 | urllib.request.urlopen(req, timeout=self.timeout) |
| 0981a08… | noreply | 26 | except Exception as e: |
| 0981a08… | noreply | 27 | logger.warning(f"Webhook POST failed: {e}") |
| 0981a08… | noreply | 28 | |
| 0981a08… | noreply | 29 | def on_step_start(self, step: str, index: int, total: int) -> None: |
| 0981a08… | noreply | 30 | self._post( |
| 0981a08… | noreply | 31 | { |
| 0981a08… | noreply | 32 | "event": "step_start", |
| 0981a08… | noreply | 33 | "step": step, |
| 0981a08… | noreply | 34 | "index": index, |
| 0981a08… | noreply | 35 | "total": total, |
| 0981a08… | noreply | 36 | } |
| 0981a08… | noreply | 37 | ) |
| 0981a08… | noreply | 38 | |
| 0981a08… | noreply | 39 | def on_step_complete(self, step: str, index: int, total: int) -> None: |
| 0981a08… | noreply | 40 | self._post( |
| 0981a08… | noreply | 41 | { |
| 0981a08… | noreply | 42 | "event": "step_complete", |
| 0981a08… | noreply | 43 | "step": step, |
| 0981a08… | noreply | 44 | "index": index, |
| 0981a08… | noreply | 45 | "total": total, |
| 0981a08… | noreply | 46 | } |
| 0981a08… | noreply | 47 | ) |
| 0981a08… | noreply | 48 | |
| 0981a08… | noreply | 49 | def on_progress(self, step: str, percent: float, message: str = "") -> None: |
| 0981a08… | noreply | 50 | self._post( |
| 0981a08… | noreply | 51 | { |
| 0981a08… | noreply | 52 | "event": "progress", |
| 0981a08… | noreply | 53 | "step": step, |
| 0981a08… | noreply | 54 | "percent": percent, |
| 0981a08… | noreply | 55 | "message": message, |
| 0981a08… | noreply | 56 | } |
| 0981a08… | noreply | 57 | ) |