From bf7f62ca1b305ed75de3080ea06196221518b995 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 8 Feb 2026 07:13:48 +0000 Subject: [PATCH 1/3] Initial plan From ad31f26beb9a4a0821b7c3680083454fd499cde3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 8 Feb 2026 07:16:57 +0000 Subject: [PATCH 2/3] Migrate from deprecated @app.on_event to lifespan context manager - Convert sync and async startup handlers to single lifespan function - Remove deprecated @app.on_event("startup") and @app.on_event("shutdown") - Add asynccontextmanager import for lifespan pattern - Consolidate all startup/shutdown logic into one place - Fixes Python 3.14 compatibility issue causing startup failure Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com> --- app/main.py | 68 +++++++++++++++++++++++++++---------------------- test_startup.py | 44 ++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+), 31 deletions(-) create mode 100644 test_startup.py diff --git a/app/main.py b/app/main.py index 64961a07..955e9199 100644 --- a/app/main.py +++ b/app/main.py @@ -2,6 +2,7 @@ import os import logging import pathlib +from contextlib import asynccontextmanager from fastapi import FastAPI, HTTPException, Request, status from fastapi.responses import JSONResponse @@ -34,30 +35,14 @@ if settings.auth_enabled and not settings.session_secret: raise ValueError("SESSION_SECRET must be set when AUTH_ENABLED=True. Generate one with: python -c 'import secrets; print(secrets.token_hex(32))'") SESSION_SECRET = settings.session_secret or "INSECURE_DEFAULT_FOR_DEVELOPMENT_ONLY_DO_NOT_USE_IN_PRODUCTION_MINIMUM_32_CHARS" -app = FastAPI(title="DocuElevate") -# 1) Session Middleware (for request.session to work) -app.add_middleware(SessionMiddleware, secret_key=SESSION_SECRET) - -# 2) Respect the X-Forwarded-* headers from Traefik -app.add_middleware(ProxyHeadersMiddleware, trusted_hosts="*") - -# 3) (Optional but recommended) Restrict valid hosts: -app.add_middleware(TrustedHostMiddleware, allowed_hosts=[ - settings.external_hostname, - "localhost", - "127.0.0.1" -]) - -# Mount the static files directory -static_dir = pathlib.Path(__file__).parents[1] / "frontend" / "static" -if os.path.exists(static_dir): - app.mount("/static", StaticFiles(directory=str(static_dir)), name="static") -else: - print(f"WARNING: Static directory not found at {static_dir}. Static files will not be served.") - -@app.on_event("startup") -def on_startup(): +@asynccontextmanager +async def lifespan(app: FastAPI): + """ + Manage application lifespan events (startup and shutdown). + This replaces the deprecated @app.on_event decorators. + """ + # Startup: Initialize database init_db() # Create tables if they don't exist # Load settings from database after DB initialization @@ -72,10 +57,7 @@ def on_startup(): logging.error(f"Failed to load database settings: {e}") finally: db.close() - -@app.on_event("startup") -async def startup_event(): - """Run startup tasks for the application""" + # Force settings dump to log for troubleshooting from app.utils.config_validator import dump_all_settings dump_all_settings() @@ -97,15 +79,39 @@ async def startup_event(): # Send startup notification notify_startup() - -@app.on_event("shutdown") -async def shutdown_event(): - """Run shutdown tasks for the application""" + + # Application is now running + yield + + # Shutdown: Cleanup tasks logging.info("Application shutting down") # Send shutdown notification notify_shutdown() + +app = FastAPI(title="DocuElevate", lifespan=lifespan) + +# 1) Session Middleware (for request.session to work) +app.add_middleware(SessionMiddleware, secret_key=SESSION_SECRET) + +# 2) Respect the X-Forwarded-* headers from Traefik +app.add_middleware(ProxyHeadersMiddleware, trusted_hosts="*") + +# 3) (Optional but recommended) Restrict valid hosts: +app.add_middleware(TrustedHostMiddleware, allowed_hosts=[ + settings.external_hostname, + "localhost", + "127.0.0.1" +]) + +# Mount the static files directory +static_dir = pathlib.Path(__file__).parents[1] / "frontend" / "static" +if os.path.exists(static_dir): + app.mount("/static", StaticFiles(directory=str(static_dir)), name="static") +else: + print(f"WARNING: Static directory not found at {static_dir}. Static files will not be served.") + # Custom exception handlers that return JSON for API routes and HTML for frontend routes @app.exception_handler(HTTPException) async def http_exception_handler(request: Request, exc: HTTPException): diff --git a/test_startup.py b/test_startup.py new file mode 100644 index 00000000..8b5389d1 --- /dev/null +++ b/test_startup.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python3 +""" +Simple test script to verify the application can start. +This script tests that the FastAPI app with lifespan can be created. +""" +import sys +import os + +# Set up minimal environment +os.environ.setdefault("DATABASE_URL", "sqlite:///:memory:") +os.environ.setdefault("REDIS_URL", "redis://localhost:6379/1") +os.environ.setdefault("OPENAI_API_KEY", "test-key") +os.environ.setdefault("AZURE_AI_KEY", "test-key") +os.environ.setdefault("AZURE_REGION", "test") +os.environ.setdefault("AZURE_ENDPOINT", "https://test.cognitiveservices.azure.com/") +os.environ.setdefault("GOTENBERG_URL", "http://localhost:3000") +os.environ.setdefault("WORKDIR", "/tmp") +os.environ.setdefault("AUTH_ENABLED", "False") +os.environ.setdefault("SESSION_SECRET", "test_secret_key_for_testing_must_be_at_least_32_characters_long") +os.environ.setdefault("EXTERNAL_HOSTNAME", "localhost") + +try: + # Import the app - this will fail if there are syntax errors or import issues + from app.main import app + print("✓ Successfully imported app.main") + + # Check that the app has the lifespan configured + if hasattr(app, 'router') and hasattr(app.router, 'lifespan_context'): + print("✓ App has lifespan context configured") + else: + print("✓ App created successfully (lifespan may not be directly testable)") + + # Check that there are no on_event handlers registered + # (In newer FastAPI versions, on_event is deprecated) + print("✓ App configuration looks correct") + + print("\n✅ All checks passed! The application should start correctly.") + sys.exit(0) + +except Exception as e: + print(f"\n❌ Error importing or configuring app: {e}") + import traceback + traceback.print_exc() + sys.exit(1) From b6755f8b365cbced1d286dcd5c3d65d6bb35d626 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 8 Feb 2026 07:18:21 +0000 Subject: [PATCH 3/3] Fix line length to comply with Black formatting (120 chars) - Break long line in lifespan function to multiple lines - Remove test_startup.py (not needed, existing tests validate startup) Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com> --- app/main.py | 4 +++- test_startup.py | 44 -------------------------------------------- 2 files changed, 3 insertions(+), 45 deletions(-) delete mode 100644 test_startup.py diff --git a/app/main.py b/app/main.py index 955e9199..14e1a010 100644 --- a/app/main.py +++ b/app/main.py @@ -66,7 +66,9 @@ async def lifespan(app: FastAPI): config_issues = check_all_configs() # Log overall status - has_issues = any(config_issues['email']) or any(len(issues) > 0 for provider, issues in config_issues['storage'].items()) + has_issues = any(config_issues['email']) or any( + len(issues) > 0 for provider, issues in config_issues['storage'].items() + ) if has_issues: logging.warning("Application started with configuration issues - some features may be unavailable") else: diff --git a/test_startup.py b/test_startup.py deleted file mode 100644 index 8b5389d1..00000000 --- a/test_startup.py +++ /dev/null @@ -1,44 +0,0 @@ -#!/usr/bin/env python3 -""" -Simple test script to verify the application can start. -This script tests that the FastAPI app with lifespan can be created. -""" -import sys -import os - -# Set up minimal environment -os.environ.setdefault("DATABASE_URL", "sqlite:///:memory:") -os.environ.setdefault("REDIS_URL", "redis://localhost:6379/1") -os.environ.setdefault("OPENAI_API_KEY", "test-key") -os.environ.setdefault("AZURE_AI_KEY", "test-key") -os.environ.setdefault("AZURE_REGION", "test") -os.environ.setdefault("AZURE_ENDPOINT", "https://test.cognitiveservices.azure.com/") -os.environ.setdefault("GOTENBERG_URL", "http://localhost:3000") -os.environ.setdefault("WORKDIR", "/tmp") -os.environ.setdefault("AUTH_ENABLED", "False") -os.environ.setdefault("SESSION_SECRET", "test_secret_key_for_testing_must_be_at_least_32_characters_long") -os.environ.setdefault("EXTERNAL_HOSTNAME", "localhost") - -try: - # Import the app - this will fail if there are syntax errors or import issues - from app.main import app - print("✓ Successfully imported app.main") - - # Check that the app has the lifespan configured - if hasattr(app, 'router') and hasattr(app.router, 'lifespan_context'): - print("✓ App has lifespan context configured") - else: - print("✓ App created successfully (lifespan may not be directly testable)") - - # Check that there are no on_event handlers registered - # (In newer FastAPI versions, on_event is deprecated) - print("✓ App configuration looks correct") - - print("\n✅ All checks passed! The application should start correctly.") - sys.exit(0) - -except Exception as e: - print(f"\n❌ Error importing or configuring app: {e}") - import traceback - traceback.print_exc() - sys.exit(1)