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,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}'")
|
||||
Reference in New Issue
Block a user