Source code for flask_ligand.default_settings

"""Default Flask application settings"""

# ======================================================================================================================
# Imports
# ======================================================================================================================
from __future__ import annotations
import os
from typing import TYPE_CHECKING


# ======================================================================================================================
# Type Checking
# ======================================================================================================================
if TYPE_CHECKING:  # pragma: no cover
    from typing import Any
    from flask import Flask


# ======================================================================================================================
# Classes: Private
# ======================================================================================================================
class _DefaultConfig(dict):  # type: ignore
    """
    The default Flask settings for all environments.

    Args:
        api_title: The title (name) of the API to display in the OpenAPI documentation.
        api_version: The semantic version for the OpenAPI client.
        openapi_client_name: The package name to use for generated OpenAPI clients.
        kwargs: Additional settings to add to the configuration.

    Raises:
        RuntimeError: Attempted to override a protected setting or specified an additional setting that was not all
            uppercase.
    """

    def __init__(self, api_title: str, api_version: str, openapi_client_name: str, **kwargs: dict[str, Any]):
        # Settings are specific to this library
        ligand_default_settings: dict[str, Any] = {
            "SERVICE_PUBLIC_URL": os.getenv("SERVICE_PUBLIC_URL"),
            "SERVICE_PRIVATE_URL": os.getenv("SERVICE_PRIVATE_URL"),
            "ALLOWED_ROLES": os.getenv("ALLOWED_ROLES").split(",")  # type: ignore
            if os.getenv("ALLOWED_ROLES") is not None
            else None,
        }

        db_default_settings: dict[str, Any] = {
            "SQLALCHEMY_DATABASE_URI": os.getenv("SQLALCHEMY_DATABASE_URI"),
            "SQLALCHEMY_TRACK_MODIFICATIONS": False,
            "DB_AUTO_UPGRADE": False,
            "DB_MIGRATION_DIR": "migrations",
            "JSON_SORT_KEYS": False,
        }

        auth_default_settings: dict[str, Any] = {
            "OIDC_DISCOVERY_URL": os.getenv("OIDC_DISCOVERY_URL"),
            "VERIFY_SSL_CERT": True,
            "JWT_TOKEN_LOCATION": "headers",
            "JWT_HEADER_NAME": "Authorization",
            "JWT_HEADER_TYPE": "Bearer",
            "JWT_ERROR_MESSAGE_KEY": "message",
            "JWT_PUBLIC_KEY": "",
        }

        open_api_default_settings: dict[str, Any] = {
            "OPENAPI_GEN_SERVER_URL": os.getenv("OPENAPI_GEN_SERVER_URL"),
            "OPENAPI_VERSION": os.getenv("OPENAPI_VERSION", "3.0.3"),
            "OPENAPI_URL_PREFIX": "/",
            "OPENAPI_JSON_PATH": "/openapi/api-spec.json",
            "OPENAPI_SWAGGER_UI_PATH": os.getenv("OPENAPI_SWAGGER_UI_PATH", "/apidocs"),
            "OPENAPI_SWAGGER_UI_URL": "https://cdn.jsdelivr.net/npm/swagger-ui-dist/",
            "API_SPEC_OPTIONS": {"servers": [{"url": os.getenv("SERVICE_PUBLIC_URL"), "description": "Public URL"}]},
        }

        super().__init__(
            {
                "API_TITLE": api_title,
                "API_VERSION": api_version,
                "OPENAPI_CLIENT_NAME": openapi_client_name,
                **ligand_default_settings,
                **db_default_settings,
                **auth_default_settings,
                **open_api_default_settings,
            }
        )

        for key in kwargs:
            if key.isupper():
                if key not in ["API_TITLE", "API_VERSION", "OPENAPI_CLIENT_NAME"]:
                    self[key] = kwargs[key]
                else:
                    raise RuntimeError(f"The '{key}' setting is not allowed to be overridden!")
            else:
                raise RuntimeError(f"The setting name '{key}' must be uppercase!")

        required_settings: dict[str, Any] = {
            "SERVICE_PUBLIC_URL": self["SERVICE_PUBLIC_URL"],
            "SERVICE_PRIVATE_URL": self["SERVICE_PRIVATE_URL"],
            "ALLOWED_ROLES": self["ALLOWED_ROLES"],
            "OIDC_DISCOVERY_URL": self["OIDC_DISCOVERY_URL"],
            "SQLALCHEMY_DATABASE_URI": self["SQLALCHEMY_DATABASE_URI"],
            "OPENAPI_GEN_SERVER_URL": self["OPENAPI_GEN_SERVER_URL"],
        }

        for key in required_settings:
            if required_settings[key] is None:
                raise RuntimeError(
                    f"The '{key}' environment variable must be set when running in this Flask environment!"
                )


# ======================================================================================================================
# Classes: Public
# ======================================================================================================================
[docs]class ProdConfig(_DefaultConfig): """ Configuration for production environments. Args: api_title: The title (name) of the API to display in the OpenAPI documentation. api_version: The semantic version for the OpenAPI client. openapi_client_name: The package name to use for generated OpenAPI clients. kwargs: Additional settings to add to the configuration. Raises: RuntimeError: Attempted to override a protected setting or specified an additional setting that was not all uppercase. """ def __init__(self, api_title: str, api_version: str, openapi_client_name: str, **kwargs: dict[str, Any]): prod_settings: dict[str, Any] = { "JWT_ALGORITHM": "RS256", "JWT_DECODE_AUDIENCE": os.getenv("JWT_DECODE_AUDIENCE"), } combined_settings = {**prod_settings, **kwargs} super().__init__(api_title, api_version, openapi_client_name, **combined_settings)
[docs]class StagingConfig(ProdConfig): """ Configuration for development/staging environments. Args: api_title: The title (name) of the API to display in the OpenAPI documentation. api_version: The semantic version for the OpenAPI client. openapi_client_name: The package name to use for generated OpenAPI clients. kwargs: Additional settings to add to the configuration. Raises: RuntimeError: Attempted to override a protected setting or specified an additional setting that was not all uppercase. """ def __init__(self, api_title: str, api_version: str, openapi_client_name: str, **kwargs: dict[str, Any]): dev_settings: dict[str, Any] = {"VERIFY_SSL_CERT": False} combined_settings = {**dev_settings, **kwargs} super().__init__(f"DEV {api_title}", api_version, openapi_client_name, **combined_settings)
[docs]class FlaskLocalConfig(StagingConfig): """ Configuration used for running a local Flask server. Args: api_title: The title (name) of the API to display in the OpenAPI documentation. api_version: The semantic version for the OpenAPI client. openapi_client_name: The package name to use for generated OpenAPI clients. kwargs: Additional settings to add to the configuration. Raises: RuntimeError: Attempted to override a protected setting or specified an additional setting that was not all uppercase. """ def __init__(self, api_title: str, api_version: str, openapi_client_name: str, **kwargs: dict[str, Any]): # noinspection HttpUrlsUsage flask_local_settings: dict[str, Any] = { "SERVICE_PUBLIC_URL": os.getenv("SERVICE_PUBLIC_URL", "http://localhost:5000"), "SERVICE_PRIVATE_URL": os.getenv("SERVICE_PRIVATE_URL", "http://localhost:5000"), "ALLOWED_ROLES": os.getenv("ALLOWED_ROLES", "user,admin").split(","), "SQLALCHEMY_DATABASE_URI": os.getenv("SQLALCHEMY_DATABASE_URI", "sqlite:///:memory:"), "OPENAPI_GEN_SERVER_URL": os.getenv("OPENAPI_GEN_SERVER_URL", "http://api.openapi-generator.tech"), "API_SPEC_OPTIONS": { "servers": [ {"url": os.getenv("SERVICE_PUBLIC_URL", "http://localhost:5000"), "description": "Public URL"} ] }, } combined_settings = {**flask_local_settings, **kwargs} super().__init__(f"FLASK LOCAL {api_title}", api_version, openapi_client_name, **combined_settings)
[docs]class TestingConfig(_DefaultConfig): """ Configuration used for unit testing. Args: api_title: The title (name) of the API to display in the OpenAPI documentation. api_version: The semantic version for the OpenAPI client. openapi_client_name: The package name to use for generated OpenAPI clients. kwargs: Additional settings to add to the configuration. Raises: RuntimeError: Attempted to override a protected setting or specified an additional setting that was not all uppercase. """ def __init__(self, api_title: str, api_version: str, openapi_client_name: str, **kwargs: dict[str, Any]): testing_settings: dict[str, Any] = { "SERVICE_PUBLIC_URL": os.getenv("SERVICE_PUBLIC_URL", "http://public.url"), "SERVICE_PRIVATE_URL": os.getenv("SERVICE_PRIVATE_URL", "http://private.url"), "ALLOWED_ROLES": os.getenv("ALLOWED_ROLES", "user,admin").split(","), "OIDC_DISCOVERY_URL": "TESTING", "VERIFY_SSL_CERT": False, "JWT_ACCESS_TOKEN_EXPIRES": 300, "JWT_SECRET_KEY": "super-duper-secret", "SQLALCHEMY_DATABASE_URI": "sqlite:///:memory:", "OPENAPI_GEN_SERVER_URL": "http://openapi.fake.address", "API_SPEC_OPTIONS": { "servers": [{"url": os.getenv("SERVICE_PUBLIC_URL", "http://public.url"), "description": "Public URL"}] }, } combined_settings = {**testing_settings, **kwargs} super().__init__(f"TESTING {api_title}", api_version, openapi_client_name, **combined_settings)
# ====================================================================================================================== # Globals: Needs to be after class declarations to work right. # ====================================================================================================================== ENVIRONMENTS = { "prod": ProdConfig, "stage": StagingConfig, "local": FlaskLocalConfig, "testing": TestingConfig, "cli": ProdConfig, } # ====================================================================================================================== # Functions: Public # ======================================================================================================================
[docs]def flask_environment_configurator( app: Flask, environment: str, api_title: str, api_version: str, openapi_client_name: str, **kwargs: Any, ) -> None: """Update a Flask app configuration for a given environment with optional setting overrides. Args: app: The root Flask app to configure with the given extension. environment: The target environment to create a Flask configuration object for. Available environments: ``prod``: Configured for use in a production environment. ``stage``: Configured for use in a development/staging environment. ``local``: Configured for use with a local Flask server. ``testing``: Configured for use in unit testing. ``cli``: Configured for use in a production environment without initializing extensions. (Use for CI/CD) api_title: The title (name) of the API to display in the OpenAPI documentation. api_version: The semantic version for the OpenAPI client. openapi_client_name: The package name to use for generated OpenAPI clients. kwargs: Additional settings to add to the configuration object or overrides for unprotected settings. Raises: RuntimeError: Attempted to override a protected setting, specified an additional setting that was not all uppercase or the specified environment is invalid. """ try: app.config.from_mapping(ENVIRONMENTS[environment](api_title, api_version, openapi_client_name, **kwargs)) except KeyError: raise RuntimeError(f"The specified '{environment}' environment is invalid!")