Rename the import package app to fluksio
A wheel whose top-level module is `app` collides with anything else in a user's venv, so the package that is about to be published takes the name it is published under. Only the Python package moves; the repo, the Docker WORKDIR and the compose project keep theirs. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
import time
|
||||
from typing import Any
|
||||
|
||||
from fastapi import APIRouter, Depends, Request, Response
|
||||
from fastapi.concurrency import run_in_threadpool
|
||||
from pydantic.networks import EmailStr
|
||||
|
||||
from fluksio.api.deps import get_current_active_superuser
|
||||
from fluksio.flow.state import RedisState
|
||||
from fluksio.models import Message
|
||||
from fluksio.utils import generate_test_email, send_email
|
||||
|
||||
router = APIRouter(prefix="/utils", tags=["utils"])
|
||||
|
||||
|
||||
@router.post(
|
||||
"/test-email/",
|
||||
dependencies=[Depends(get_current_active_superuser)],
|
||||
status_code=201,
|
||||
)
|
||||
def test_email(email_to: EmailStr) -> Message:
|
||||
"""
|
||||
Test emails.
|
||||
"""
|
||||
email_data = generate_test_email(email_to=email_to)
|
||||
send_email(
|
||||
email_to=email_to,
|
||||
subject=email_data.subject,
|
||||
html_content=email_data.html_content,
|
||||
)
|
||||
return Message(message="Test email sent")
|
||||
|
||||
|
||||
@router.get("/health-check/")
|
||||
async def health_check() -> bool:
|
||||
return True
|
||||
|
||||
|
||||
@router.get("/health/")
|
||||
async def health(request: Request, response: Response) -> dict[str, Any]:
|
||||
"""Deep health: 200 while the engine can serve its purpose, 503 otherwise.
|
||||
|
||||
"Serve its purpose" means the event loop is responsive and the configured
|
||||
state backend answers — the two failure modes a process-alive check never
|
||||
sees. The queue and pool sections are filled by the execution service.
|
||||
"""
|
||||
controller = getattr(request.app.state, "flow_controller", None)
|
||||
watchdog = getattr(request.app.state, "watchdog", None)
|
||||
problems: list[str] = []
|
||||
|
||||
loop_lag = watchdog.snapshot() if watchdog else {"ewma": 0.0, "max_60s": 0.0}
|
||||
if watchdog is not None and watchdog.degraded:
|
||||
problems.append("event loop lagging")
|
||||
|
||||
redis_info: dict[str, Any] = {
|
||||
"configured": False,
|
||||
"connected": None,
|
||||
"rtt_ms": None,
|
||||
}
|
||||
engine: dict[str, Any] = {"flows": 0, "nodes": 0, "quarantined": []}
|
||||
queue: dict[str, Any] = {}
|
||||
if controller is not None:
|
||||
state = controller.state
|
||||
if isinstance(state, RedisState):
|
||||
redis_info["configured"] = True
|
||||
start = time.perf_counter()
|
||||
connected = await run_in_threadpool(state.ping)
|
||||
redis_info["connected"] = connected
|
||||
redis_info["rtt_ms"] = round((time.perf_counter() - start) * 1000, 1)
|
||||
if not connected:
|
||||
problems.append("redis unreachable")
|
||||
# `loaded` is keyed by node id, so its length is a node count.
|
||||
loaded = getattr(controller, "loaded", {}) or {}
|
||||
engine["flows"] = len({entry.flow for entry in loaded.values()})
|
||||
engine["nodes"] = len(
|
||||
getattr(getattr(controller, "pipeline", None), "nodes", []) or []
|
||||
)
|
||||
engine["quarantined"] = sorted(getattr(controller, "quarantined", ()) or ())
|
||||
stats = getattr(controller, "queue_stats", None)
|
||||
if callable(stats):
|
||||
queue = await run_in_threadpool(stats)
|
||||
if queue.get("oldest_pending_s", 0) > 120:
|
||||
problems.append("queue stalled")
|
||||
|
||||
status = "degraded" if problems else "ok"
|
||||
if problems:
|
||||
response.status_code = 503
|
||||
return {
|
||||
"status": status,
|
||||
"problems": problems,
|
||||
"loop_lag_ms": loop_lag,
|
||||
"redis": redis_info,
|
||||
"engine": engine,
|
||||
"queue": queue,
|
||||
"ts": time.time(),
|
||||
}
|
||||
Reference in New Issue
Block a user