from datetime import UTC, datetime from enum import StrEnum from uuid import UUID, uuid4 from pydantic import BaseModel, ConfigDict, Field, HttpUrl, model_validator def now_utc() -> datetime: return datetime.now(UTC) class State(StrEnum): NEVER_CHECKED = "never_checked" UP = "up" DOWN = "down" ERROR = "error" BLOCKED = "blocked" 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): model_config = ConfigDict(frozen=True) state: State = State.NEVER_CHECKED checked_at: datetime | None = None observed_status: int | None = None latency_ms: float | None = None error: str | None = None class Monitor(BaseModel): model_config = ConfigDict(frozen=True) id: UUID = Field(default_factory=uuid4) name: str url: HttpUrl 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): monitor_id: UUID class ErrorBody(BaseModel): code: str message: str class ErrorResponse(BaseModel): error: ErrorBody