Source code for magpie.constants

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Constant settings for Magpie application.

Constants defined with format ``MAGPIE_[VARIABLE_NAME]`` can be matched with corresponding
settings formatted as ``magpie.[variable_name]`` in the ``magpie.ini`` configuration file.

.. note::
    Since the ``magpie.ini`` file has to be loaded by the application to retrieve various configuration settings,
    constant ``MAGPIE_INI_FILE_PATH`` (or any other `path variable` defined before it - see below) has to be defined
    by environment variable if the default location is not desired (ie: if you want to provide your own configuration).
"""
import logging
import os
import re
import shutil
import warnings
from typing import TYPE_CHECKING

import dotenv
from pyramid.settings import asbool

if TYPE_CHECKING:
    # pylint: disable=W0611,unused-import
    from typing import Optional

    from magpie.typedefs import AnySettingsContainer, SettingValue, Str

# ===========================
# path variables
# ===========================
[docs] MAGPIE_MODULE_DIR = os.path.abspath(os.path.dirname(__file__))
[docs] MAGPIE_ROOT = os.path.dirname(MAGPIE_MODULE_DIR)
[docs] MAGPIE_CONFIG_DIR = os.getenv("MAGPIE_CONFIG_DIR") or os.path.join(MAGPIE_ROOT, "config") # default also if empty
[docs] MAGPIE_PROVIDERS_CONFIG_PATH = os.getenv( "MAGPIE_PROVIDERS_CONFIG_PATH", "{}/providers.cfg".format(MAGPIE_CONFIG_DIR))
[docs] MAGPIE_PROVIDERS_HOOKS_PATH = os.getenv("MAGPIE_PROVIDERS_HOOKS_PATH", MAGPIE_ROOT)
[docs] MAGPIE_PERMISSIONS_CONFIG_PATH = os.getenv( "MAGPIE_PERMISSIONS_CONFIG_PATH", "{}/permissions.cfg".format(MAGPIE_CONFIG_DIR))
[docs] MAGPIE_WEBHOOKS_CONFIG_PATH = os.getenv("MAGPIE_WEBHOOKS_CONFIG_PATH")
[docs] MAGPIE_CONFIG_PATH = os.getenv("MAGPIE_CONFIG_PATH") # default None, require explicit specification
[docs] MAGPIE_INI_FILE_PATH = os.getenv( "MAGPIE_INI_FILE_PATH", "{}/magpie.ini".format(MAGPIE_CONFIG_DIR))
# allow custom location of env files directory to avoid # loading from installed magpie in python site-packages
[docs] MAGPIE_ENV_DIR = os.getenv("MAGPIE_ENV_DIR", os.path.join(MAGPIE_ROOT, "env"))
[docs] MAGPIE_ENV_FILE = os.path.join(MAGPIE_ENV_DIR, "magpie.env")
[docs] MAGPIE_POSTGRES_ENV_FILE = os.path.join(MAGPIE_ENV_DIR, "postgres.env")
# create .env from .env.example if not present and load variables into environment # if files still cannot be found at 'MAGPIE_ENV_DIR' and variables are still not set, # default values in following sections will be used instead
[docs] _MAGPIE_ENV_EXAMPLE = MAGPIE_ENV_FILE + ".example"
[docs] _POSTGRES_ENV_EXAMPLE = MAGPIE_POSTGRES_ENV_FILE + ".example"
if not os.path.isfile(MAGPIE_ENV_FILE) and os.path.isfile(_MAGPIE_ENV_EXAMPLE): shutil.copyfile(_MAGPIE_ENV_EXAMPLE, MAGPIE_ENV_FILE) if not os.path.isfile(MAGPIE_POSTGRES_ENV_FILE) and os.path.isfile(_POSTGRES_ENV_EXAMPLE): shutil.copyfile(_POSTGRES_ENV_EXAMPLE, MAGPIE_POSTGRES_ENV_FILE) del _MAGPIE_ENV_EXAMPLE del _POSTGRES_ENV_EXAMPLE try: # if variables already exist, don't override them from defaults in env files dotenv.load_dotenv(MAGPIE_ENV_FILE, override=False) dotenv.load_dotenv(MAGPIE_POSTGRES_ENV_FILE, override=False) except IOError: warnings.warn("Failed to open environment files [MAGPIE_ENV_DIR={}].".format(MAGPIE_ENV_DIR), RuntimeWarning)
[docs] def _get_default_log_level(): # type: () -> Str """ Get logging level from INI configuration file or fallback to default ``INFO`` if it cannot be retrieved. """ _default_log_lvl = "INFO" try: import magpie.utils # pylint: disable=C0415 # avoid circular import error _settings = magpie.utils.get_settings_from_config_ini(MAGPIE_INI_FILE_PATH, ini_main_section_name="logger_magpie") _default_log_lvl = _settings.get("level", _default_log_lvl) # also considers 'ModuleNotFoundError' derived from 'ImportError', but not added to avoid Python <3.6 name error except (AttributeError, ImportError): # noqa: W0703 # nosec: B110 pass return _default_log_lvl
# =========================== # variables from magpie.env # ===========================
[docs] MAGPIE_URL = os.getenv("MAGPIE_URL", None)
[docs] MAGPIE_SECRET = os.getenv("MAGPIE_SECRET", "")
[docs] MAGPIE_PASSWORD_MIN_LENGTH = os.getenv("MAGPIE_PASSWORD_MIN_LENGTH", 12)
[docs] MAGPIE_ADMIN_USER = os.getenv("MAGPIE_ADMIN_USER", "")
[docs] MAGPIE_ADMIN_PASSWORD = os.getenv("MAGPIE_ADMIN_PASSWORD", "")
[docs] MAGPIE_ADMIN_EMAIL = "{}@mail.com".format(MAGPIE_ADMIN_USER)
[docs] MAGPIE_ADMIN_GROUP = os.getenv("MAGPIE_ADMIN_GROUP", "administrators")
[docs] MAGPIE_ANONYMOUS_USER = os.getenv("MAGPIE_ANONYMOUS_USER", "anonymous")
[docs] MAGPIE_ANONYMOUS_PASSWORD = MAGPIE_ANONYMOUS_USER
[docs] MAGPIE_ANONYMOUS_EMAIL = "{}@mail.com".format(MAGPIE_ANONYMOUS_USER)
[docs] MAGPIE_ANONYMOUS_GROUP = MAGPIE_ANONYMOUS_USER # left for backward compatibility of migration scripts
[docs] MAGPIE_EDITOR_GROUP = os.getenv("MAGPIE_EDITOR_GROUP", "editors")
[docs] MAGPIE_USERS_GROUP = os.getenv("MAGPIE_USERS_GROUP", "users")
[docs] MAGPIE_CRON_LOG = os.getenv("MAGPIE_CRON_LOG", "~/magpie-cron.log")
[docs] MAGPIE_DB_MIGRATION = asbool(os.getenv("MAGPIE_DB_MIGRATION", True)) # run db migration on startup
[docs] MAGPIE_DB_MIGRATION_ATTEMPTS = int(os.getenv("MAGPIE_DB_MIGRATION_ATTEMPTS", 5))
[docs] MAGPIE_LOG_LEVEL = os.getenv("MAGPIE_LOG_LEVEL", _get_default_log_level()) # log level to apply to the loggers
[docs] MAGPIE_LOG_PRINT = asbool(os.getenv("MAGPIE_LOG_PRINT", False)) # log also forces print to the console
[docs] MAGPIE_LOG_REQUEST = asbool(os.getenv("MAGPIE_LOG_REQUEST", True)) # log detail of every incoming request
[docs] MAGPIE_LOG_EXCEPTION = asbool(os.getenv("MAGPIE_LOG_EXCEPTION", True)) # log detail of generated exceptions
[docs] MAGPIE_UI_ENABLED = asbool(os.getenv("MAGPIE_UI_ENABLED", True))
[docs] MAGPIE_UI_THEME = os.getenv("MAGPIE_UI_THEME", "blue")
[docs] PHOENIX_USER = os.getenv("PHOENIX_USER", "phoenix")
[docs] PHOENIX_PASSWORD = os.getenv("PHOENIX_PASSWORD", "qwerty")
[docs] PHOENIX_HOST = os.getenv("PHOENIX_HOST") # default None to use HOSTNAME
[docs] PHOENIX_PORT = int(os.getenv("PHOENIX_PORT", 8443))
[docs] PHOENIX_PUSH = asbool(os.getenv("PHOENIX_PUSH", False))
[docs] TWITCHER_PROTECTED_PATH = os.getenv("TWITCHER_PROTECTED_PATH", "/ows/proxy")
[docs] TWITCHER_PROTECTED_URL = os.getenv("TWITCHER_PROTECTED_URL", None)
[docs] TWITCHER_HOST = os.getenv("TWITCHER_HOST", None)
# external identify connectors, define variables only to avoid unnecessary print-log warnings in each CLI call
[docs] GITHUB_CLIENT_ID = os.getenv("GITHUB_CLIENT_ID", None)
[docs] GITHUB_CLIENT_SECRET = os.getenv("GITHUB_CLIENT_SECRET", None)
[docs] WSO2_HOSTNAME = os.getenv("WSO2_HOSTNAME", None)
[docs] WSO2_CLIENT_ID = os.getenv("WSO2_CLIENT_ID", None)
[docs] WSO2_CLIENT_SECRET = os.getenv("WSO2_CLIENT_SECRET", None)
[docs] WSO2_CERTIFICATE_FILE = os.getenv("WSO2_CERTIFICATE_FILE", None)
[docs] WSO2_SSL_VERIFY = os.getenv("WSO2_SSL_VERIFY", None)
# =========================== # variables from postgres.env # ===========================
[docs] MAGPIE_POSTGRES_USERNAME = os.getenv("MAGPIE_POSTGRES_USERNAME", "magpie")
[docs] MAGPIE_POSTGRES_PASSWORD = os.getenv("MAGPIE_POSTGRES_PASSWORD", "qwerty")
[docs] MAGPIE_POSTGRES_HOST = os.getenv("MAGPIE_POSTGRES_HOST", "postgres")
[docs] MAGPIE_POSTGRES_PORT = int(os.getenv("MAGPIE_POSTGRES_PORT", 5432))
[docs] MAGPIE_POSTGRES_DB = os.getenv("MAGPIE_POSTGRES_DB", "magpie")
# =========================== # constants # ===========================
[docs] MAGPIE_ADMIN_PERMISSION = "admin" # user must be administrator to access a view (default permission, always allowed)
[docs] MAGPIE_LOGGED_PERMISSION = "MAGPIE_LOGGED_USER" # user must be itself (either literally or inferred MAGPIE_LOGGED_USER)
[docs] MAGPIE_CONTEXT_PERMISSION = "MAGPIE_CONTEXT_USER" # path user must be itself, MAGPIE_LOGGED_USER or unauthenticated
[docs] MAGPIE_LOGGED_USER = "current"
[docs] MAGPIE_DEFAULT_PROVIDER = "ziggurat"
# above this length is considered a token, # refuse longer username creation
[docs] MAGPIE_USER_NAME_MAX_LENGTH = 64
[docs] MAGPIE_GROUP_NAME_MAX_LENGTH = 64
# ignore matches of settings and environment variables for following cases
[docs] MAGPIE_CONSTANTS = [ "MAGPIE_CONSTANTS", "MAGPIE_ADMIN_PERMISSION", "MAGPIE_LOGGED_PERMISSION", "MAGPIE_CONTEXT_PERMISSION", "MAGPIE_LOGGED_USER", "MAGPIE_DEFAULT_PROVIDER", "MAGPIE_USER_NAME_MAX_LENGTH", "MAGPIE_GROUP_NAME_MAX_LENGTH", ]
# =========================== # utilities # ===========================
[docs] _REGEX_ASCII_ONLY = re.compile(r"\W|^(?=\d)")
[docs] def get_constant_setting_name(name): # type: (Str) -> Str """ Find the equivalent setting name of the provided environment variable name. Lower-case name and replace all non-ascii chars by `_`. Then, convert known prefixes with their dotted name. """ name = re.sub(_REGEX_ASCII_ONLY, "_", name.strip().lower()) for prefix in ["magpie", "twitcher", "postgres", "phoenix"]: known_prefix = "{}_".format(prefix) dotted_prefix = "{}.".format(prefix) if name.startswith(known_prefix): return name.replace(known_prefix, dotted_prefix, 1) return name
[docs] def get_constant(constant_name, # type: Str settings_container=None, # type: Optional[AnySettingsContainer] settings_name=None, # type: Optional[Str] default_value=None, # type: Optional[SettingValue] raise_not_set=True, # type: bool raise_missing=True, # type: bool print_missing=False, # type: bool empty_missing=False, # type: bool ): # type: (...) -> SettingValue """ Search in order for matched value of :paramref:`constant_name`: 1. search in :py:data:`MAGPIE_CONSTANTS` 2. search in settings if specified 3. search alternative setting names (see below) 4. search in :mod:`magpie.constants` definitions 5. search in environment variables Parameter :paramref:`constant_name` is expected to have the format ``MAGPIE_[VARIABLE_NAME]`` although any value can be passed to retrieve generic settings from all above-mentioned search locations. If :paramref:`settings_name` is provided as alternative name, it is used as is to search for results if :paramref:`constant_name` was not found. Otherwise, ``magpie.[variable_name]`` is used for additional search when the format ``MAGPIE_[VARIABLE_NAME]`` was used for :paramref:`constant_name` (i.e.: ``MAGPIE_ADMIN_USER`` will also search for ``magpie.admin_user`` and so on for corresponding constants). :param constant_name: key to search for a value :param settings_container: WSGI application settings container (if not provided, uses found one in current thread) :param settings_name: alternative name for `settings` if specified :param default_value: default value to be returned if not found anywhere, and exception raises are disabled. :param raise_not_set: raise an exception if the found key is ``None``, search until last case if others are ``None`` :param raise_missing: raise exception if key is not found anywhere :param print_missing: print message if key is not found anywhere, return ``None`` :param empty_missing: consider an empty value for an existing key as if it was missing (i.e.: as if not set). :returns: found value or `default_value` :raises ValueError: if resulting value is invalid based on options (by default raise missing/empty/``None`` value) :raises LookupError: if no appropriate value could be found from all search locations (according to options) """ from magpie.utils import get_settings, print_log, raise_log # pylint: disable=C0415 # avoid circular import error if constant_name in MAGPIE_CONSTANTS: return globals()[constant_name] missing = True magpie_value = None settings = get_settings(settings_container, app=True) if settings and constant_name in settings: # pylint: disable=E1135 missing = False magpie_value = settings.get(constant_name) if magpie_value is not None: if not empty_missing or magpie_value != "": print_log("Config found in settings with: {}".format(constant_name), level=logging.DEBUG) return magpie_value print_log("Constant ignored from settings (empty): {}".format(constant_name), level=logging.DEBUG) if not settings_name: settings_name = get_constant_setting_name(constant_name) print_log("Constant alternate search: {}".format(settings_name), level=logging.DEBUG) if settings and settings_name and settings_name in settings: # pylint: disable=E1135 missing = False magpie_value = settings.get(settings_name) if magpie_value is not None: if not empty_missing or magpie_value != "": print_log("Constant found in settings with: {}".format(settings_name), level=logging.DEBUG) return magpie_value print_log("Constant ignored from settings (empty): {}".format(settings_name), level=logging.DEBUG) magpie_globals = globals() if constant_name in magpie_globals: missing = False magpie_value = magpie_globals.get(constant_name) if magpie_value is not None: if not empty_missing or magpie_value != "": print_log("Constant found in definitions with: {}".format(constant_name), level=logging.DEBUG) return magpie_value print_log("Constant ignored from definition (empty): {}".format(constant_name), level=logging.DEBUG) if constant_name in os.environ: missing = False magpie_value = os.environ.get(constant_name) if magpie_value is not None: if not empty_missing or magpie_value != "": print_log("Constant found in environment with: {}".format(constant_name), level=logging.DEBUG) return magpie_value print_log("Constant ignored from environment (empty): {}".format(constant_name), level=logging.DEBUG) if not missing and raise_not_set: raise_log("Constant was found but was not set: {}".format(constant_name), level=logging.ERROR, exception=ValueError) if missing and raise_missing: raise_log("Constant could not be found: {}".format(constant_name), level=logging.ERROR, exception=LookupError) if missing and print_missing: print_log("Constant could not be found: {} (using default: {})" .format(constant_name, default_value), level=logging.WARN) return magpie_value or default_value