spore-commons/node/spore_node/api/routers/holons.py

103 lines
2.8 KiB
Python

"""Holon management endpoints."""
import uuid
from fastapi import APIRouter, HTTPException
from pydantic import BaseModel
from spore_node.db.connection import get_pool
from spore_node.rid_types import SporeHolon
router = APIRouter(prefix="/holons", tags=["holons"])
class HolonCreate(BaseModel):
slug: str
name: str
holon_type: str = "agent"
description: str = ""
membrane_config: dict = {}
metadata: dict = {}
class HolonResponse(BaseModel):
id: str
rid: str
slug: str
name: str
holon_type: str
description: str
membrane_config: dict
metadata: dict
@router.post("", response_model=HolonResponse, status_code=201)
async def create_holon(data: HolonCreate):
"""Register a new holon in the commons."""
pool = get_pool()
rid = str(SporeHolon(data.slug))
try:
row = await pool.fetchrow(
"""
INSERT INTO holons (rid, slug, name, holon_type, description, membrane_config, metadata)
VALUES ($1, $2, $3, $4, $5, $6::jsonb, $7::jsonb)
RETURNING id, rid, slug, name, holon_type, description, membrane_config, metadata
""",
rid, data.slug, data.name, data.holon_type, data.description,
_json(data.membrane_config), _json(data.metadata),
)
except Exception as e:
if "unique" in str(e).lower():
raise HTTPException(409, f"Holon '{data.slug}' already exists")
raise
# Log event
await pool.execute(
"""
INSERT INTO events (entity_rid, event_kind, payload)
VALUES ($1, 'holon.created', $2::jsonb)
""",
rid, _json({"slug": data.slug, "name": data.name}),
)
return _row_to_dict(row)
@router.get("", response_model=list[HolonResponse])
async def list_holons(holon_type: str | None = None):
"""List all holons, optionally filtered by type."""
pool = get_pool()
if holon_type:
rows = await pool.fetch(
"SELECT * FROM holons WHERE holon_type = $1 ORDER BY created_at",
holon_type,
)
else:
rows = await pool.fetch("SELECT * FROM holons ORDER BY created_at")
return [_row_to_dict(r) for r in rows]
@router.get("/{slug}", response_model=HolonResponse)
async def get_holon(slug: str):
"""Get a single holon by slug."""
pool = get_pool()
row = await pool.fetchrow("SELECT * FROM holons WHERE slug = $1", slug)
if not row:
raise HTTPException(404, f"Holon '{slug}' not found")
return _row_to_dict(row)
def _row_to_dict(row) -> dict:
import json
d = dict(row)
d["id"] = str(d["id"])
for k in ("membrane_config", "metadata"):
if isinstance(d[k], str):
d[k] = json.loads(d[k])
return d
def _json(obj: dict) -> str:
import json
return json.dumps(obj)