Open Notebook API Startup Sequence and Migration Auto-Detection Process
Open Notebook uses a FastAPI lifespan context manager to automatically detect and execute pending SurrealDB schema migrations before the server accepts any requests, ensuring the database is always synchronized with the application code.
When the FastAPI server for Open Notebook (lfnovo/open-notebook) boots, it executes a carefully orchestrated startup sequence that validates the environment, configures security middleware, and runs automatic database migrations. This process ensures that the SurrealDB schema is always up-to-date before any client requests are processed, eliminating manual migration steps and preventing schema mismatches in production.
FastAPI Lifespan and Application Setup
The entry point in api/main.py creates the FastAPI application with a custom lifespan context manager registered at lines 57-61. This pattern ensures that database migrations and security checks run before the server begins accepting traffic, with proper cleanup handled on shutdown.
Environment and Middleware Configuration
The startup begins by loading environment variables via dotenv.load_dotenv() at lines 2-4 of api/main.py. The application then configures middleware in a specific security order: PasswordAuthMiddleware is added at lines 73-86 to ensure authentication runs before CORS processing, followed by CORSMiddleware at lines 88-95. CORS origins are parsed using the _parse_cors_origins helper function (lines 53-64), defaulting to * if not specified.
The Startup Sequence Explained
When the server boots, the lifespan context manager entered at lines 98-102 executes the following sequence:
- Security Validation: Verifies that
OPEN_NOTEBOOK_ENCRYPTION_KEYexists in the environment (lines 107-114), logging a warning if the encryption key is missing. - Migration Manager Initialization: Instantiates
AsyncMigrationManager()at lines 117-119 to handle schema versioning. - Version Detection: Calls
get_current_version()inasync_migrate.py:71-80to query the_sbl_migrationstable, returning the highest version number or0if the table does not exist. - Auto-Detection: The
needs_migration()method (lines 75-78) compares the current version against the number of bundled migration files inopen_notebook/database/migrations/. - Migration Execution: If needed,
run_migration_up()invokesAsyncMigrationRunner.run_all()(lines 84-89) to execute pending.surrealqlscripts sequentially. - Version Tracking: After each migration,
bump_version()inserts a new row into_sbl_migrations(lines 36-44) to track applied changes. - Legacy Migration: Runs
migrate_podcast_profilesfromopen_notebook/podcasts/migration.py(lines 39-45) for backward compatibility.
Once complete, the context yields control to the request handling loop at lines 49-52.
Migration Auto-Detection Mechanism
The automatic migration system relies on version tracking within SurrealDB itself.
Version Storage
The _sbl_migrations table stores the schema history. Each successful migration inserts a row containing the version number and timestamp, created by the bump_version() function in async_migrate.py:36-44.
Detecting Schema Drift
The AsyncMigrationManager loads all .surrealql files from open_notebook/database/migrations/ during initialization. The needs_migration() method returns True when current_version < len(self.up_migrations), triggering the update process.
Sequential Execution
AsyncMigrationRunner.run_all() iterates from the current version to the latest available migration, executing each file and updating the version counter atomically. This ensures that interruptions result in a consistent state, with the version number indicating exactly which migrations succeeded.
Running and Debugging Migrations
To start the API with automatic migrations:
uvicorn api.main:app --host 0.0.0.0 --port 5055
To manually check or run migrations for debugging:
import asyncio
from open_notebook.database.async_migrate import AsyncMigrationManager
async def debug_migrations():
manager = AsyncMigrationManager()
current = await manager.get_current_version()
print(f"Current version: {current}")
if await manager.needs_migration():
await manager.run_migration_up()
print("Migrations applied successfully")
asyncio.run(debug_migrations())
To verify applied versions directly in SurrealDB:
SELECT * FROM _sbl_migrations ORDER BY version;
Summary
- Open Notebook implements a lifespan context manager in
api/main.pythat orchestrates startup tasks before request handling begins. - The
AsyncMigrationManagerauto-detects schema drift by comparing the_sbl_migrationstable version against bundled.surrealqlfiles inopen_notebook/database/migrations/. - Middleware ordering places
PasswordAuthMiddlewarebeforeCORSMiddlewareto ensure authentication precedes CORS handling. - Migrations run automatically and sequentially, with each successful update recorded in the
_sbl_migrationstable. - A legacy podcast profile migration runs separately after the main database schema updates to maintain backward compatibility.
Frequently Asked Questions
How does Open Notebook detect if migrations are needed?
The AsyncMigrationManager queries the _sbl_migrations table using get_current_version() to determine the highest applied migration number. It compares this value against the count of .surrealql files in the migrations directory. If the file count exceeds the stored version, needs_migration() returns True and triggers the automatic update process.
What happens if the OPEN_NOTEBOOK_ENCRYPTION_KEY is missing during startup?
The lifespan context in api/main.py:107-114 validates the presence of OPEN_NOTEBOOK_ENCRYPTION_KEY. If the variable is missing, the system logs a warning but continues starting the server. This allows development environments to run without the key while alerting production operators to potential security configuration gaps.
Can I run migrations manually without starting the full API?
Yes. You can instantiate AsyncMigrationManager directly in an async Python script and call await manager.run_migration_up() to execute pending migrations. This is useful for CI/CD pipelines, debugging, or maintenance windows where you need to verify database state before deploying application code.
Where are migration files stored and what naming convention do they use?
Migration files are stored in open_notebook/database/migrations/ and use the .surrealql extension with numeric filenames (e.g., 1.surrealql, 2.surrealql). The AsyncMigrationRunner executes these files sequentially based on their numeric order, ensuring dependencies are applied in the correct sequence.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →