init_db.py#

#!/usr/bin/env python3
"""
Initialize the database schema by creating all ORM tables.

This module provides a thin, dependency-free CLI wrapper around
``app.database.ensure_database_schema`` so that schema creation can be invoked
from containers, CI jobs, or local development shells. It does not perform any
application bootstrapping beyond configuring logging and calling the database
utility.

See Also
--------
app.database.ensure_database_schema : Create DB tables via the shared helper.
app.config.settings : Runtime configuration values.

Notes
-----
- Primary role: minimal CLI to delegate schema creation to
  ``app.database.ensure_database_schema``.
- Key dependencies: a valid ``app.config.settings.DATABASE_URL`` and a
  reachable database service.
- Invariants: the operation is idempotent and safe to re-run; if all tables
  already exist, no changes are made.

Examples
--------
>>> # Programmatic usage                                   # doctest: +SKIP
>>> from app.init_db import initialize_database_schema
>>> initialize_database_schema()                            # doctest: +SKIP
"""


import argparse
import logging

from sqlalchemy.exc import SQLAlchemyError

from app.database import ensure_database_schema

# Constants
SCRIPT_DESCRIPTION: str = "Initialize the database schema for the ML Weather project."
LOG_LEVEL_CHOICES: list[str] = ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
DEFAULT_LOG_LEVEL: str = "INFO"

# Module-level logger
logger = logging.getLogger(__name__)


def initialize_database_schema() -> None:
    """Create all ORM tables in the configured database.

    Delegates to :func:`app.database.ensure_database_schema` and logs the
    outcome. This function is idempotent and may be called multiple times
    without side effects if the schema already exists.

    Raises
    ------
    SQLAlchemyError
        If schema initialization fails (e.g., connectivity or permissions).

    See Also
    --------
    app.database.ensure_database_schema : Create DB tables via the shared helper.
    """
    ensure_database_schema()
    logger.info("Database schema initialized.")


def parse_args() -> argparse.Namespace:
    """Parse command-line arguments.

    Returns
    -------
    argparse.Namespace
        Parsed arguments, including ``log-level``.

    Notes
    -----
    The ``--log-level`` argument is constrained to
    :data:`LOG_LEVEL_CHOICES` to prevent invalid values.
    """
    parser = argparse.ArgumentParser(description=SCRIPT_DESCRIPTION)
    parser.add_argument(
        "--log-level",
        type=str,
        choices=LOG_LEVEL_CHOICES,
        default=DEFAULT_LOG_LEVEL,
        help=f"Set the logging level (default: {DEFAULT_LOG_LEVEL}).",
    )
    return parser.parse_args()


def main() -> None:
    """CLI entry point to initialize the database schema.

    Parses arguments, configures logging, and calls
    :func:`initialize_database_schema`.

    Examples
    --------
    >>> # From a shell (inside the environment)          # doctest: +SKIP
    >>> python -m app.init_db --log-level DEBUG          # doctest: +SKIP

    Raises
    ------
    SQLAlchemyError
        Propagated from :func:`initialize_database_schema` when schema
        initialization fails.
    """
    args = parse_args()
    logging.basicConfig(level=getattr(logging, args.log_level))
    try:
        initialize_database_schema()
    except SQLAlchemyError as error:
        logger.error("Failed to initialize database schema.", exc_info=True)
        raise error


if __name__ == "__main__":
    main()