Skip to content

Commit 6541ecd

Browse files
committed
better handling of migrations and updating of endpoints
1 parent b93e8ec commit 6541ecd

File tree

2 files changed

+82
-72
lines changed

2 files changed

+82
-72
lines changed

app/main.py

Lines changed: 46 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -239,11 +239,25 @@ class StudioUpgradeResponse(BaseModel):
239239
@asynccontextmanager
240240
async def lifespan(app: FastAPI):
241241
"""Lifespan context manager for the FastAPI application"""
242-
# Create document upload directory on startup
243-
#path_manager.make_dirs(path_manager.upload_dir)
242+
# Startup code
244243
os.makedirs(UPLOAD_DIR, exist_ok=True)
245244
print(f"Document upload directory created at: {UPLOAD_DIR}")
245+
246+
# Run database migrations (init_db() already called by DatabaseManager)
247+
try:
248+
print("Running database migrations via external script...")
249+
result = subprocess.run(
250+
["uv", "run", "python", "run_migrations.py"],
251+
capture_output=True,
252+
text=True,
253+
check=True
254+
)
255+
print(f"Migration completed: {result.stdout}")
256+
except subprocess.CalledProcessError as e:
257+
print(f"Migration warning: {e.stderr}")
258+
246259
yield
260+
print("Application shutting down...")
247261

248262

249263
app = FastAPI(
@@ -338,12 +352,12 @@ def get_timeout_for_request(request: Request) -> float:
338352

339353

340354

341-
@app.on_event("startup")
342-
async def startup_event():
343-
"""Check for and apply any pending migrations on startup"""
344-
success, message = await alembic_manager.handle_database_upgrade()
345-
if not success:
346-
print(f"Warning: {message}")
355+
# @app.on_event("startup")
356+
# async def startup_event():
357+
# """Check for and apply any pending migrations on startup"""
358+
# success, message = await alembic_manager.handle_database_upgrade()
359+
# if not success:
360+
# print(f"Warning: {message}")
347361

348362
@app.post("/get_project_files", include_in_schema=True, responses = responses,
349363
description = "get project file details")
@@ -1318,12 +1332,10 @@ async def get_example_payloads(use_case:UseCase):
13181332
return payload
13191333

13201334

1321-
# Add these two endpoints
13221335
@app.get("/synthesis-studio/check-upgrade", response_model=StudioUpgradeStatus)
13231336
async def check_upgrade_status():
13241337
"""Check if any upgrades are available"""
13251338
try:
1326-
13271339
# Fetch latest changes
13281340
subprocess.run(["git", "fetch"], check=True, capture_output=True)
13291341

@@ -1357,39 +1369,22 @@ async def check_upgrade_status():
13571369
git_remote_commit=remote_commit,
13581370
updates_available=updates_available
13591371
)
1372+
1373+
except subprocess.CalledProcessError as e:
1374+
raise HTTPException(status_code=500, detail=f"Git error: {str(e)}")
13601375
except Exception as e:
13611376
raise HTTPException(status_code=500, detail=str(e))
1362-
13631377

1364-
# Ensure uv is installed
1365-
def ensure_uv_installed():
1366-
try:
1367-
print(subprocess.run(["uv", "--version"], check=True, capture_output=True))
1368-
except (subprocess.CalledProcessError, FileNotFoundError):
1369-
print(subprocess.run(["curl -LsSf https://astral.sh/uv/install.sh | sh"], shell=True, check=True))
1370-
os.environ["PATH"] = f"{os.environ['HOME']}/.cargo/bin:{os.environ['PATH']}"
13711378

1372-
# Setup virtual environment and dependencies
1373-
def setup_environment(PROJECT_ROOT):
1374-
1375-
venv_dir = PROJECT_ROOT / ".venv"
1376-
1377-
# Create venv if it doesn't exist - specifying the path explicitly
1378-
if not venv_dir.exists():
1379-
print(subprocess.run(["uv", "venv", ".venv"], cwd=PROJECT_ROOT, check=True))
1380-
1381-
# Install dependencies with uv pip instead of sync to avoid pyproject.toml parsing issues
1382-
print(subprocess.run(["uv", "pip", "install", "-e", "."], cwd=PROJECT_ROOT, check=True))
13831379

13841380
@app.post("/synthesis-studio/upgrade", response_model=StudioUpgradeResponse)
13851381
async def perform_upgrade():
13861382
"""
13871383
Perform upgrade process:
13881384
1. Pull latest code
1389-
2. Run database migrations with Alembic
1390-
3. Run build_client.sh
1391-
4. Run start_application.py
1392-
5. Restart CML application
1385+
2. Run database migrations
1386+
3. Build frontend (includes dependency sync)
1387+
4. Restart application
13931388
"""
13941389
try:
13951390
messages = []
@@ -1398,75 +1393,65 @@ async def perform_upgrade():
13981393
db_upgraded = False
13991394

14001395
PROJECT_ROOT = Path(os.getcwd())
1396+
14011397
# 1. Git operations
14021398
try:
1403-
14041399
# Stash any changes
1405-
print(subprocess.run(["git", "stash"], check=True, capture_output=True))
1400+
subprocess.run(["git", "stash"], check=True, capture_output=True)
14061401

14071402
# Pull updates
1408-
print(subprocess.run(["git", "pull"], check=True, capture_output=True))
1403+
subprocess.run(["git", "pull"], check=True, capture_output=True)
14091404

14101405
# Try to pop stash
14111406
try:
1412-
print(subprocess.run(["git", "stash", "pop"], check=True, capture_output=True))
1407+
subprocess.run(["git", "stash", "pop"], check=True, capture_output=True)
14131408
except subprocess.CalledProcessError:
14141409
messages.append("Warning: Could not restore local changes")
14151410

14161411
git_updated = True
14171412
messages.append("Git repository updated")
1418-
print(messages)
14191413

14201414
except subprocess.CalledProcessError as e:
14211415
messages.append(f"Git update failed: {e}")
14221416
raise HTTPException(status_code=500, detail=str(e))
14231417

14241418
# 2. Database migrations
14251419
try:
1426-
# In your upgrade endpoint, you can add this debug line:
1427-
print(f"Current working directory: {os.getcwd()}")
1428-
print(f"Alembic.ini exists: {os.path.exists('alembic.ini')}")
1429-
print("--- Starting database migration via external script ---")
1430-
# Use `uv run` to ensure the script runs within the project's virtual environment
1431-
# This is more robust than just calling 'python'
14321420
result = subprocess.run(
14331421
["uv", "run", "python", "run_migrations.py"],
14341422
capture_output=True,
14351423
text=True,
1436-
check=True # This will raise CalledProcessError on failure
1424+
check=True,
1425+
cwd=PROJECT_ROOT
14371426
)
14381427

1439-
print(result.stdout) # Log the output from the script
1428+
print(result.stdout)
14401429
db_upgraded = True
1441-
messages.append("Database migration check completed successfully.")
1442-
except Exception as e:
1430+
messages.append("Database migrations completed")
1431+
except subprocess.CalledProcessError as e:
14431432
messages.append(f"Database migration failed: {str(e)}")
14441433
raise HTTPException(status_code=500, detail=str(e))
14451434

1446-
# 3. Run build_client.sh
1435+
# 3. Build frontend (build_client.sh handles uv and dependencies)
14471436
try:
1448-
1449-
ensure_uv_installed()
1450-
1451-
setup_environment(PROJECT_ROOT)
1452-
1453-
subprocess.run(["bash build/shell_scripts/build_client.sh"], shell=True, check=True)
1437+
subprocess.run(
1438+
["bash", "build/shell_scripts/build_client.sh"],
1439+
check=True,
1440+
cwd=PROJECT_ROOT
1441+
)
14541442
frontend_rebuilt = True
14551443
messages.append("Frontend rebuilt successfully")
14561444
except subprocess.CalledProcessError as e:
14571445
messages.append(f"Frontend build failed: {e}")
14581446
raise HTTPException(status_code=500, detail=str(e))
14591447

1460-
1448+
# 4. Restart application
14611449
if git_updated or frontend_rebuilt or db_upgraded:
14621450
try:
1463-
# Small delay to ensure logs are captured
1464-
time.sleep(10)
1465-
print("application restart will happen now")
1451+
time.sleep(5)
14661452
restart_application()
14671453
messages.append("Application restart initiated")
14681454

1469-
# Note: This response might not reach the client due to the restart
14701455
return StudioUpgradeResponse(
14711456
success=True,
14721457
message="; ".join(messages),
@@ -1479,10 +1464,7 @@ async def perform_upgrade():
14791464
raise HTTPException(status_code=500, detail=str(e))
14801465

14811466
except Exception as e:
1482-
raise HTTPException(
1483-
status_code=500,
1484-
detail=f"Upgrade failed: {str(e)}"
1485-
)
1467+
raise HTTPException(status_code=500, detail=f"Upgrade failed: {str(e)}")
14861468
#****** comment below for testing just backend**************
14871469
current_directory = os.path.dirname(os.path.abspath(__file__))
14881470
client_build_path = os.path.join(current_directory, "client", "dist")

run_migrations.py

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,61 @@
11
import asyncio
22
import sys
33
from pathlib import Path
4-
4+
import subprocess
5+
import os
56
# Ensure the 'app' directory is in the Python path
67
ROOT_DIR = Path(__file__).parent
78
APP_DIR = ROOT_DIR / "app"
89
sys.path.append(str(ROOT_DIR))
910

1011
from app.migrations.alembic_manager import AlembicMigrationManager
1112

13+
14+
15+
# Add this function before main()
16+
def handle_fresh_installation(db_path):
17+
"""Handle installations where init_db() already created schema"""
18+
if os.path.exists(db_path):
19+
# Database exists (created by init_db), check migration status
20+
try:
21+
result = subprocess.run(["alembic", "current"], capture_output=True, text=True, check=True)
22+
print(f"Checking migration status: {result.stdout.strip()}")
23+
24+
# Check if no migration history exists
25+
if not any(len(line.strip()) >= 12 and not line.startswith('INFO')
26+
for line in result.stdout.split('\n')):
27+
print("Database exists (via init_db) but no migration history - stamping with head")
28+
subprocess.run(["alembic", "stamp", "head"], check=True)
29+
return True
30+
except subprocess.CalledProcessError as e:
31+
print(f"Error checking migration status: {e.stderr}")
32+
return False
33+
34+
35+
# Update main() function
1236
async def main():
1337
"""
14-
Initializes the migration manager and runs the database upgrade.
15-
This will always use the latest code from disk.
38+
Migration script that works with init_db() pattern
1639
"""
17-
print("--- Running dedicated migration script ---")
18-
# Assumes your DB file is named metadata.db in the root
40+
print("--- Running migration script ---")
1941
db_path = str(ROOT_DIR / "metadata.db")
20-
alembic_manager = AlembicMigrationManager(db_path)
42+
2143

44+
# Now handle the migration history
45+
if handle_fresh_installation(db_path):
46+
print("Fresh installation: database created and stamped successfully")
47+
return
48+
49+
# For existing installations with migration history, run normal migrations
50+
print("Running incremental migrations...")
51+
alembic_manager = AlembicMigrationManager(db_path)
2252
success, message = await alembic_manager.handle_database_upgrade()
2353

2454
if not success:
2555
print(f"Migration Error: {message}")
26-
# Exit with a non-zero status code to indicate failure
2756
sys.exit(1)
2857

2958
print(f"Migration Success: {message}")
30-
print("--- Migration script finished ---")
3159

3260
if __name__ == "__main__":
3361
asyncio.run(main())

0 commit comments

Comments
 (0)