chore: enforce ruff lint and format standards across backend

This commit is contained in:
Jonas Miederer 2026-09-14 08:10:14 +02:00
parent a8e312a4f8
commit a6cc585dd0
39 changed files with 459 additions and 559 deletions

View file

@ -5,6 +5,7 @@ from __future__ import annotations
import json
from collections.abc import AsyncIterator, Iterator
from pathlib import Path
from typing import Annotated
from uuid import UUID
import httpx
@ -12,15 +13,14 @@ from fastapi import APIRouter, Depends, HTTPException, Query
from fastapi.responses import StreamingResponse
from sqlmodel import Session
from app.auth.deps import get_optional_user, resolve_tenant
from app.db import get_engine, get_session
from app.models import User
from app.settings import settings
from app.auth.deps import get_optional_user, resolve_tenant
from . import runner, store
from . import input_schema as form_schema
from . import runner, store
from .deploy import import_from_langflow
from .sync import lfx_id_for, sync_from_directory
from .langflow_client import (
LangflowClient,
LangflowError,
@ -33,7 +33,6 @@ from .lfx_client import (
LfxError,
LfxInvalidFlow,
LfxNotFound,
LfxRunFailed,
LfxUnavailable,
)
from .schemas import (
@ -52,6 +51,7 @@ from .schemas import (
RunRequest,
SyncReport,
)
from .sync import lfx_id_for, sync_from_directory
flows_router = APIRouter(prefix="/api/flows", tags=["flows"])
approvals_router = APIRouter(prefix="/api/approvals", tags=["approvals"])
@ -66,9 +66,7 @@ def get_lfx() -> LfxClient:
def get_langflow() -> LangflowClient:
return LangflowClient(
settings.langflow_url, api_key=settings.langflow_api_key or ""
)
return LangflowClient(settings.langflow_url, api_key=settings.langflow_api_key or "")
def _read_definition(d) -> FlowDefinitionRead:
@ -129,7 +127,7 @@ def _read_task(t) -> ApprovalRead:
@flows_router.post("", response_model=FlowDefinitionRead, status_code=201)
def create_flow(data: FlowDefinitionCreate, session: Session = Depends(get_db)):
def create_flow(data: FlowDefinitionCreate, session: Annotated[Session, Depends(get_db)]):
try:
return _read_definition(store.create_definition(session, data))
except store.ConflictError as exc:
@ -139,14 +137,14 @@ def create_flow(data: FlowDefinitionCreate, session: Session = Depends(get_db)):
@flows_router.get("", response_model=list[FlowDefinitionRead])
def list_flows(session: Session = Depends(get_db)):
def list_flows(session: Annotated[Session, Depends(get_db)]):
return [_read_definition(d) for d in store.list_definitions(session)]
@flows_router.post("/sync", response_model=SyncReport)
async def sync_flows(
session: Session = Depends(get_db),
lfx: LfxClient = Depends(get_lfx),
session: Annotated[Session, Depends(get_db)],
lfx: Annotated[LfxClient, Depends(get_lfx)],
):
"""Reconcile backend/flows/*.json into the DB + LFX (add/update/delete)."""
try:
@ -156,18 +154,16 @@ async def sync_flows(
@flows_router.get("/{flow_id}", response_model=FlowDefinitionRead)
def get_flow(flow_id: UUID, session: Session = Depends(get_db)):
def get_flow(flow_id: UUID, session: Annotated[Session, Depends(get_db)]):
try:
return _read_definition(store.get_definition(session, flow_id))
except store.NotFoundError as exc:
raise HTTPException(status_code=404, detail=str(exc)) from exc
@flows_router.post(
"/{flow_id}/versions", response_model=FlowVersionRead, status_code=201
)
@flows_router.post("/{flow_id}/versions", response_model=FlowVersionRead, status_code=201)
def create_version(
flow_id: UUID, data: FlowVersionCreate, session: Session = Depends(get_db)
flow_id: UUID, data: FlowVersionCreate, session: Annotated[Session, Depends(get_db)]
):
try:
return _read_version(store.create_version(session, flow_id, data))
@ -178,21 +174,19 @@ def create_version(
@flows_router.get("/{flow_id}/versions", response_model=list[FlowVersionRead])
def list_versions(flow_id: UUID, session: Session = Depends(get_db)):
def list_versions(flow_id: UUID, session: Annotated[Session, Depends(get_db)]):
try:
return [_read_version(v) for v in store.list_versions(session, flow_id)]
except store.NotFoundError as exc:
raise HTTPException(status_code=404, detail=str(exc)) from exc
@flows_router.post(
"/{flow_id}/versions/{version_no}/deploy", response_model=FlowVersionRead
)
@flows_router.post("/{flow_id}/versions/{version_no}/deploy", response_model=FlowVersionRead)
async def deploy_version(
flow_id: UUID,
version_no: int,
session: Session = Depends(get_db),
lfx: LfxClient = Depends(get_lfx),
session: Annotated[Session, Depends(get_db)],
lfx: Annotated[LfxClient, Depends(get_lfx)],
):
try:
version = store.get_version_by_no(session, flow_id, version_no)
@ -227,10 +221,10 @@ def _resolve_version(session: Session, flow_id: UUID, version_no: int | None):
async def run_flow(
flow_id: UUID,
data: RunRequest,
session: Annotated[Session, Depends(get_db)],
lfx: Annotated[LfxClient, Depends(get_lfx)],
user: Annotated[User | None, Depends(get_optional_user)],
stream: bool = Query(default=False),
session: Session = Depends(get_db),
lfx: LfxClient = Depends(get_lfx),
user: User | None = Depends(get_optional_user),
):
# Tenant comes from the session; the body param stays as a deprecated
# fallback for unauthenticated callers.
@ -265,9 +259,7 @@ async def run_flow(
async def _run_stream(session: Session, lfx: LfxClient, version, data: RunRequest):
if not version.lfx_flow_id:
raise HTTPException(
status_code=422, detail=f"version {version.id} is not deployed to LFX"
)
raise HTTPException(status_code=422, detail=f"version {version.id} is not deployed to LFX")
run = store.create_run(
session,
version,
@ -288,30 +280,28 @@ async def _run_stream(session: Session, lfx: LfxClient, version, data: RunReques
async def event_gen() -> AsyncIterator[str]:
yield f"event: run\ndata: {json.dumps({'run_id': str(run_id)})}\n\n"
try:
async with httpx.AsyncClient(
base_url=lfx.base_url, timeout=lfx.timeout, headers=lfx._headers()
) as client:
async with client.stream(
async with (
httpx.AsyncClient(
base_url=lfx.base_url, timeout=lfx.timeout, headers=lfx._headers()
) as client,
client.stream(
"POST",
f"/flows/{lfx_flow_id}/stream",
json={"input_value": envelope, "session_id": session_id},
) as response:
if response.status_code == 404:
raise LfxNotFound(f"flow {lfx_flow_id} unknown to LFX")
response.raise_for_status()
async for chunk in response.aiter_text():
yield chunk
) as response,
):
if response.status_code == 404:
raise LfxNotFound(f"flow {lfx_flow_id} unknown to LFX")
response.raise_for_status()
async for chunk in response.aiter_text():
yield chunk
except (LfxNotFound, httpx.HTTPError) as exc:
yield f"event: error\ndata: {json.dumps({'error': str(exc)})}\n\n"
# Finalize from a fresh session: the request session may be closed.
with Session(get_engine()) as fresh:
try:
fresh_run = store.get_run(fresh, run_id)
status = (
"pending_approval"
if runner._task_exists(fresh, run_id)
else "completed"
)
status = "pending_approval" if runner._task_exists(fresh, run_id) else "completed"
store.set_run_result(fresh, fresh_run, status, {"streamed": True})
except store.NotFoundError:
status = "failed"
@ -323,9 +313,9 @@ async def _run_stream(session: Session, lfx: LfxClient, version, data: RunReques
@flows_router.get("/{flow_id}/runs", response_model=list[RunRead])
def list_runs(
flow_id: UUID,
session: Annotated[Session, Depends(get_db)],
user: Annotated[User | None, Depends(get_optional_user)],
tenant_id: str | None = Query(default=None),
session: Session = Depends(get_db),
user: User | None = Depends(get_optional_user),
):
# Authenticated callers are scoped to their session tenant unless they
# pass an explicit tenant (deprecated fallback for unauthenticated use).
@ -338,7 +328,7 @@ def list_runs(
@flows_router.get("/langflow/available")
async def list_langflow_flows(langflow: LangflowClient = Depends(get_langflow)):
async def list_langflow_flows(langflow: Annotated[LangflowClient, Depends(get_langflow)]):
"""Browse flows on the Langflow authoring server (admin import source)."""
try:
return await langflow.list_flows()
@ -351,9 +341,9 @@ async def list_langflow_flows(langflow: LangflowClient = Depends(get_langflow)):
@flows_router.post("/import", response_model=FlowImportRead, status_code=201)
async def import_flow(
data: FlowImportRequest,
session: Session = Depends(get_db),
langflow: LangflowClient = Depends(get_langflow),
lfx: LfxClient = Depends(get_lfx),
session: Annotated[Session, Depends(get_db)],
langflow: Annotated[LangflowClient, Depends(get_langflow)],
lfx: Annotated[LfxClient, Depends(get_lfx)],
):
"""Import a flow from Langflow: export -> version -> (deploy to LFX)."""
try:
@ -384,24 +374,20 @@ async def import_flow(
raise HTTPException(status_code=503, detail=str(exc)) from exc
except LfxError as exc:
raise HTTPException(status_code=502, detail=str(exc)) from exc
return FlowImportRead(
definition=_read_definition(definition), version=_read_version(version)
)
return FlowImportRead(definition=_read_definition(definition), version=_read_version(version))
@flows_router.get("/{flow_id}/input-schema", response_model=InputSchemaRead)
def get_input_schema(
flow_id: UUID,
session: Session = Depends(get_db),
session: Annotated[Session, Depends(get_db)],
):
"""Form schema of the latest deployed version (404 when none)."""
try:
version = store.get_latest_deployed_version(session, flow_id)
except (store.NotFoundError, store.InvalidError) as exc:
raise HTTPException(status_code=404, detail=str(exc)) from exc
cached = version.input_schema or form_schema.extract_form_schema(
version.langflow_json
)
cached = version.input_schema or form_schema.extract_form_schema(version.langflow_json)
return InputSchemaRead(
flow_id=flow_id,
version_no=version.version_no,
@ -414,9 +400,9 @@ def get_input_schema(
@flows_router.get("/runs/{run_id}", response_model=RunRead)
def get_run(
run_id: UUID,
session: Annotated[Session, Depends(get_db)],
user: Annotated[User | None, Depends(get_optional_user)],
tenant_id: str | None = Query(default=None),
session: Session = Depends(get_db),
user: User | None = Depends(get_optional_user),
):
"""Read a run. With tenant_id, cross-tenant reads 404 (v1 scoping)."""
if user is not None and tenant_id is None:
@ -431,7 +417,7 @@ def get_run(
@approvals_router.post("", response_model=ApprovalRead, status_code=201)
def create_approval(data: ApprovalCreate, session: Session = Depends(get_db)):
def create_approval(data: ApprovalCreate, session: Annotated[Session, Depends(get_db)]):
"""Flow callback contract: report a terminal approval state for a run."""
try:
return _read_task(store.create_approval_task(session, data))
@ -445,9 +431,9 @@ def create_approval(data: ApprovalCreate, session: Session = Depends(get_db)):
@approvals_router.get("", response_model=list[ApprovalRead])
def list_approvals(
session: Annotated[Session, Depends(get_db)],
user: Annotated[User | None, Depends(get_optional_user)],
tenant_id: str | None = Query(default=None),
session: Session = Depends(get_db),
user: User | None = Depends(get_optional_user),
):
if user is not None and tenant_id is None:
tenant_id = user.tenant_id
@ -458,8 +444,8 @@ def list_approvals(
async def decide_approval(
task_id: UUID,
data: DecisionRequest,
session: Session = Depends(get_db),
lfx: LfxClient = Depends(get_lfx),
session: Annotated[Session, Depends(get_db)],
lfx: Annotated[LfxClient, Depends(get_lfx)],
):
"""Decide an open task. Approve starts the continuation flow (if any)."""
try: