Coverage for mcpgateway/bootstrap_db.py: 83%

1# -*- coding: utf-8 -*-

2"""Database bootstrap/upgrade entry-point for MCP Gateway.

3

4 Copyright 2025

5 SPDX-License-Identifier: Apache-2.0

6 Authors: Madhav Kandukuri

7

8 The script:

9

10 1. Creates a synchronous SQLAlchemy ``Engine`` from ``settings.database_url``.

11 2. Looks for an *alembic.ini* two levels up from this file to drive migrations.

12 3. If the database is still empty (no ``gateways`` table), it:

13 - builds the base schema with ``Base.metadata.create_all()``

14 - stamps the migration head so Alembic knows it is up-to-date

15 4. Otherwise, it applies any outstanding Alembic revisions.

16 5. Logs a **"Database ready"** message on success.

17

18 It is intended to be invoked via ``python -m mcpgateway.bootstrap_db`` or

19 directly with ``python mcpgateway/bootstrap_db.py``.

20"""

21

22# Standard

23 import asyncio

24 from importlib.resources import files

25 import logging

26

27# Third-Party

28 from alembic.config import Config

29 from sqlalchemy import create_engine, inspect

30

31# First-Party

32 from alembic import command

33 from mcpgateway.config import settings

34 from mcpgateway.db import Base

35

36 logger = logging.getLogger(__name__)

37

38

39 async def main() -> None:

40 """

41 Bootstrap or upgrade the database schema, then log readiness.

42

43 Runs `create_all()` + `alembic stamp head` on an empty DB, otherwise just

44 executes `alembic upgrade head`, leaving application data intact.

45

46 Args:

47 None

48 """

49 engine = create_engine(settings.database_url)

50 ini_path = files("mcpgateway").joinpath("alembic.ini")

51 cfg = Config(str(ini_path)) # path in container

52 cfg.attributes["configure_logger"] = False

53

54 command.ensure_version(cfg)

55

56 insp = inspect(engine)

57 if "gateways" not in insp.get_table_names(): 57 ↛ 58line 57 didn't jump to line 58 because the condition on line 57 was never true

58 logger.info("Empty DB detected - creating baseline schema")

59 Base.metadata.create_all(engine)

60 command.stamp(cfg, "head") # record baseline

61 else:

62 command.upgrade(cfg, "head") # apply any new revisions

63 logger.info("Database ready")

64

65

66 if __name__ == "__main__":

67 asyncio.run(main())