decomposer: generate deliverable files for Define the service contract and project architecture for the FastAPI endpoint monitoring service.; Implement the typed monitor CRUD API and concurrency-safe in-memory state according to the service design.; Implement secure on-demand endpoint checks with status updates, latency measurement, robust error handling, and redacted structured logs.; Add operational API endpoints and environment-driven runtime configuration to the monitoring service.; Create automated tests for the monitoring service.; Package the service with Docker and developer documentation.; Validate the complete project.
Some checks failed
ci / quality (push) Has been cancelled
ci / container (push) Has been cancelled

This commit is contained in:
2026-08-09 16:03:43 +00:00
parent 9d5137d688
commit 0b1c0be312

View File

@@ -1,68 +1,53 @@
import asyncio import asyncio
from dataclasses import dataclass from dataclasses import dataclass
from datetime import datetime, timezone
from uuid import UUID, uuid4 from uuid import UUID, uuid4
from app.models import CheckStatus, Monitor, MonitorCreate, MonitorUpdate from .models import CheckResult, Monitor, MonitorCreate, MonitorStatus, MonitorUpdate, utcnow
@dataclass(frozen=True)
class Snapshot:
monitor: Monitor
revision: int
@dataclass
class MonitorStore: class MonitorStore:
def __init__(self) -> None: def __post_init__(self) -> None:
self._items: dict[UUID, Monitor] = {}
self._lock = asyncio.Lock() self._lock = asyncio.Lock()
self._items: dict[UUID, tuple[Monitor, int]] = {}
async def create(self, data: MonitorCreate) -> Monitor: async def create(self, data: MonitorCreate) -> Monitor:
now = datetime.now(timezone.utc) now = utcnow()
item = Monitor(id=uuid4(), name=data.name, url=data.url, created_at=now, item = Monitor(id=uuid4(), name=data.name, url=data.url, created_at=now, updated_at=now)
updated_at=now, status=CheckStatus())
async with self._lock: async with self._lock:
self._items[item.id] = (item, 1) self._items[item.id] = item
return item.model_copy(deep=True) return item
async def list(self) -> list[Monitor]: async def list(self) -> list[Monitor]:
async with self._lock: async with self._lock:
values = [item.model_copy(deep=True) for item, _ in self._items.values()] return sorted(self._items.values(), key=lambda item: (item.created_at, str(item.id)))
return sorted(values, key=lambda item: (item.created_at, str(item.id)))
async def snapshot(self, monitor_id: UUID) -> Snapshot | None: async def get(self, monitor_id: UUID) -> Monitor | None:
async with self._lock: async with self._lock:
stored = self._items.get(monitor_id) return self._items.get(monitor_id)
if stored is None:
return None
item, revision = stored
return Snapshot(item.model_copy(deep=True), revision)
async def update(self, monitor_id: UUID, data: MonitorUpdate) -> Monitor | None: async def update(self, monitor_id: UUID, data: MonitorUpdate) -> Monitor | None:
async with self._lock: async with self._lock:
stored = self._items.get(monitor_id) current = self._items.get(monitor_id)
if stored is None: if current is None:
return None return None
item, revision = stored updated = current.model_copy(update={
changes = data.model_dump(exclude_none=True) "name": data.name, "url": data.url, "updated_at": utcnow(),
if "url" in changes: "status": MonitorStatus.unknown, "checked_at": None,
changes["status"] = CheckStatus() "latency_ms": None, "http_status": None, "error": None,
changes["updated_at"] = datetime.now(timezone.utc) })
updated = item.model_copy(update=changes) self._items[monitor_id] = updated
self._items[monitor_id] = (updated, revision + 1) return updated
return updated.model_copy(deep=True)
async def delete(self, monitor_id: UUID) -> bool: async def delete(self, monitor_id: UUID) -> bool:
async with self._lock: async with self._lock:
return self._items.pop(monitor_id, None) is not None return self._items.pop(monitor_id, None) is not None
async def apply_status(self, monitor_id: UUID, revision: int, async def record(self, monitor_id: UUID, result: CheckResult) -> Monitor | None:
status: CheckStatus) -> bool:
async with self._lock: async with self._lock:
stored = self._items.get(monitor_id) current = self._items.get(monitor_id)
if stored is None or stored[1] != revision: if current is None:
return False return None
item, current_revision = stored updated = current.model_copy(update={**result.model_dump(), "updated_at": utcnow()})
updated = item.model_copy(update={"status": status}) self._items[monitor_id] = updated
self._items[monitor_id] = (updated, current_revision) return updated
return True