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:
2026-08-21 21:48:05 +02:00
co-authored by Claude Opus 5
parent 97785ee590
commit 60d7ec81c0
170 changed files with 629 additions and 619 deletions
+91
View File
@@ -0,0 +1,91 @@
"""Alerting configuration: which failures go where."""
from pathlib import Path
from typing import Any
from fastapi import APIRouter, Depends, HTTPException
from fastapi.concurrency import run_in_threadpool
from fluksio.api.deps import FlowControllerDep, get_current_user
from fluksio.core.config import settings
from fluksio.flow.alerts import Alert, AlertsConfig
from fluksio.models import Message
router = APIRouter(
prefix="/alerts", tags=["alerts"], dependencies=[Depends(get_current_user)]
)
def _path() -> Path:
return settings.ALERTS_FILE
def read_config() -> AlertsConfig:
"""The stored configuration, or an empty one. Blocking."""
path = _path()
if not path.exists():
return AlertsConfig()
try:
return AlertsConfig.model_validate_json(path.read_text())
except Exception:
# A hand-edited file that no longer parses must not stop the engine.
return AlertsConfig()
def write_config(config: AlertsConfig) -> None:
"""Blocking."""
path = _path()
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(config.model_dump_json(indent=2))
@router.get("/config", response_model=AlertsConfig)
async def read_alerts_config() -> Any:
"""What the engine alerts on, and where it sends it."""
return await run_in_threadpool(read_config)
@router.put("/config", response_model=AlertsConfig)
async def save_alerts_config(body: AlertsConfig, controller: FlowControllerDep) -> Any:
"""Replace the configuration. Takes effect immediately."""
known = {channel.name for channel in body.channels}
for rule in body.rules:
missing = [name for name in rule.channels if name not in known]
if missing:
raise HTTPException(
status_code=422,
detail=f"No channel named {', '.join(repr(m) for m in missing)}",
)
await run_in_threadpool(write_config, body)
if controller.alerts is not None:
controller.alerts.config = body
return body
@router.post("/test/{channel_name}", response_model=Message)
async def test_channel(channel_name: str, controller: FlowControllerDep) -> Any:
"""Send one alert, to prove the channel works before relying on it."""
if controller.alerts is None:
raise HTTPException(status_code=503, detail="Alerting is not running")
config = controller.alerts.config
channel = next((c for c in config.channels if c.name == channel_name), None)
if channel is None:
raise HTTPException(status_code=404, detail=f"No channel '{channel_name}'")
try:
await controller.alerts.send(
channel,
Alert(
title="Fluksio test alert",
body="If you are reading this, the channel works.",
severity="warning",
),
raise_on_error=True,
)
except Exception as exc:
# Whatever the sender said, verbatim: it is the only clue the operator
# has about why the channel does not work.
raise HTTPException(status_code=502, detail=str(exc) or type(exc).__name__)
return Message(message=f"Sent a test alert through '{channel_name}'")