Get or create a SQLAlchemy engine for a project's assistant database. Uses a cache to avoid creating new engines for each request, which improves performance by reusing database connections. Thread-safe: Uses a lock to prevent race conditions when multiple threads try to create eng
(project_dir: Path)
| 69 | |
| 70 | |
| 71 | def get_engine(project_dir: Path): |
| 72 | """Get or create a SQLAlchemy engine for a project's assistant database. |
| 73 | |
| 74 | Uses a cache to avoid creating new engines for each request, which improves |
| 75 | performance by reusing database connections. |
| 76 | |
| 77 | Thread-safe: Uses a lock to prevent race conditions when multiple threads |
| 78 | try to create engines simultaneously for the same project. |
| 79 | """ |
| 80 | cache_key = project_dir.as_posix() |
| 81 | |
| 82 | # Double-checked locking for thread safety and performance |
| 83 | if cache_key in _engine_cache: |
| 84 | return _engine_cache[cache_key] |
| 85 | |
| 86 | with _cache_lock: |
| 87 | # Check again inside the lock in case another thread created it |
| 88 | if cache_key not in _engine_cache: |
| 89 | db_path = get_db_path(project_dir) |
| 90 | # Use as_posix() for cross-platform compatibility with SQLite connection strings |
| 91 | db_url = f"sqlite:///{db_path.as_posix()}" |
| 92 | engine = create_engine( |
| 93 | db_url, |
| 94 | echo=False, |
| 95 | connect_args={ |
| 96 | "check_same_thread": False, |
| 97 | "timeout": 30, # Wait up to 30s for locks |
| 98 | } |
| 99 | ) |
| 100 | Base.metadata.create_all(engine) |
| 101 | _engine_cache[cache_key] = engine |
| 102 | logger.debug(f"Created new database engine for {cache_key}") |
| 103 | |
| 104 | return _engine_cache[cache_key] |
| 105 | |
| 106 | |
| 107 | def dispose_engine(project_dir: Path) -> bool: |
no test coverage detected