Coverage for src / quber / db / connection.py: 100%
50 statements
« prev ^ index » next coverage.py v7.14.0, created at 2026-09-23 22:14 -0400
« prev ^ index » next coverage.py v7.14.0, created at 2026-09-23 22:14 -0400
1"""Database connection utilities for Quber RAG system."""
3from contextlib import contextmanager
4from typing import Generator
6from loguru import logger
7from sqlalchemy import create_engine
8from sqlalchemy.engine import Engine
9from sqlalchemy.orm import Session, sessionmaker
11from quber.db.models import Base
12from quber.settings import get_settings
15def get_database_url() -> str:
16 """
17 Construct database URL from environment variables.
19 Returns:
20 Database connection URL string.
22 Raises:
23 ValueError: If required environment variables are missing.
24 """
25 db_settings = get_settings().db
26 user = db_settings.user
27 password = db_settings.password
28 host = db_settings.host
29 port = db_settings.port
30 db = db_settings.db
32 url = f"postgresql+psycopg://{user}:{password}@{host}:{port}/{db}"
33 logger.debug(f"Database URL: postgresql+psycopg://{user}:***@{host}:{port}/{db}")
34 return url
37def get_engine(echo: bool = False) -> Engine:
38 """
39 Create and return a SQLAlchemy engine.
41 Args:
42 echo: If True, log all SQL statements (useful for debugging).
44 Returns:
45 SQLAlchemy Engine instance.
46 """
47 url = get_database_url()
48 engine = create_engine(url, echo=echo, pool_pre_ping=True)
49 logger.info(f"Database engine created: {engine.url.database}@{engine.url.host}")
50 return engine
53def init_db(engine: Engine | None = None, drop_all: bool = False) -> None:
54 """
55 Initialize database schema.
57 Args:
58 engine: SQLAlchemy engine. If None, creates a new one.
59 drop_all: If True, drop all tables before creating (WARNING: data loss).
60 """
61 if engine is None:
62 engine = get_engine()
64 if drop_all:
65 logger.warning("Dropping all tables...")
66 Base.metadata.drop_all(engine)
68 logger.info("Creating database tables...")
69 Base.metadata.create_all(engine)
70 logger.success("Database initialized successfully")
73def get_session_factory(engine: Engine | None = None) -> sessionmaker[Session]:
74 """
75 Create a session factory.
77 Args:
78 engine: SQLAlchemy engine. If None, creates a new one.
80 Returns:
81 SQLAlchemy sessionmaker.
82 """
83 if engine is None:
84 engine = get_engine()
86 return sessionmaker(bind=engine, expire_on_commit=False)
89@contextmanager
90def get_session(engine: Engine | None = None) -> Generator[Session, None, None]:
91 """
92 Context manager for database sessions.
94 Args:
95 engine: SQLAlchemy engine. If None, creates a new one.
97 Yields:
98 Database session.
100 Example:
101 ```python
102 with get_session() as session:
103 documents = session.query(Document).all()
104 ```
105 """
106 if engine is None:
107 engine = get_engine()
109 SessionLocal = sessionmaker(bind=engine, expire_on_commit=False)
110 session = SessionLocal()
111 try:
112 yield session
113 session.commit()
114 except Exception as e:
115 session.rollback()
116 logger.error(f"Session error: {e}")
117 raise
118 finally:
119 session.close()