From e3d868ce8dad7a643167fc85cfad385dcbeacd68 Mon Sep 17 00:00:00 2001 From: demo-bot Date: Sun, 9 Aug 2026 15:43:59 +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/models.py | 53 ++++++++++++++++++++++++++++++++++----------------- 1 file changed, 35 insertions(+), 18 deletions(-) diff --git a/app/models.py b/app/models.py index 1200aac..598882e 100644 --- a/app/models.py +++ b/app/models.py @@ -1,43 +1,60 @@ -from datetime import datetime, timezone +from datetime import UTC, datetime from enum import StrEnum -from uuid import UUID +from uuid import UUID, uuid4 -from pydantic import BaseModel, ConfigDict, Field, HttpUrl +from pydantic import BaseModel, ConfigDict, Field, HttpUrl, model_validator -def utcnow() -> datetime: - return datetime.now(timezone.utc) +def now_utc() -> datetime: + return datetime.now(UTC) class State(StrEnum): - UNKNOWN = "unknown" + NEVER_CHECKED = "never_checked" UP = "up" DOWN = "down" ERROR = "error" + BLOCKED = "blocked" -class MonitorInput(BaseModel): - model_config = ConfigDict(str_strip_whitespace=True) - +class MonitorCreate(BaseModel): name: str = Field(min_length=1, max_length=100) url: HttpUrl + expected_status: int = Field(default=200, ge=100, le=599) + + +class MonitorUpdate(BaseModel): + name: str | None = Field(default=None, min_length=1, max_length=100) + url: HttpUrl | None = None + expected_status: int | None = Field(default=None, ge=100, le=599) + + @model_validator(mode="after") + def not_empty(self) -> "MonitorUpdate": + if not self.model_fields_set: + raise ValueError("at least one field is required") + return self class CurrentStatus(BaseModel): - state: State = State.UNKNOWN + model_config = ConfigDict(frozen=True) + + state: State = State.NEVER_CHECKED checked_at: datetime | None = None - latency_ms: float | None = Field(default=None, ge=0) - status_code: int | None = Field(default=None, ge=100, le=599) - error_code: str | None = None + observed_status: int | None = None + latency_ms: float | None = None + error: str | None = None class Monitor(BaseModel): - id: UUID + model_config = ConfigDict(frozen=True) + + id: UUID = Field(default_factory=uuid4) name: str url: HttpUrl - created_at: datetime - updated_at: datetime - current_status: CurrentStatus + expected_status: int + created_at: datetime = Field(default_factory=now_utc) + updated_at: datetime = Field(default_factory=now_utc) + current_status: CurrentStatus = Field(default_factory=CurrentStatus) class CheckResult(CurrentStatus): @@ -49,5 +66,5 @@ class ErrorBody(BaseModel): message: str -class ErrorEnvelope(BaseModel): +class ErrorResponse(BaseModel): error: ErrorBody