feat: add SQLite database layer with SQLModel
Add shared database infrastructure to clide core for use by both the CLI and clide-web. SQLModel provides Pydantic-native models that double as SQLAlchemy table definitions. Models: Project, Session, UserPreference, ConnectionLog. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
"""Pydantic models for Clide."""
|
||||
|
||||
from clide.models.config import ClideSettings, PanelConfig
|
||||
from clide.models.db import ConnectionLog, Project, Session, UserPreference
|
||||
from clide.models.diff import ChangeType, DiffContent, DiffHunk, DiffLine, DiffViewState
|
||||
from clide.models.editor import CursorPosition, EditorState, FileBuffer, Selection
|
||||
from clide.models.git import (
|
||||
@@ -56,4 +57,9 @@ __all__ = [
|
||||
"TabInfo",
|
||||
"TabType",
|
||||
"TAB_ICONS",
|
||||
# Database (SQLModel)
|
||||
"Project",
|
||||
"Session",
|
||||
"UserPreference",
|
||||
"ConnectionLog",
|
||||
]
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
"""SQLModel table models for persistent storage.
|
||||
|
||||
These models serve both the standalone Clide TUI and the clide-web server.
|
||||
They ARE Pydantic models (SQLModel inherits from BaseModel).
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from sqlmodel import Field, SQLModel
|
||||
|
||||
|
||||
class Project(SQLModel, table=True):
|
||||
"""A project (git repo) that can be opened in Clide."""
|
||||
|
||||
id: int | None = Field(default=None, primary_key=True)
|
||||
name: str = Field(unique=True, index=True)
|
||||
path: str
|
||||
theme: str = "summer-night"
|
||||
last_accessed: datetime | None = None
|
||||
created_at: datetime = Field(default_factory=datetime.utcnow)
|
||||
|
||||
|
||||
class Session(SQLModel, table=True):
|
||||
"""A tmux session running a Clide instance (used by clide-web)."""
|
||||
|
||||
id: int | None = Field(default=None, primary_key=True)
|
||||
project_name: str = Field(index=True)
|
||||
tmux_session: str = Field(unique=True)
|
||||
pid: int | None = None
|
||||
status: str = "active"
|
||||
created_at: datetime = Field(default_factory=datetime.utcnow)
|
||||
last_activity: datetime = Field(default_factory=datetime.utcnow)
|
||||
|
||||
|
||||
class UserPreference(SQLModel, table=True):
|
||||
"""Key-value user preferences persisted across restarts."""
|
||||
|
||||
id: int | None = Field(default=None, primary_key=True)
|
||||
key: str = Field(unique=True, index=True)
|
||||
value: str
|
||||
|
||||
|
||||
class ConnectionLog(SQLModel, table=True):
|
||||
"""Audit log of browser connections (used by clide-web)."""
|
||||
|
||||
id: int | None = Field(default=None, primary_key=True)
|
||||
project_name: str
|
||||
client_ip: str
|
||||
connected_at: datetime = Field(default_factory=datetime.utcnow)
|
||||
disconnected_at: datetime | None = None
|
||||
@@ -0,0 +1,48 @@
|
||||
"""SQLite database engine and session management.
|
||||
|
||||
Used by both standalone Clide and clide-web. The database file
|
||||
defaults to ~/.clide/clide.db but is configurable.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Generator
|
||||
from pathlib import Path
|
||||
|
||||
from sqlmodel import Session as DBSession
|
||||
from sqlmodel import SQLModel, create_engine
|
||||
|
||||
_engine = None
|
||||
|
||||
DEFAULT_DB_PATH = Path.home() / ".clide" / "clide.db"
|
||||
|
||||
|
||||
def get_engine(db_path: Path | None = None):
|
||||
"""Create or return the SQLAlchemy engine."""
|
||||
global _engine
|
||||
if _engine is None:
|
||||
path = db_path or DEFAULT_DB_PATH
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
_engine = create_engine(
|
||||
f"sqlite:///{path}",
|
||||
echo=False,
|
||||
connect_args={"check_same_thread": False},
|
||||
)
|
||||
return _engine
|
||||
|
||||
|
||||
def init_db(db_path: Path | None = None) -> None:
|
||||
"""Create all tables if they don't exist."""
|
||||
# Import models so SQLModel registers them
|
||||
import clide.models.db # noqa: F401
|
||||
|
||||
engine = get_engine(db_path)
|
||||
SQLModel.metadata.create_all(engine)
|
||||
|
||||
|
||||
def get_db() -> Generator[DBSession, None, None]:
|
||||
"""Yield a database session. Usable as a FastAPI dependency or context manager."""
|
||||
if _engine is None:
|
||||
raise RuntimeError("Database not initialized — call init_db() first")
|
||||
with DBSession(_engine) as session:
|
||||
yield session
|
||||
@@ -32,6 +32,8 @@ dependencies = [
|
||||
"rich>=13.0.0",
|
||||
"watchdog>=4.0.0",
|
||||
"wcwidth>=0.2.0", # Required by vendored pyte
|
||||
"sqlmodel>=0.0.16",
|
||||
"aiosqlite>=0.20",
|
||||
# Tree-sitter for syntax highlighting
|
||||
"tree-sitter>=0.21.0",
|
||||
"tree-sitter-python>=0.21.0",
|
||||
|
||||
Reference in New Issue
Block a user