Skip to content

[Bug]: Database migration runs during doctest executionΒ #661

@manavgup

Description

@manavgup

🐞 Bug Summary

The make doctest command fails with a database migration error because module-level code in main.py executes database operations during pytest module import, even when only scanning for doctests.


🧩 Affected Component

Select the area of the project impacted:

  • mcpgateway - API
  • mcpgateway - UI (admin panel)
  • mcpgateway.wrapper - stdio wrapper
  • Federation or Transports
  • CLI, Makefiles, or shell scripts
  • Container setup (Docker/Podman/Compose)
  • Other (explain below)

πŸ” Steps to Reproduce

  1. Clone the repository
  2. Set up the environment with make venv and make install-dev
  3. Run make doctest

πŸ€” Expected Behavior

Doctests should run without triggering application startup or database operations
Module imports during doctest scanning should not have side effects


πŸ““ Logs / Error Output

============================================================= ERRORS =============================================================
____________________________________________ ERROR collecting mcpgateway/main.py _____________________________________________
mcpgateway/main.py:155: in <module>
    loop = asyncio.get_running_loop()
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
E   RuntimeError: no running event loop

During handling of the above exception, another exception occurred:
...
E   sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) duplicate column name: slug
E   [SQL: ALTER TABLE gateways ADD COLUMN slug VARCHAR]

Root Cause

Module-level code in mcpgateway/main.py (lines ~150-160) runs database initialization during import:

# This executes when the module is imported, not just when app starts
try:
    loop = asyncio.get_running_loop()
except RuntimeError:
    asyncio.run(bootstrap_db())  # ← Runs migrations during import!

Proposed Solutions

Option 1 - Only run when script is executed directly, not imported

    wait_for_db_ready(...)
    try:
        loop = asyncio.get_running_loop()
    except RuntimeError:
        asyncio.run(bootstrap_db())

Option 2: Move to FastAPI lifespan

@asynccontextmanager
async def lifespan(_app: FastAPI) -> AsyncIterator[None]:
    logger.info("Starting MCP Gateway services")
    try:
        # Move database initialization here
        wait_for_db_ready(...)
        await bootstrap_db()
        
        await tool_service.initialize()
        # ... rest of existing initialization

Option 3: Doctest exclusion (Temporary workaround)

doctest:
    python3 -m pytest --doctest-modules mcpgateway/ --ignore=mcpgateway/main.py --tb=short

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingtriageIssues / Features awaiting triage

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions