Skip to main content

Overview

The session module (data/db/session.py) provides database connection management and session handling using SQLAlchemy. It automatically resolves the database path based on the runtime environment (development vs. production) and ensures proper initialization.

Database Path Resolution

The database path is dynamically computed using the _compute_db_path() function:

Environment-Based Path Logic

  1. Environment Variable Override: If DB_PATH is set, it takes precedence
  2. Production (PyInstaller): %APPDATA%\InkLinkOS\inklink.db (Windows)
  3. Development: dev.db in project root
import os
os.environ["DB_PATH"] = "/path/to/custom/database.db"

Source Reference

See data/db/session.py:13-46 for the complete implementation.

Database Initialization

The module automatically ensures the database exists before creating connections:
def _ensure_db_exists():
    """
    If the database file doesn't exist:
    1. Attempts to copy from template (data/dev.db)
    2. Creates all tables from Base.metadata
    """
This happens automatically on import at data/db/session.py:97.

Core Components

Engine Configuration

from data.db.session import engine

# SQLite engine with SQLAlchemy 2.0 API
engine = create_engine(
    f"sqlite:///{DB_PATH}",
    future=True,
    echo=False
)
Source: data/db/session.py:109
Foreign Keys Enabled: SQLite foreign key constraints are enabled via a connection event listener at data/db/session.py:113-117.

Session Factory

from data.db.session import SessionLocal

# Scoped session factory
SessionLocal = scoped_session(
    sessionmaker(
        bind=engine,
        autoflush=False,
        autocommit=False,
        expire_on_commit=False
    )
)
Source: data/db/session.py:121-123

Usage Examples

Basic Session Usage

from data.db.session import SessionLocal
from data.models.client import Client

# Using context manager (recommended)
with SessionLocal() as db:
    clients = db.query(Client).all()
    # Session automatically closed and rolled back on exception

Transaction Management

from data.db.session import SessionLocal

with SessionLocal() as db:
    db.begin()
    try:
        # Create new client
        client = Client(
            name="John Doe",
            phone="555-1234",
            email="[email protected]"
        )
        db.add(client)
        db.commit()
    except Exception:
        db.rollback()
        raise

Initialize Database Schema

from data.db.session import init_db

# Create all tables if they don't exist
init_db()
Source: data/db/session.py:125-129
init_db() only creates missing tables. It does not alter existing schemas. Use migration scripts for schema changes.

Utility Functions

Get Database Path

from data.db.session import get_db_path

db_path = get_db_path()
print(f"Database location: {db_path}")
# Output: Database location: /path/to/dev.db
Source: data/db/session.py:131-135

Get Data Root Directory

from data.db.session import get_data_root

data_dir = get_data_root()
print(f"Data directory: {data_dir}")
# Output: Data directory: /path/to/project
Source: data/db/session.py:138-145
The data root is the parent directory of the database file. In production, this is typically %APPDATA%\InkLinkOS.

Configuration Variables

VariableTypeDescriptionSource
DB_PATHstrResolved database file pathsession.py:50
DATA_DIRPathBase directory for user datasession.py:102-105
engineEngineSQLAlchemy database enginesession.py:109
SessionLocalscoped_sessionThread-safe session factorysession.py:121-123

Best Practices

Always use with SessionLocal() as db: to ensure sessions are properly closed and resources are released.
# Good
with SessionLocal() as db:
    results = db.query(Client).all()

# Avoid
db = SessionLocal()
results = db.query(Client).all()
db.close()  # Easy to forget!
Use explicit begin() and commit() for multi-operation transactions:
with SessionLocal() as db:
    db.begin()
    try:
        db.add(client)
        db.add(session)
        db.commit()
    except Exception:
        db.rollback()
        raise
The session is configured with expire_on_commit=False, which means objects remain accessible after commit. Be aware this can lead to stale data if you reuse objects across multiple transactions.
with SessionLocal() as db:
    client = db.query(Client).first()
    db.commit()
    # client object is still accessible
    print(client.name)  # Works, but may be stale

Build docs developers (and LLMs) love