nemocity / server /schemas.py
AndresCarreon's picture
NEMOCITY v0 — mock backend, gradio 6.16.0 (pre-SSR)
d72231c verified
Raw
History Blame Contribute Delete
2.74 kB
"""Pydantic request/response models for the NEMOCITY HTTP API.
Shapes follow ARCHITECTURE.md §HTTP API exactly (wire names keep godseed's wish_*;
"petition" is user-facing copy only):
GET /api/state -> StateResponse (includes server_now for the shared sim clock)
POST /api/wish {text} -> WishAccepted (errors: 400/429/503 with a city-hall `detail`)
POST /api/fix -> WishAccepted (409 when traffic is flowing smoothly)
GET /api/wishes -> list[WishSummary]
GET /api/wishes/{id} -> raw WishTrace dict (passed through, engine-owned shape)
Features and WishTraces are deliberately NOT strict models here: the engine owns those
shapes and the server passes them through untouched (the "her" pattern — transport never
reinterprets city facts).
"""
from __future__ import annotations
from typing import Any, Optional
from pydantic import BaseModel, ConfigDict, Field, field_validator
# The moderation fast-path enforces the real petition limit (<=160 chars) with a
# readable reason; this transport-level cap only stops abusive megabyte bodies before
# they reach moderation.
TRANSPORT_MAX_WISH_CHARS = 2000
class WishRequest(BaseModel):
"""Body of POST /api/wish (a petition)."""
model_config = ConfigDict(str_strip_whitespace=True)
text: str = Field(..., min_length=1, max_length=TRANSPORT_MAX_WISH_CHARS)
@field_validator("text")
@classmethod
def _not_blank(cls, value: str) -> str:
if not value.strip():
raise ValueError("a petition needs words")
return value.strip()
class WishAccepted(BaseModel):
"""Response of POST /api/wish and POST /api/fix."""
wish_id: str
position: int = Field(..., ge=1, description="1-based place in the queue")
class CurrentWish(BaseModel):
"""The petition city hall is processing right now."""
wish_id: str
text_preview: str
class QueueInfo(BaseModel):
length: int = 0
current: Optional[CurrentWish] = None
class WorldState(BaseModel):
"""Snapshot of the deterministic city: ordered event list + counters."""
version: int = Field(..., ge=0, description="total number of features")
epoch: int = Field(..., ge=0, description="number of granted petitions")
features: list[dict[str, Any]]
class WishSummary(BaseModel):
"""One row of the ledger index (GET /api/wishes)."""
wish_id: str
text: str
epitaph: str = ""
epoch: int
ts: float
class StateResponse(BaseModel):
"""GET /api/state. server_now anchors every client's shared sim clock
(simTime = server_now - CITY_EPOCH_S); clients slew toward it, never snap."""
world: WorldState
queue: QueueInfo
wishes_recent: list[WishSummary]
server_now: float