79 lines
2.9 KiB
Python
79 lines
2.9 KiB
Python
|
|
"""An outside master in another process, driven through its control API.
|
||
|
|
|
||
|
|
Same interface as `stubs.master.StubMaster`, so the harness and the flow
|
||
|
|
runner can swap it in. Used for the independent JS master in
|
||
|
|
`impl/js/src/master.mjs` (MAS-WP-0009).
|
||
|
|
"""
|
||
|
|
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
import json
|
||
|
|
import subprocess
|
||
|
|
import urllib.request
|
||
|
|
from pathlib import Path
|
||
|
|
from typing import Optional
|
||
|
|
|
||
|
|
ROOT = Path(__file__).resolve().parents[1]
|
||
|
|
JS_MASTER = ROOT / "impl/js/src/master.mjs"
|
||
|
|
|
||
|
|
|
||
|
|
def _call(url: str, body: Optional[dict] = None):
|
||
|
|
data = None if body is None else json.dumps(body).encode()
|
||
|
|
req = urllib.request.Request(url, data=data, method="GET" if body is None else "POST",
|
||
|
|
headers={"Content-Type": "application/json"})
|
||
|
|
with urllib.request.urlopen(req, timeout=15) as r:
|
||
|
|
return json.loads(r.read().decode())
|
||
|
|
|
||
|
|
|
||
|
|
class ExternalMaster:
|
||
|
|
def __init__(self, argv: list[str]):
|
||
|
|
self.proc = subprocess.Popen(argv, stdout=subprocess.PIPE, text=True)
|
||
|
|
info = json.loads(self.proc.stdout.readline())
|
||
|
|
self.events_url, self.control_url = info["events_url"], info["control_url"]
|
||
|
|
self._email_url = None
|
||
|
|
|
||
|
|
@classmethod
|
||
|
|
def js(cls, key: bytes, peer_id: str = "cms-1") -> "ExternalMaster":
|
||
|
|
return cls(["node", str(JS_MASTER), "--peer-id", peer_id, "--key-hex", key.hex()])
|
||
|
|
|
||
|
|
@property
|
||
|
|
def email_url(self):
|
||
|
|
return self._email_url
|
||
|
|
|
||
|
|
@email_url.setter
|
||
|
|
def email_url(self, url):
|
||
|
|
self._email_url = url
|
||
|
|
_call(self.control_url + "/config", {"email_url": url})
|
||
|
|
|
||
|
|
@property
|
||
|
|
def received(self) -> list[dict]:
|
||
|
|
return _call(self.control_url + "/received")
|
||
|
|
|
||
|
|
def of_type(self, type_: str) -> list[dict]:
|
||
|
|
return [m for m in self.received if m["type"] == type_]
|
||
|
|
|
||
|
|
def command(self, type_: str, case: Optional[dict] = None, **fields):
|
||
|
|
out = _call(self.control_url + "/command",
|
||
|
|
{"type": type_, **({"case": case} if case else {}),
|
||
|
|
**{k: v for k, v in fields.items() if v is not None}})
|
||
|
|
return out["status"], out["body"]
|
||
|
|
|
||
|
|
def assign(self, intake_ref: str, resource_id: str):
|
||
|
|
return self.command("case.assign", intake_ref=intake_ref, resource_id=resource_id)
|
||
|
|
|
||
|
|
def policy(self, case: dict, autonomous_send=None, transfers=None):
|
||
|
|
return self.command("case.policy", case, autonomous_send=autonomous_send, transfers=transfers)
|
||
|
|
|
||
|
|
def send(self, case: dict, in_reply_to: str, body: str, subject: Optional[str] = None):
|
||
|
|
return self.command("mail.send", case, in_reply_to=in_reply_to, body=body, subject=subject)
|
||
|
|
|
||
|
|
def cancel(self, case: dict, handoff_id: str, reason: str = "timed-out"):
|
||
|
|
return self.command("handoff.cancel", case, handoff_id=handoff_id, reason=reason)
|
||
|
|
|
||
|
|
def pump(self):
|
||
|
|
return []
|
||
|
|
|
||
|
|
def close(self):
|
||
|
|
self.proc.terminate()
|
||
|
|
self.proc.wait(timeout=10)
|