@@ -52,6 +52,45 @@ def _run_alembic_migrations() -> None:
5252 upgrade (alembic_cfg , "head" )
5353
5454
55+ def _stamp_alembic_head_if_fresh () -> None :
56+ """Stamp alembic_version at the current head when the version table is absent
57+ or empty.
58+
59+ After ``SQLModel.metadata.create_all()`` creates tables directly (bypassing
60+ alembic), alembic would otherwise try to re-run the initial migration and fail
61+ with *table already exists*.
62+
63+ When ``alembic_version`` already has a row (e.g. an old backup was restored),
64+ stamping is skipped so that ``alembic upgrade head`` can apply pending
65+ migrations on top of whatever revision the backup was at.
66+ """
67+ from alembic .config import Config
68+ from alembic import command
69+ from alembic .script import ScriptDirectory
70+
71+ import sqlite3
72+
73+ db_path = _extract_db_path (settings .database_url )
74+ conn = sqlite3 .connect (db_path )
75+ try :
76+ cursor = conn .execute (
77+ "SELECT name FROM sqlite_master WHERE type='table' AND name='alembic_version'"
78+ )
79+ table_exists = cursor .fetchone () is not None
80+ if table_exists :
81+ row_count = conn .execute ("SELECT COUNT(*) FROM alembic_version" ).fetchone ()[0 ]
82+ if row_count > 0 :
83+ return
84+ finally :
85+ conn .close ()
86+
87+ alembic_cfg = Config (os .path .join (os .path .dirname (__file__ ), ".." , ".." , "alembic.ini" ))
88+ script = ScriptDirectory .from_config (alembic_cfg )
89+ head = script .get_current_head ()
90+ if head :
91+ command .stamp (alembic_cfg , head )
92+
93+
5594def _recreate_engine () -> None :
5695 """Replace the global SQLAlchemy engine with a fresh one.
5796
@@ -67,6 +106,7 @@ def _recreate_engine() -> None:
67106 connect_args = {"check_same_thread" : False },
68107 )
69108 SQLModel .metadata .create_all (new_engine )
109+ _stamp_alembic_head_if_fresh ()
70110 _run_alembic_migrations ()
71111 db_mod .engine = new_engine
72112
0 commit comments