Create database and return engine + session maker. Uses a cache to avoid creating new engines for each request, which improves performance by reusing database connections. Args: project_dir: Directory containing the project Returns: Tuple of (engine, SessionLo
(project_dir: Path)
| 366 | |
| 367 | |
| 368 | def create_database(project_dir: Path) -> tuple: |
| 369 | """ |
| 370 | Create database and return engine + session maker. |
| 371 | |
| 372 | Uses a cache to avoid creating new engines for each request, which improves |
| 373 | performance by reusing database connections. |
| 374 | |
| 375 | Args: |
| 376 | project_dir: Directory containing the project |
| 377 | |
| 378 | Returns: |
| 379 | Tuple of (engine, SessionLocal) |
| 380 | """ |
| 381 | cache_key = project_dir.as_posix() |
| 382 | |
| 383 | if cache_key in _engine_cache: |
| 384 | return _engine_cache[cache_key] |
| 385 | |
| 386 | db_url = get_database_url(project_dir) |
| 387 | |
| 388 | # Ensure parent directory exists (for .autoforge/ layout) |
| 389 | db_path = get_database_path(project_dir) |
| 390 | db_path.parent.mkdir(parents=True, exist_ok=True) |
| 391 | |
| 392 | # Choose journal mode based on filesystem type |
| 393 | # WAL mode doesn't work reliably on network filesystems and can cause corruption |
| 394 | is_network = _is_network_path(project_dir) |
| 395 | journal_mode = "DELETE" if is_network else "WAL" |
| 396 | |
| 397 | engine = create_engine(db_url, connect_args={ |
| 398 | "check_same_thread": False, |
| 399 | "timeout": 30 # Wait up to 30s for locks |
| 400 | }) |
| 401 | |
| 402 | # Set journal mode BEFORE configuring event hooks |
| 403 | # PRAGMA journal_mode must run outside of a transaction, and our event hooks |
| 404 | # start a transaction with BEGIN IMMEDIATE on every operation |
| 405 | with engine.connect() as conn: |
| 406 | # Get raw DBAPI connection to execute PRAGMA outside transaction |
| 407 | raw_conn = conn.connection.dbapi_connection |
| 408 | if raw_conn is None: |
| 409 | raise RuntimeError("Failed to get raw DBAPI connection") |
| 410 | cursor = raw_conn.cursor() |
| 411 | try: |
| 412 | cursor.execute(f"PRAGMA journal_mode={journal_mode}") |
| 413 | cursor.execute("PRAGMA busy_timeout=30000") |
| 414 | finally: |
| 415 | cursor.close() |
| 416 | |
| 417 | # Configure IMMEDIATE transactions via event hooks AFTER setting PRAGMAs |
| 418 | # This must happen before create_all() and migrations run |
| 419 | _configure_sqlite_immediate_transactions(engine) |
| 420 | |
| 421 | Base.metadata.create_all(bind=engine) |
| 422 | |
| 423 | # Migrate existing databases |
| 424 | _migrate_add_in_progress_column(engine) |
| 425 | _migrate_fix_null_boolean_fields(engine) |
no test coverage detected