From 8168fcb73d7a7213e9616537d59837bbc455f0ee Mon Sep 17 00:00:00 2001 From: demo-bot Date: Sun, 9 Aug 2026 15:57:38 +0000 Subject: [PATCH] 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. --- app/store.py | 81 +++++++++++++++++++++++++++++++--------------------- 1 file changed, 49 insertions(+), 32 deletions(-) diff --git a/app/store.py b/app/store.py index 630cbca..6d921bb 100644 --- a/app/store.py +++ b/app/store.py @@ -1,58 +1,75 @@ import asyncio +from dataclasses import dataclass +from datetime import UTC, datetime from uuid import UUID, uuid4 -from .models import CheckSnapshot, Monitor, MonitorInput, now +from app.models import Monitor, MonitorCreate, MonitorUpdate, StatusSnapshot -class StoreFullError(Exception): - pass +@dataclass +class _Record: + monitor: Monitor + revision: int class MonitorStore: - def __init__(self, max_monitors: int) -> None: - self._items: dict[UUID, Monitor] = {} + def __init__(self) -> None: 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: - if len(self._items) >= self._max: - raise StoreFullError - 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 + self._records[monitor.id] = _Record(monitor, 0) + return monitor.model_copy(deep=True) async def list(self) -> list[Monitor]: 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 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: - old = self._items.get(monitor_id) - if old is None: + record = self._records.get(monitor_id) + if not record: return None - item = old.model_copy(update={**data.model_dump(), "updated_at": now(), - "revision": old.revision + 1, - "status": CheckSnapshot()}) - self._items[monitor_id] = item - return item + url_changed = record.monitor.url != data.url + status = StatusSnapshot() if url_changed else record.monitor.status + record.monitor = record.monitor.model_copy(update={ + "name": data.name, "url": data.url, "updated_at": datetime.now(UTC), + "status": status, + }) + record.revision += 1 + return record.monitor.model_copy(deep=True) async def delete(self, monitor_id: UUID) -> bool: 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, - status: CheckSnapshot) -> Monitor | None: + async def begin_check(self, monitor_id: UUID) -> tuple[Monitor, int] | None: async with self._lock: - old = self._items.get(monitor_id) - if old is None or old.revision != revision: + record = self._records.get(monitor_id) + if not record: return None - item = old.model_copy(update={"status": status, "updated_at": now()}) - self._items[monitor_id] = item - return item + return record.monitor.model_copy(deep=True), record.revision + + 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