Configure engine for IMMEDIATE transactions via event hooks. Per SQLAlchemy docs: https://docs.sqlalchemy.org/en/20/dialects/sqlite.html This replaces fragile pysqlite implicit transaction handling with explicit BEGIN IMMEDIATE at transaction start. Benefits: - Acquires write lock
(engine)
| 337 | |
| 338 | |
| 339 | def _configure_sqlite_immediate_transactions(engine) -> None: |
| 340 | """Configure engine for IMMEDIATE transactions via event hooks. |
| 341 | |
| 342 | Per SQLAlchemy docs: https://docs.sqlalchemy.org/en/20/dialects/sqlite.html |
| 343 | |
| 344 | This replaces fragile pysqlite implicit transaction handling with explicit |
| 345 | BEGIN IMMEDIATE at transaction start. Benefits: |
| 346 | - Acquires write lock immediately, preventing stale reads |
| 347 | - Works correctly regardless of prior ORM operations |
| 348 | - Future-proof: won't break when pysqlite legacy mode is removed in Python 3.16 |
| 349 | """ |
| 350 | @event.listens_for(engine, "connect") |
| 351 | def do_connect(dbapi_connection, connection_record): |
| 352 | # Disable pysqlite's implicit transaction handling |
| 353 | dbapi_connection.isolation_level = None |
| 354 | |
| 355 | # Set busy_timeout on raw connection before any transactions |
| 356 | cursor = dbapi_connection.cursor() |
| 357 | try: |
| 358 | cursor.execute("PRAGMA busy_timeout=30000") |
| 359 | finally: |
| 360 | cursor.close() |
| 361 | |
| 362 | @event.listens_for(engine, "begin") |
| 363 | def do_begin(conn): |
| 364 | # Use IMMEDIATE for all transactions to prevent stale reads |
| 365 | conn.exec_driver_sql("BEGIN IMMEDIATE") |
| 366 | |
| 367 | |
| 368 | def create_database(project_dir: Path) -> tuple: |