Add a new `/dbapi` router package providing a generic database API over `dataset`, restricted to admin sessions, admin API keys, and the internal gateway key. Includes: - `tables.py`: list all tables and inspect table schemas - `crud.py`: full CRUD operations (GET, POST, PATCH, DELETE) with soft-delete awareness, born-live inserts, `?include_deleted`, `.../restore`, and `?hard=true` purge - `query.py`: validated read-only SELECT execution via sqlglot parsing, classification, and EXPLAIN dry-run; async query jobs with WebSocket streaming via `DbApiJobService` - `nl.py`: natural-language-to-SQL conversion using the platform AI gateway with re-prompting until validation passes Also register `DbApiJobService` and `PubSubService` in the service manager, add `DBAPI_DIR` to config data paths, and force cleartext `http://` connections to HTTP/1.1 in `curl_transport` to fix large request failures against uvicorn's HTTP/1.1-only internal gateway.
44 lines
1.2 KiB
Python
44 lines
1.2 KiB
Python
# retoor <retoor@molodetz.nl>
|
|
|
|
from __future__ import annotations
|
|
|
|
import asyncio
|
|
|
|
MAX_BUFFER = 2000
|
|
|
|
|
|
class ProgressHub:
|
|
def __init__(self) -> None:
|
|
self._subscribers: dict[str, set[asyncio.Queue]] = {}
|
|
self._buffers: dict[str, list[dict]] = {}
|
|
|
|
def subscribe(self, uid: str) -> asyncio.Queue:
|
|
queue: asyncio.Queue = asyncio.Queue()
|
|
self._subscribers.setdefault(uid, set()).add(queue)
|
|
return queue
|
|
|
|
def unsubscribe(self, uid: str, queue: asyncio.Queue) -> None:
|
|
listeners = self._subscribers.get(uid)
|
|
if not listeners:
|
|
return
|
|
listeners.discard(queue)
|
|
if not listeners:
|
|
self._subscribers.pop(uid, None)
|
|
|
|
def publish(self, uid: str, frame: dict) -> None:
|
|
buffer = self._buffers.setdefault(uid, [])
|
|
buffer.append(frame)
|
|
if len(buffer) > MAX_BUFFER:
|
|
del buffer[: len(buffer) - MAX_BUFFER]
|
|
for queue in self._subscribers.get(uid, set()):
|
|
queue.put_nowait(frame)
|
|
|
|
def snapshot(self, uid: str) -> list[dict]:
|
|
return list(self._buffers.get(uid, []))
|
|
|
|
def clear(self, uid: str) -> None:
|
|
self._buffers.pop(uid, None)
|
|
|
|
|
|
hub = ProgressHub()
|