Files
ai-rpg/app/schemas/__init__.py
Mikan 7cbe8da103 fix
2026-06-21 07:52:25 +03:00

287 lines
7.7 KiB
Python

"""Pydantic schemas (request/response) for the API layer.
These are NOT the same as the world's JSON-schema — see `app/core/state_validator`
for world-schema validation. Pydantic here only handles HTTP boundary validation.
"""
from __future__ import annotations
import uuid
from datetime import datetime
from typing import Any, Literal
from pydantic import BaseModel, EmailStr, Field, field_validator
# --------------------------------------------------------------------------- #
# Auth
# --------------------------------------------------------------------------- #
class RegisterRequest(BaseModel):
email: EmailStr
username: str = Field(min_length=3, max_length=64, pattern=r"^[a-zA-Z0-9_]+$")
password: str = Field(min_length=8, max_length=128)
password_confirm: str = Field(min_length=8, max_length=128)
@field_validator("password_confirm")
@classmethod
def _match(cls, v, info):
if "password" in info.data and v != info.data["password"]:
raise ValueError("password and password_confirm do not match")
return v
class AdminRegisterRequest(BaseModel):
token: str
email: EmailStr
username: str = Field(min_length=3, max_length=64, pattern=r"^[a-zA-Z0-9_]+$")
password: str = Field(min_length=8, max_length=128)
password_confirm: str = Field(min_length=8, max_length=128)
@field_validator("password_confirm")
@classmethod
def _match(cls, v, info):
if "password" in info.data and v != info.data["password"]:
raise ValueError("password and password_confirm do not match")
return v
class LoginRequest(BaseModel):
login: str = Field(min_length=1, max_length=255) # email OR username
password: str = Field(min_length=1, max_length=128)
class TokenResponse(BaseModel):
access_token: str
refresh_token: str
token_type: str = "bearer"
expires_in: int = 60 * 24
user: "UserPublic"
class UserPublic(BaseModel):
id: uuid.UUID
email: EmailStr
username: str
is_admin: bool
is_active: bool
created_at: datetime
last_login_at: datetime | None = None
model_config = {"from_attributes": True}
# --------------------------------------------------------------------------- #
# Worlds
# --------------------------------------------------------------------------- #
class WorldSummary(BaseModel):
id: uuid.UUID
name: str
description: str | None
language: str
status: str
last_played_at: datetime | None
current_time: str
created_at: datetime
preview_player_name: str | None = None
model_config = {"from_attributes": True}
class WorldFull(BaseModel):
id: uuid.UUID
owner_id: uuid.UUID
preset_id: uuid.UUID | None
name: str
description: str | None
language: str
rules: list
time_schema: dict
schemas: list
environment_schema: list
environment: dict
plot_rails: dict
current_time: str
status: str
intro_scene: str | None
created_at: datetime
updated_at: datetime
last_played_at: datetime | None
model_config = {"from_attributes": True}
class WorldCreateRequest(BaseModel):
mode: Literal["preset", "form"]
preset_id: uuid.UUID | None = None
form_data: dict | None = None
name: str = Field(min_length=1, max_length=255)
language: str = Field(min_length=2, max_length=8, default="en")
player_name: str = Field(min_length=1, max_length=128)
notes: str | None = None
class WorldPatchRequest(BaseModel):
name: str | None = None
description: str | None = None
rules: list | None = None
schemas: list | None = None
environment_schema: list | None = None
environment: dict | None = None
plot_rails: dict | None = None
time_schema: dict | None = None
current_time: str | None = None
intro_scene: str | None = None
status: str | None = None
updated_at: datetime | None = None # for optimistic locking
class WorldEditRequest(BaseModel):
instruction: str = Field(min_length=1, max_length=4000)
# --------------------------------------------------------------------------- #
# Sessions
# --------------------------------------------------------------------------- #
class IterateRequest(BaseModel):
action: str = Field(min_length=1, max_length=4000)
action_source: str = "custom" # accept any string, default to "custom"
class AnswerRequest(BaseModel):
text: str = Field(min_length=1, max_length=4000)
class SessionState(BaseModel):
world: dict
environment: dict
recent_steps: list[dict]
next_actions: list[str]
# --------------------------------------------------------------------------- #
# Presets
# --------------------------------------------------------------------------- #
class PresetSummary(BaseModel):
id: uuid.UUID
name: str
description: str | None
language: str
is_public: bool
status: str
version: int
created_at: datetime
model_config = {"from_attributes": True}
class PresetFull(BaseModel):
id: uuid.UUID
owner_id: uuid.UUID
name: str
description: str | None
language: str
rules: list
time_schema: dict
schemas: list
environment_schema: list
environment_initial: dict
status: str
is_public: bool
version: int
created_at: datetime
updated_at: datetime
model_config = {"from_attributes": True}
class PresetCreateRequest(BaseModel):
name: str = Field(min_length=1, max_length=255)
description: str | None = None
language: str = "en"
rules: list = Field(default_factory=list)
time_schema: dict = Field(default_factory=lambda: {"hours_in_day": 24, "initial_date": "day_1_hour_8"})
schemas: list = Field(default_factory=list)
environment_schema: list = Field(default_factory=list)
environment_initial: dict = Field(default_factory=dict)
is_public: bool = False
# --------------------------------------------------------------------------- #
# Admin
# --------------------------------------------------------------------------- #
class SettingsPatchRequest(BaseModel):
"""A flat dict of {setting_key: value} to upsert."""
settings: dict[str, Any]
class LlmLogOut(BaseModel):
id: uuid.UUID
stage: str
model: str
status: str
latency_ms: int | None
prompt_tokens: int | None
completion_tokens: int | None
error_message: str | None
created_at: datetime
model_config = {"from_attributes": True}
class LlmLogDetail(BaseModel):
id: uuid.UUID
user_id: uuid.UUID | None
world_id: uuid.UUID | None
step_id: uuid.UUID | None
stage: str
model: str
request_messages: list
request_tools: list | None
response_message: dict
tool_calls: list | None
prompt_tokens: int | None
completion_tokens: int | None
latency_ms: int | None
temperature: float | None
status: str
error_message: str | None
created_at: datetime
model_config = {"from_attributes": True}
class TestLlmRequest(BaseModel):
api_url: str | None = None
api_key: str | None = None
model: str | None = None
class TestEmbeddingsRequest(BaseModel):
api_url: str | None = None
api_key: str | None = None
model: str | None = None
provider: str | None = None
# --------------------------------------------------------------------------- #
# Misc
# --------------------------------------------------------------------------- #
class HealthResponse(BaseModel):
status: str
db: bool
qdrant: bool
llm: bool
embeddings: bool
version: str
class ErrorOut(BaseModel):
error: dict[str, Any]
# --------------------------------------------------------------------------- #
# Forward refs
# --------------------------------------------------------------------------- #
TokenResponse.model_rebuild()