Download core/db.py from LoveLogicAI/kanbot-backend: direct link, hf CLI and curl.
- Browser
- Download file 3.99 kB
-
https://huggingface.co/spaces/LoveLogicAI/kanbot-backend/resolve/main/core/db.py
- Command line
-
hf download hf://spaces/LoveLogicAI/kanbot-backend/core/db.py
-
curl -L -o db.py https://huggingface.co/spaces/LoveLogicAI/kanbot-backend/resolve/main/core/db.py
3.99 kB
| """ | |
| Thin async Postgres access layer + card (de)serialization. | |
| Kept separate so mcp_server, conductor, and reaper share one pool + one mapper. | |
| """ | |
| from __future__ import annotations | |
| import json | |
| import os | |
| from datetime import datetime, timedelta, timezone | |
| from typing import Optional | |
| import asyncpg | |
| from .contract import Card, Column, Role, ThreadEntry | |
| _POOL: Optional[asyncpg.Pool] = None | |
| async def pool() -> asyncpg.Pool: | |
| global _POOL | |
| if _POOL is None: | |
| dsn = os.environ.get( | |
| "DATABASE_URL", | |
| "postgresql://kanbot:kanbot@localhost:5432/kanbot", | |
| ) | |
| _POOL = await asyncpg.create_pool(dsn, min_size=1, max_size=10) | |
| return _POOL | |
| def _row_to_card(row: asyncpg.Record) -> Card: | |
| return Card( | |
| id=row["id"], | |
| title=row["title"], | |
| column=Column(row["column"]), | |
| agent_role=Role(row["agent_role"]), | |
| spec=row["spec"], | |
| acceptance_criteria=json.loads(row["acceptance_criteria"]), | |
| assigned_worker=row["assigned_worker"], | |
| parent=row["parent"], | |
| children=json.loads(row["children"]), | |
| thread=[ThreadEntry(**e) for e in json.loads(row["thread"])], | |
| lease_expires_at=row["lease_expires_at"].isoformat() | |
| if row["lease_expires_at"] else None, | |
| attempt=row["attempt"], | |
| requires_human=row["requires_human"], | |
| tags=json.loads(row["tags"]), | |
| created_at=row["created_at"].isoformat(), | |
| updated_at=row["updated_at"].isoformat(), | |
| ) | |
| async def get_card(card_id: str) -> Optional[Card]: | |
| async with (await pool()).acquire() as c: | |
| row = await c.fetchrow("SELECT * FROM cards WHERE id = $1", card_id) | |
| return _row_to_card(row) if row else None | |
| async def list_column(column: Column) -> list[Card]: | |
| async with (await pool()).acquire() as c: | |
| rows = await c.fetch( | |
| 'SELECT * FROM cards WHERE "column" = $1 ORDER BY created_at', column.value | |
| ) | |
| return [_row_to_card(r) for r in rows] | |
| async def next_card_id() -> str: | |
| async with (await pool()).acquire() as c: | |
| n = await c.fetchval("SELECT count(*) FROM cards") | |
| return f"CARD-{n + 1}" | |
| async def insert_card(card: Card) -> Card: | |
| async with (await pool()).acquire() as c: | |
| await c.execute( | |
| """INSERT INTO cards | |
| (id,title,"column",agent_role,spec,acceptance_criteria,parent, | |
| children,thread,tags,requires_human,attempt) | |
| VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12)""", | |
| card.id, card.title, card.column.value, card.agent_role.value, | |
| card.spec, json.dumps(card.acceptance_criteria), card.parent, | |
| json.dumps(card.children), json.dumps([e.__dict__ for e in card.thread]), | |
| json.dumps(card.tags), card.requires_human, card.attempt, | |
| ) | |
| return card | |
| async def append_thread(card_id: str, entry: ThreadEntry) -> None: | |
| async with (await pool()).acquire() as c: | |
| await c.execute( | |
| "UPDATE cards SET thread = thread || $2::jsonb WHERE id = $1", | |
| card_id, json.dumps([entry.__dict__]), | |
| ) | |
| async def set_lease(card_id: str, worker: str, seconds: int) -> None: | |
| expiry = datetime.now(timezone.utc) + timedelta(seconds=seconds) | |
| async with (await pool()).acquire() as c: | |
| await c.execute( | |
| "UPDATE cards SET assigned_worker=$2, lease_expires_at=$3 WHERE id=$1", | |
| card_id, worker, expiry, | |
| ) | |
| async def clear_lease(card_id: str) -> None: | |
| async with (await pool()).acquire() as c: | |
| await c.execute( | |
| "UPDATE cards SET assigned_worker=NULL, lease_expires_at=NULL WHERE id=$1", | |
| card_id, | |
| ) | |
| async def add_child(parent_id: str, child_id: str) -> None: | |
| async with (await pool()).acquire() as c: | |
| await c.execute( | |
| "UPDATE cards SET children = children || $2::jsonb WHERE id = $1", | |
| parent_id, json.dumps([child_id]), | |
| ) | |