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 / test (push) Has been cancelled

This commit is contained in:
2026-08-09 15:57:38 +00:00
parent 14a443d589
commit 8168fcb73d

View File

@@ -1,58 +1,75 @@
import asyncio import asyncio
from dataclasses import dataclass
from datetime import UTC, datetime
from uuid import UUID, uuid4 from uuid import UUID, uuid4
from .models import CheckSnapshot, Monitor, MonitorInput, now from app.models import Monitor, MonitorCreate, MonitorUpdate, StatusSnapshot
class StoreFullError(Exception): @dataclass
pass class _Record:
monitor: Monitor
revision: int
class MonitorStore: class MonitorStore:
def __init__(self, max_monitors: int) -> None: def __init__(self) -> None:
self._items: dict[UUID, Monitor] = {}
self._lock = asyncio.Lock() self._lock = asyncio.Lock()
self._max = max_monitors self._records: dict[UUID, _Record] = {}
async def create(self, data: MonitorInput) -> Monitor: async def create(self, data: MonitorCreate) -> Monitor:
now = datetime.now(UTC)
monitor = Monitor(
id=uuid4(), name=data.name, url=data.url, created_at=now,
updated_at=now, status=StatusSnapshot(),
)
async with self._lock: async with self._lock:
if len(self._items) >= self._max: self._records[monitor.id] = _Record(monitor, 0)
raise StoreFullError return monitor.model_copy(deep=True)
timestamp = now()
item = Monitor(id=uuid4(), **data.model_dump(), created_at=timestamp,
updated_at=timestamp, revision=1, status=CheckSnapshot())
self._items[item.id] = item
return item
async def list(self) -> list[Monitor]: async def list(self) -> list[Monitor]:
async with self._lock: async with self._lock:
return sorted(self._items.values(), key=lambda item: item.created_at) values = [item.monitor for item in self._records.values()]
return [value.model_copy(deep=True) for value in values]
async def get(self, monitor_id: UUID) -> Monitor | None: async def get(self, monitor_id: UUID) -> Monitor | None:
async with self._lock: async with self._lock:
return self._items.get(monitor_id) record = self._records.get(monitor_id)
return record.monitor.model_copy(deep=True) if record else None
async def replace(self, monitor_id: UUID, data: MonitorInput) -> Monitor | None: async def update(self, monitor_id: UUID, data: MonitorUpdate) -> Monitor | None:
async with self._lock: async with self._lock:
old = self._items.get(monitor_id) record = self._records.get(monitor_id)
if old is None: if not record:
return None return None
item = old.model_copy(update={**data.model_dump(), "updated_at": now(), url_changed = record.monitor.url != data.url
"revision": old.revision + 1, status = StatusSnapshot() if url_changed else record.monitor.status
"status": CheckSnapshot()}) record.monitor = record.monitor.model_copy(update={
self._items[monitor_id] = item "name": data.name, "url": data.url, "updated_at": datetime.now(UTC),
return item "status": status,
})
record.revision += 1
return record.monitor.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._records.pop(monitor_id, None) is not None
async def set_status(self, monitor_id: UUID, revision: int, async def begin_check(self, monitor_id: UUID) -> tuple[Monitor, int] | None:
status: CheckSnapshot) -> Monitor | None:
async with self._lock: async with self._lock:
old = self._items.get(monitor_id) record = self._records.get(monitor_id)
if old is None or old.revision != revision: if not record:
return None return None
item = old.model_copy(update={"status": status, "updated_at": now()}) return record.monitor.model_copy(deep=True), record.revision
self._items[monitor_id] = item
return item async def finish_check(
self, monitor_id: UUID, revision: int, status: StatusSnapshot,
) -> bool:
async with self._lock:
record = self._records.get(monitor_id)
if not record or record.revision != revision:
return False
record.monitor = record.monitor.model_copy(update={
"status": status, "updated_at": datetime.now(UTC),
})
return True