fix(models): Literal types on topology enum fields, hoist _MUTATION_OPS, top-level json import
MutationRow.op was str despite _MUTATION_OPS existing; Topology.mode/status, TopologyDecky.state, TopologyMutation.op/state carried valid values only in comments; deferred json import had no justification. - Promote _MUTATION_OPS before table classes so table fields can reference it - Add sa_column=Column(String) on each Literal-annotated table field to satisfy SQLModel 0.0.38 column-type inference - Move import json to module top; remove deferred import inside _decode_json_payload - MutationRow.op: str -> _MUTATION_OPS
This commit is contained in:
@@ -1,14 +1,25 @@
|
|||||||
"""MazeNET topology tables + the REST DTOs that wrap them."""
|
"""MazeNET topology tables + the REST DTOs that wrap them."""
|
||||||
|
import json
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
from typing import Annotated, Any, Literal, Optional
|
from typing import Annotated, Any, Literal, Optional
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
from pydantic import BaseModel, BeforeValidator, ConfigDict, Field as PydanticField
|
from pydantic import BaseModel, BeforeValidator, ConfigDict, Field as PydanticField
|
||||||
from sqlalchemy import Column, Index, Text, UniqueConstraint
|
from sqlalchemy import Column, Index, String, Text, UniqueConstraint
|
||||||
from sqlmodel import Field, SQLModel
|
from sqlmodel import Field, SQLModel
|
||||||
|
|
||||||
from ._base import _BIG_TEXT
|
from ._base import _BIG_TEXT
|
||||||
|
|
||||||
|
_MUTATION_OPS = Literal[
|
||||||
|
"add_lan",
|
||||||
|
"remove_lan",
|
||||||
|
"add_decky",
|
||||||
|
"attach_decky",
|
||||||
|
"detach_decky",
|
||||||
|
"remove_decky",
|
||||||
|
"update_decky",
|
||||||
|
"update_lan",
|
||||||
|
]
|
||||||
|
|
||||||
# --- MazeNET tables ---
|
# --- MazeNET tables ---
|
||||||
# Nested deception topologies: an arbitrary-depth DAG of LANs connected by
|
# Nested deception topologies: an arbitrary-depth DAG of LANs connected by
|
||||||
@@ -19,7 +30,9 @@ class Topology(SQLModel, table=True):
|
|||||||
__tablename__ = "topologies"
|
__tablename__ = "topologies"
|
||||||
id: str = Field(default_factory=lambda: str(uuid4()), primary_key=True)
|
id: str = Field(default_factory=lambda: str(uuid4()), primary_key=True)
|
||||||
name: str = Field(index=True, unique=True)
|
name: str = Field(index=True, unique=True)
|
||||||
mode: str = Field(default="unihost") # unihost|agent
|
mode: Literal["unihost", "agent"] = Field(
|
||||||
|
default="unihost", sa_column=Column("mode", String, nullable=False, default="unihost")
|
||||||
|
)
|
||||||
# When ``mode == "agent"``, pins this topology to a specific enrolled
|
# When ``mode == "agent"``, pins this topology to a specific enrolled
|
||||||
# worker. ``None`` for unihost topologies (master-local deploy).
|
# worker. ``None`` for unihost topologies (master-local deploy).
|
||||||
target_host_uuid: Optional[str] = Field(
|
target_host_uuid: Optional[str] = Field(
|
||||||
@@ -29,9 +42,12 @@ class Topology(SQLModel, table=True):
|
|||||||
config_snapshot: str = Field(
|
config_snapshot: str = Field(
|
||||||
sa_column=Column("config_snapshot", _BIG_TEXT, nullable=False, default="{}")
|
sa_column=Column("config_snapshot", _BIG_TEXT, nullable=False, default="{}")
|
||||||
)
|
)
|
||||||
status: str = Field(
|
status: Literal[
|
||||||
default="pending", index=True
|
"pending", "deploying", "active", "degraded", "failed", "tearing_down", "torn_down"
|
||||||
) # pending|deploying|active|degraded|failed|tearing_down|torn_down
|
] = Field(
|
||||||
|
default="pending",
|
||||||
|
sa_column=Column("status", String, nullable=False, default="pending", index=True),
|
||||||
|
)
|
||||||
status_changed_at: datetime = Field(
|
status_changed_at: datetime = Field(
|
||||||
default_factory=lambda: datetime.now(timezone.utc)
|
default_factory=lambda: datetime.now(timezone.utc)
|
||||||
)
|
)
|
||||||
@@ -101,10 +117,12 @@ class TopologyDecky(SQLModel, table=True):
|
|||||||
default=None, sa_column=Column("decky_config", _BIG_TEXT, nullable=True)
|
default=None, sa_column=Column("decky_config", _BIG_TEXT, nullable=True)
|
||||||
)
|
)
|
||||||
ip: Optional[str] = Field(default=None)
|
ip: Optional[str] = Field(default=None)
|
||||||
# Same vocabulary as DeckyShard.state to keep dashboard rendering uniform.
|
state: Literal[
|
||||||
state: str = Field(
|
"pending", "running", "failed", "torn_down", "degraded", "tearing_down", "teardown_failed"
|
||||||
default="pending", index=True
|
] = Field(
|
||||||
) # pending|running|failed|torn_down|degraded|tearing_down|teardown_failed
|
default="pending",
|
||||||
|
sa_column=Column("state", String, nullable=False, default="pending", index=True),
|
||||||
|
)
|
||||||
last_error: Optional[str] = Field(
|
last_error: Optional[str] = Field(
|
||||||
default=None, sa_column=Column("last_error", Text, nullable=True)
|
default=None, sa_column=Column("last_error", Text, nullable=True)
|
||||||
)
|
)
|
||||||
@@ -168,15 +186,14 @@ class TopologyMutation(SQLModel, table=True):
|
|||||||
)
|
)
|
||||||
id: str = Field(default_factory=lambda: str(uuid4()), primary_key=True)
|
id: str = Field(default_factory=lambda: str(uuid4()), primary_key=True)
|
||||||
topology_id: str = Field(foreign_key="topologies.id", index=True)
|
topology_id: str = Field(foreign_key="topologies.id", index=True)
|
||||||
# add_lan|remove_lan|add_decky|attach_decky|detach_decky|
|
op: _MUTATION_OPS = Field(sa_column=Column("op", String, nullable=False, index=True))
|
||||||
# remove_decky|update_decky|update_lan
|
|
||||||
op: str = Field(index=True)
|
|
||||||
# JSON-serialised op payload (keys depend on ``op``).
|
|
||||||
payload: str = Field(
|
payload: str = Field(
|
||||||
sa_column=Column("payload", _BIG_TEXT, nullable=False, default="{}")
|
sa_column=Column("payload", _BIG_TEXT, nullable=False, default="{}")
|
||||||
)
|
)
|
||||||
# pending|applying|applied|failed
|
state: Literal["pending", "applying", "applied", "failed"] = Field(
|
||||||
state: str = Field(default="pending", index=True)
|
default="pending",
|
||||||
|
sa_column=Column("state", String, nullable=False, default="pending", index=True),
|
||||||
|
)
|
||||||
requested_at: datetime = Field(
|
requested_at: datetime = Field(
|
||||||
default_factory=lambda: datetime.now(timezone.utc), index=True
|
default_factory=lambda: datetime.now(timezone.utc), index=True
|
||||||
)
|
)
|
||||||
@@ -332,18 +349,6 @@ class EdgeCreateRequest(BaseModel):
|
|||||||
expected_version: Optional[int] = None
|
expected_version: Optional[int] = None
|
||||||
|
|
||||||
|
|
||||||
_MUTATION_OPS = Literal[
|
|
||||||
"add_lan",
|
|
||||||
"remove_lan",
|
|
||||||
"add_decky",
|
|
||||||
"attach_decky",
|
|
||||||
"detach_decky",
|
|
||||||
"remove_decky",
|
|
||||||
"update_decky",
|
|
||||||
"update_lan",
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class MutationEnqueueRequest(BaseModel):
|
class MutationEnqueueRequest(BaseModel):
|
||||||
op: _MUTATION_OPS
|
op: _MUTATION_OPS
|
||||||
payload: dict[str, Any] = PydanticField(default_factory=dict)
|
payload: dict[str, Any] = PydanticField(default_factory=dict)
|
||||||
@@ -353,8 +358,7 @@ class MutationEnqueueRequest(BaseModel):
|
|||||||
def _decode_json_payload(v: Any) -> Any:
|
def _decode_json_payload(v: Any) -> Any:
|
||||||
"""Accept either a dict or a JSON-encoded string for mutation payloads."""
|
"""Accept either a dict or a JSON-encoded string for mutation payloads."""
|
||||||
if isinstance(v, str):
|
if isinstance(v, str):
|
||||||
import json as _json
|
return json.loads(v) if v else {}
|
||||||
return _json.loads(v) if v else {}
|
|
||||||
return v
|
return v
|
||||||
|
|
||||||
|
|
||||||
@@ -365,7 +369,7 @@ class MutationRow(BaseModel):
|
|||||||
model_config = ConfigDict(extra="ignore")
|
model_config = ConfigDict(extra="ignore")
|
||||||
id: str
|
id: str
|
||||||
topology_id: str
|
topology_id: str
|
||||||
op: str
|
op: _MUTATION_OPS
|
||||||
payload: _MutationPayload = PydanticField(default_factory=dict)
|
payload: _MutationPayload = PydanticField(default_factory=dict)
|
||||||
state: str
|
state: str
|
||||||
requested_at: datetime
|
requested_at: datetime
|
||||||
|
|||||||
Reference in New Issue
Block a user