Files
copilot-swe-agent[bot] 423c596c28 fix: ensure data persistence across reinstallations
- Add entrypoint.sh: creates /app/data, stamps legacy DBs, runs alembic upgrade head
- Update Dockerfile to use new entrypoint and pre-create /app/data
- Change default DATABASE_URL to sqlite:///./data/dmarq.db
- Add _ensure_sqlite_dir() to database.py for automatic directory creation
- Update docker-compose.yml with app_data named volume for /app/data
- Add 6 tests for _ensure_sqlite_dir and updated default URL

Agent-Logs-Url: https://github.com/christianlouis/dmarq/sessions/2bac317b-97a2-450b-84f1-3e316f7ef54c

Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
2026-03-29 18:53:00 +00:00

78 lines
2.4 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import os
from typing import Generator
from urllib.parse import urlparse, urlunparse
from sqlalchemy import create_engine
from sqlalchemy.engine import make_url
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from app.core.config import get_settings
_ASYNC_TO_SYNC_SCHEMES = {
"postgresql+asyncpg": "postgresql+psycopg2",
}
def _make_sync_db_url(url: str) -> str:
"""Return the synchronous-driver equivalent of *url*.
Kubernetes and docker-compose deployments sometimes configure DATABASE_URL
with an async driver scheme (e.g. ``postgresql+asyncpg://``). Alembic and
the synchronous SQLAlchemy engine used here require a sync driver, so we
map known async schemes to their psycopg2 equivalents.
Only the scheme component of the URL is rewritten; all other parts
(credentials, host, path, query) are left untouched.
"""
parsed = urlparse(url)
sync_scheme = _ASYNC_TO_SYNC_SCHEMES.get(parsed.scheme)
if sync_scheme is None:
return url
return urlunparse(parsed._replace(scheme=sync_scheme))
def _ensure_sqlite_dir(url: str) -> None:
"""Create the parent directory for a SQLite database file if needed.
For SQLite URLs (``sqlite:///relative/path`` or ``sqlite:////absolute/path``),
the parent directory must exist before SQLAlchemy tries to open (or create)
the file. This is a no-op for in-memory databases (``sqlite://``) and for
non-SQLite URLs.
"""
sa_url = make_url(url)
if not sa_url.drivername.startswith("sqlite"):
return
db_path = sa_url.database
if not db_path or db_path == ":memory:":
return # in-memory nothing to create
parent = os.path.dirname(db_path)
if parent:
os.makedirs(parent, exist_ok=True)
settings = get_settings()
_sync_url = _make_sync_db_url(settings.DATABASE_URL)
# Ensure the parent directory exists before SQLAlchemy tries to open the file
_ensure_sqlite_dir(_sync_url)
# Configure SQLAlchemy (normalise async driver schemes to their sync equivalents)
engine = create_engine(_sync_url, pool_pre_ping=True)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
# Create base class for SQLAlchemy models
Base = declarative_base()
def get_db() -> Generator:
"""
Dependency for getting DB sessions
"""
db = SessionLocal()
try:
yield db
finally:
db.close()