refactor: tighten backend, flows, frontend, tests; push flows to Langflow

This commit is contained in:
Jonas Miederer 2026-09-28 15:58:00 +02:00
parent 6688fb08c6
commit 06ab25fb47
31 changed files with 783 additions and 358 deletions

View file

@ -8,6 +8,8 @@ from __future__ import annotations
import httpx
from .base_client import BaseApiClient
class LangflowError(Exception):
pass
@ -21,23 +23,16 @@ class LangflowUnavailable(LangflowError):
pass
class LangflowClient:
class LangflowClient(BaseApiClient):
"""Thin async wrapper around the Langflow `/api/v1/flows` endpoints."""
def __init__(self, base_url: str, api_key: str = "", timeout: float = 60.0) -> None:
self.base_url = base_url.rstrip("/")
self.api_key = api_key
self.timeout = timeout
def _headers(self) -> dict[str, str]:
return {"x-api-key": self.api_key} if self.api_key else {}
super().__init__(base_url, timeout=timeout, api_key=api_key)
async def list_flows(self) -> list[dict]:
"""List flows (header mode keeps payloads small)."""
try:
async with httpx.AsyncClient(
base_url=self.base_url, timeout=self.timeout, headers=self._headers()
) as client:
async with self._client() as client:
response = await client.get("/api/v1/flows/", params={"header_flows": "true"})
if response.status_code == 404:
raise LangflowNotFound("flow list not found")
@ -52,9 +47,7 @@ class LangflowClient:
async def export_flow(self, langflow_flow_id: str) -> dict:
"""Export one flow as Langflow JSON (FlowRead: id/name/description/data)."""
try:
async with httpx.AsyncClient(
base_url=self.base_url, timeout=self.timeout, headers=self._headers()
) as client:
async with self._client() as client:
response = await client.get(f"/api/v1/flows/{langflow_flow_id}")
if response.status_code == 404:
raise LangflowNotFound(f"flow {langflow_flow_id} not found")
@ -64,3 +57,24 @@ class LangflowClient:
raise LangflowError(f"Langflow error {exc.response.status_code}") from exc
except httpx.HTTPError as exc:
raise LangflowUnavailable(f"Langflow unreachable: {exc}") from exc
async def upsert_flow(self, doc: dict) -> dict:
"""Create or update a flow in Langflow by name (authoring UI refresh).
Used after flow/component tightening so the visual editor shows the
latest canonical JSON without a container restart.
"""
try:
async with self._client() as client:
existing = await self.list_flows()
match = next((f for f in existing if f.get("name") == doc.get("name")), None)
if match and match.get("id"):
response = await client.patch(f"/api/v1/flows/{match['id']}", json=doc)
else:
response = await client.post("/api/v1/flows/", json=doc)
response.raise_for_status()
return response.json()
except httpx.HTTPStatusError as exc:
raise LangflowError(f"Langflow error {exc.response.status_code}") from exc
except httpx.HTTPError as exc:
raise LangflowUnavailable(f"Langflow unreachable: {exc}") from exc