139 lines
4.0 KiB
Python
139 lines
4.0 KiB
Python
from __future__ import annotations
|
|
|
|
from contextlib import asynccontextmanager
|
|
from typing import Any, TYPE_CHECKING
|
|
from collections.abc import AsyncIterator
|
|
|
|
from yuxi.utils.logging_config import logger
|
|
|
|
if TYPE_CHECKING:
|
|
from .client import UrbitClient
|
|
|
|
|
|
class HttpPokeApiClient:
|
|
def __init__(self, client: UrbitClient, channel_id: str):
|
|
self._client = client
|
|
self._channel_id = channel_id
|
|
self._closed = False
|
|
|
|
@property
|
|
def channel_id(self) -> str:
|
|
return self._channel_id
|
|
|
|
async def poke(self, app: str, mark: str, json_data: dict[str, Any], *, timeout: float = 15.0) -> dict[str, Any]:
|
|
if self._closed:
|
|
raise RuntimeError("HttpPokeApiClient is closed")
|
|
|
|
payload = {
|
|
"action": "poke",
|
|
"ship": self._client.ship_name,
|
|
"app": app,
|
|
"mark": mark,
|
|
"json": json_data,
|
|
}
|
|
|
|
r = await self._client.put(
|
|
f"/~/channel/{self._channel_id}",
|
|
json=payload,
|
|
timeout=timeout,
|
|
)
|
|
r.raise_for_status()
|
|
return r.json()
|
|
|
|
async def scry(self, app: str, path: str) -> dict[str, Any]:
|
|
if self._closed:
|
|
raise RuntimeError("HttpPokeApiClient is closed")
|
|
|
|
r = await self._client.get(
|
|
f"/~/scry/{app}{path}",
|
|
timeout=10.0,
|
|
)
|
|
r.raise_for_status()
|
|
return r.json()
|
|
|
|
async def subscribe(self, app: str, path: str) -> None:
|
|
if self._closed:
|
|
raise RuntimeError("HttpPokeApiClient is closed")
|
|
|
|
payload = {
|
|
"action": "subscribe",
|
|
"ship": self._client.ship_name,
|
|
"app": app,
|
|
"path": path,
|
|
}
|
|
|
|
r = await self._client.put(
|
|
f"/~/channel/{self._channel_id}",
|
|
json=payload,
|
|
timeout=10.0,
|
|
)
|
|
r.raise_for_status()
|
|
logger.debug(f"[Urbit] HttpPokeApi subscribed: {app}{path} on {self._channel_id}")
|
|
|
|
async def unsubscribe(self) -> None:
|
|
if self._closed:
|
|
return
|
|
payload = {
|
|
"action": "unsubscribe",
|
|
"ship": self._client.ship_name,
|
|
}
|
|
try:
|
|
r = await self._client.put(
|
|
f"/~/channel/{self._channel_id}",
|
|
json=payload,
|
|
timeout=5.0,
|
|
)
|
|
r.raise_for_status()
|
|
except Exception as e:
|
|
logger.debug(f"[Urbit] HttpPokeApi unsubscribe error on {self._channel_id}: {e}")
|
|
|
|
async def delete(self) -> None:
|
|
if self._closed:
|
|
return
|
|
try:
|
|
r = await self._client.delete(f"/~/channel/{self._channel_id}")
|
|
if r.status_code not in (200, 204):
|
|
logger.debug(f"[Urbit] HttpPokeApi DELETE {self._channel_id} → {r.status_code}")
|
|
except Exception as e:
|
|
logger.debug(f"[Urbit] HttpPokeApi DELETE error on {self._channel_id}: {e}")
|
|
|
|
async def close(self) -> None:
|
|
if self._closed:
|
|
return
|
|
await self.unsubscribe()
|
|
await self.delete()
|
|
self._closed = True
|
|
logger.debug(f"[Urbit] HttpPokeApiClient closed: {self._channel_id}")
|
|
|
|
@property
|
|
def is_closed(self) -> bool:
|
|
return self._closed
|
|
|
|
|
|
@asynccontextmanager
|
|
async def with_http_poke_api(
|
|
client: UrbitClient,
|
|
channel_id: str | None = None,
|
|
) -> AsyncIterator[HttpPokeApiClient]:
|
|
import time
|
|
|
|
cid = channel_id or f"poke-api-{int(time.time() * 1000)}"
|
|
api = HttpPokeApiClient(client, cid)
|
|
logger.debug(f"[Urbit] HttpPokeApiClient opened: {cid}")
|
|
try:
|
|
yield api
|
|
finally:
|
|
try:
|
|
await api.close()
|
|
except Exception as e:
|
|
logger.warning(f"[Urbit] HttpPokeApiClient cleanup error for {cid}: {e}")
|
|
|
|
|
|
async def create_http_poke_api(client: UrbitClient, channel_id: str | None = None) -> HttpPokeApiClient:
|
|
import time
|
|
|
|
cid = channel_id or f"poke-api-{int(time.time() * 1000)}"
|
|
api = HttpPokeApiClient(client, cid)
|
|
logger.debug(f"[Urbit] HttpPokeApiClient created: {cid}")
|
|
return api
|