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:07:03 +00:00
parent dcecec8e7a
commit 3e19a430d3

View File

@@ -1,53 +1,65 @@
import asyncio import asyncio
from dataclasses import dataclass
from uuid import UUID, uuid4 from uuid import UUID, uuid4
from .models import CheckResult, Monitor, MonitorCreate, MonitorStatus, MonitorUpdate, utcnow from app.models import CurrentStatus, Monitor, MonitorCreate, MonitorUpdate, utc_now
class MonitorNotFound(KeyError):
pass
@dataclass
class MonitorStore: class MonitorStore:
def __post_init__(self) -> None: """Copy-in/copy-out process-local store guarded by one asyncio lock."""
def __init__(self) -> None:
self._items: dict[UUID, Monitor] = {} self._items: dict[UUID, Monitor] = {}
self._lock = asyncio.Lock() self._lock = asyncio.Lock()
async def create(self, data: MonitorCreate) -> Monitor: async def create(self, data: MonitorCreate) -> Monitor:
now = utcnow() now = utc_now()
item = Monitor(id=uuid4(), name=data.name, url=data.url, created_at=now, updated_at=now) monitor = Monitor(
id=uuid4(),
created_at=now,
updated_at=now,
current_status=CurrentStatus(),
**data.model_dump(),
)
async with self._lock: async with self._lock:
self._items[item.id] = item self._items[monitor.id] = monitor
return item return monitor.model_copy(deep=True)
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, str(item.id))) values = sorted(self._items.values(), key=lambda item: item.created_at)
return [item.model_copy(deep=True) for item in values]
async def get(self, monitor_id: UUID) -> Monitor | None: async def get(self, monitor_id: UUID) -> Monitor:
async with self._lock: async with self._lock:
return self._items.get(monitor_id) item = self._items.get(monitor_id)
if item is None:
raise MonitorNotFound(monitor_id)
return item.model_copy(deep=True)
async def update(self, monitor_id: UUID, data: MonitorUpdate) -> Monitor | None: async def update(self, monitor_id: UUID, data: MonitorUpdate) -> Monitor:
async with self._lock: async with self._lock:
current = self._items.get(monitor_id) item = self._items.get(monitor_id)
if current is None: if item is None:
return None raise MonitorNotFound(monitor_id)
updated = current.model_copy(update={ updated = item.model_copy(update={**data.model_dump(), "updated_at": utc_now()})
"name": data.name, "url": data.url, "updated_at": utcnow(),
"status": MonitorStatus.unknown, "checked_at": None,
"latency_ms": None, "http_status": None, "error": None,
})
self._items[monitor_id] = updated self._items[monitor_id] = updated
return updated return updated.model_copy(deep=True)
async def delete(self, monitor_id: UUID) -> bool: async def set_status(self, monitor_id: UUID, status: CurrentStatus) -> CurrentStatus:
async with self._lock: async with self._lock:
return self._items.pop(monitor_id, None) is not None item = self._items.get(monitor_id)
if item is None:
raise MonitorNotFound(monitor_id)
self._items[monitor_id] = item.model_copy(
update={"current_status": status.model_copy(deep=True), "updated_at": utc_now()}
)
return status.model_copy(deep=True)
async def record(self, monitor_id: UUID, result: CheckResult) -> Monitor | None: async def delete(self, monitor_id: UUID) -> None:
async with self._lock: async with self._lock:
current = self._items.get(monitor_id) if self._items.pop(monitor_id, None) is None:
if current is None: raise MonitorNotFound(monitor_id)
return None
updated = current.model_copy(update={**result.model_dump(), "updated_at": utcnow()})
self._items[monitor_id] = updated
return updated