44from pathlib import Path
55from typing import AsyncGenerator , Optional
66
7+ from basic_memory .config import ProjectConfig
8+ from alembic import command
9+ from alembic .config import Config
10+
711from loguru import logger
812from sqlalchemy import text
913from sqlalchemy .ext .asyncio import (
1418 async_scoped_session ,
1519)
1620
17- from basic_memory .models import Base
1821from basic_memory .models .search import CREATE_SEARCH_INDEX
22+ from basic_memory .repository .search_repository import SearchRepository
1923
2024# Module level state
2125_engine : Optional [AsyncEngine ] = None
@@ -35,7 +39,7 @@ def get_db_url(cls, db_path: Path, db_type: "DatabaseType") -> str:
3539 logger .info ("Using in-memory SQLite database" )
3640 return "sqlite+aiosqlite://"
3741
38- return f"sqlite+aiosqlite:///{ db_path } "
42+ return f"sqlite+aiosqlite:///{ db_path } " # pragma: no cover
3943
4044
4145def get_scoped_session_factory (
@@ -69,21 +73,6 @@ async def scoped_session(
6973 await factory .remove ()
7074
7175
72- async def init_db () -> None :
73- """Initialize database with required tables."""
74- if _session_maker is None : # pragma: no cover
75- raise RuntimeError ("Database session maker not initialized" )
76-
77- logger .info ("Initializing database..." )
78-
79- async with scoped_session (_session_maker ) as session :
80- await session .execute (text ("PRAGMA foreign_keys=ON" ))
81-
82- # recreate search index
83- await session .execute (CREATE_SEARCH_INDEX )
84-
85- await session .commit ()
86-
8776
8877async def get_or_create_db (
8978 db_path : Path ,
@@ -98,9 +87,6 @@ async def get_or_create_db(
9887 _engine = create_async_engine (db_url , connect_args = {"check_same_thread" : False })
9988 _session_maker = async_sessionmaker (_engine , expire_on_commit = False )
10089
101- # Initialize database
102- await init_db ()
103-
10490 assert _engine is not None # for type checker
10591 assert _session_maker is not None # for type checker
10692 return _engine , _session_maker
@@ -120,7 +106,6 @@ async def shutdown_db() -> None: # pragma: no cover
120106async def engine_session_factory (
121107 db_path : Path ,
122108 db_type : DatabaseType = DatabaseType .MEMORY ,
123- init : bool = True ,
124109) -> AsyncGenerator [tuple [AsyncEngine , async_sessionmaker [AsyncSession ]], None ]:
125110 """Create engine and session factory.
126111
@@ -137,9 +122,6 @@ async def engine_session_factory(
137122 try :
138123 _session_maker = async_sessionmaker (_engine , expire_on_commit = False )
139124
140- if init :
141- await init_db ()
142-
143125 assert _engine is not None # for type checker
144126 assert _session_maker is not None # for type checker
145127 yield _engine , _session_maker
@@ -148,3 +130,18 @@ async def engine_session_factory(
148130 await _engine .dispose ()
149131 _engine = None
150132 _session_maker = None
133+
134+
135+ async def run_migrations (app_config : ProjectConfig , database_type = DatabaseType .FILESYSTEM ):
136+ """Run any pending alembic migrations."""
137+ logger .info ("Running database migrations..." )
138+ try :
139+ config = Config ("alembic.ini" )
140+ command .upgrade (config , "head" )
141+ logger .info ("Migrations completed successfully" )
142+
143+ _ , session_maker = await get_or_create_db (app_config .database_path , database_type )
144+ await SearchRepository (session_maker ).init_search_index ()
145+ except Exception as e : # pragma: no cover
146+ logger .error (f"Error running migrations: { e } " )
147+ raise
0 commit comments