Files
MCM/tui/api_client.py
itdrui.de 0e2a8a6bc0 feat: TUI als Browser-App via textual serve
TUI von direkten Service-Importen auf httpx API-Calls umgestellt.
Neue tui/api_client.py als schlanker async HTTP-Client. Background-
Worker pollt API alle 5s für Echtzeit-Updates. Neuer tui_standalone.py
Entry-Point für 'textual serve --port 8001 tui_standalone.py'.
2026-03-04 20:45:55 +01:00

95 lines
3.8 KiB
Python

"""Async HTTP-Client für die MCM REST-API.
Die TUI kommuniziert ausschließlich über diesen Client mit dem Backend.
Dadurch läuft die TUI sowohl im Terminal (python main.py) als auch im
Browser (textual serve tui_standalone.py) ohne Änderung.
"""
from __future__ import annotations
import logging
from typing import Any
import httpx
from config import settings
logger = logging.getLogger(__name__)
class MCMApiClient:
"""Thin async wrapper um die MCM REST-API."""
def __init__(self) -> None:
self._base = f"http://127.0.0.1:{settings.port}/api/v1"
self._headers = {"Authorization": f"Bearer {settings.api_key}"}
# ── Konversationen ─────────────────────────────────────────────────────────
async def get_conversations(self, channel: str | None = None) -> list[dict[str, Any]]:
params: dict[str, Any] = {}
if channel:
params["channel"] = channel
return await self._get("/conversations", params=params)
async def get_conversation(self, conv_id: str) -> dict[str, Any] | None:
try:
return await self._get(f"/conversations/{conv_id}")
except httpx.HTTPStatusError:
return None
async def get_messages(
self, conv_id: str, limit: int = 100, offset: int = 0
) -> list[dict[str, Any]]:
return await self._get(
f"/conversations/{conv_id}/messages",
params={"limit": limit, "offset": offset},
)
# ── Nachrichten senden ─────────────────────────────────────────────────────
async def send_message(
self,
channel: str,
text: str,
recipient_phone: str | None = None,
recipient_telegram_id: str | None = None,
reply_to_id: str | None = None,
) -> dict[str, Any]:
body: dict[str, Any] = {"channel": channel, "text": text}
if recipient_phone:
body["recipient_phone"] = recipient_phone
if recipient_telegram_id:
body["recipient_telegram_id"] = recipient_telegram_id
if reply_to_id:
body["reply_to_id"] = reply_to_id
return await self._post("/messages", body)
# ── Kontakte ───────────────────────────────────────────────────────────────
async def get_contacts(self) -> list[dict[str, Any]]:
return await self._get("/contacts")
# ── Status ─────────────────────────────────────────────────────────────────
async def get_channel_status(self) -> dict[str, Any]:
return await self._get("/channels/status")
# ── Interne Hilfsmethoden ──────────────────────────────────────────────────
async def _get(self, path: str, params: dict | None = None) -> Any:
async with httpx.AsyncClient(timeout=5.0, follow_redirects=True) as client:
resp = await client.get(
self._base + path, headers=self._headers, params=params
)
resp.raise_for_status()
return resp.json()
async def _post(self, path: str, body: dict) -> Any:
async with httpx.AsyncClient(timeout=10.0, follow_redirects=True) as client:
resp = await client.post(
self._base + path, headers=self._headers, json=body
)
resp.raise_for_status()
return resp.json()