Source code for configvars

import dataclasses
import importlib
import logging
import os
import typing

from django.core.exceptions import ImproperlyConfigured

__all__ = [
    "initialize",
    "config",
    "as_bool",
    "as_list",
    "secret",
    "get_config_variables",
]

DEFAULT_LOCAL_SETTINGS_MODULE_NAME = "local"
default_app_config = "configvars.apps.ConfigVarsAppConfig"


log = logging.getLogger("configvars")
_MISSING = object()
MASKED_SECRET_VALUE = "*****"
MAX_SECRET_FILE_SIZE = 64 * 1024


@dataclasses.dataclass
class ConfigVariable:
    name: str
    value: typing.Any = None
    desc: str = ""
    default: typing.Any = None
    secret: bool = False


class Config:
    def __init__(self):
        self._reset_state()

    def _reset_state(self):
        self._local_settings_module = None
        self._env_prefix = None
        self._all_configvars = {}
        self._local = None
        self._import_module_failed = False
        self._initialized = False

    @property
    def ENV_PREFIX(self):
        return self._env_prefix or ""

    def initialize(self, local_settings_module=None, env_prefix=None):
        self._initialized = False
        self._import_module_failed = False
        self._all_configvars = {}
        self._local = None

        self._env_prefix = env_prefix

        if not local_settings_module:
            settings_module = os.getenv("DJANGO_SETTINGS_MODULE")
            if not settings_module:
                raise ImproperlyConfigured(
                    "DJANGO_SETTINGS_MODULE environment variable is not set."
                )
            base_path = settings_module.split(".")[:-1]
            base_path.append(DEFAULT_LOCAL_SETTINGS_MODULE_NAME)
            self._local_settings_module = ".".join(base_path)
        else:
            self._local_settings_module = local_settings_module

        try:
            self._local = importlib.import_module(self._local_settings_module)
        except AttributeError as exc:
            raise ImproperlyConfigured(
                "Ensure that `local_settings_module` argument of `initialize()` "
                "is a string containing a dotted module path."
            ) from exc
        except ImportError as exc:
            if local_settings_module:  # if provided explicite
                raise ImproperlyConfigured(
                    f"Can't import local settings module " f"{local_settings_module}"
                ) from exc
            else:
                self._local = object()
                self._import_module_failed = self._local_settings_module
                self._initialized = True
        else:
            # backward compatibility
            self._initialized = True

    def local(self, key, default=None):
        if not self._initialized:
            self.initialize()
        return getattr(self._local, key, default)

    def env(self, key, default=None):
        if not self._initialized:
            self.initialize()
        return os.getenv(f"{self.ENV_PREFIX}{key}", default)

    def config(self, key, default=None, desc=None):
        if not self._initialized:
            self.initialize()
        value = self.env(key, self.local(key, default))
        self._all_configvars[key] = ConfigVariable(
            name=key, desc=desc, value=value, default=default
        )
        return value

    def secret(
        self, key=None, default=None, desc=None, file_var=None, allow_multiline=False
    ):
        if not self._initialized:
            self.initialize()

        if key is None and file_var is None:
            raise ImproperlyConfigured("Provide `key` or `file_var` to `secret()`.")

        secret_name = key or file_var
        value = _MISSING
        file_value = _MISSING

        if key is not None:
            value = self.env(key, self.local(key, _MISSING))
        if file_var is not None:
            file_value = self.env(file_var, self.local(file_var, _MISSING))

        if value is not _MISSING and file_value is not _MISSING:
            raise ImproperlyConfigured(
                f"Set only one of `{key}` or `{file_var}` for secret `{secret_name}`."
            )

        resolved_value = default
        if file_value is not _MISSING:
            if not file_value:
                resolved_value = file_value
            else:
                if not os.path.isfile(file_value):
                    raise ImproperlyConfigured(
                        f"Secret file for `{secret_name}` does not exist: {file_value}"
                    )
                if os.path.getsize(file_value) > MAX_SECRET_FILE_SIZE:
                    raise ImproperlyConfigured(
                        f"Secret file for `{secret_name}` is too large: {file_value}"
                    )
                with open(file_value) as f:
                    resolved_value = f.read()
                if not allow_multiline and any(
                    char in resolved_value for char in ("\n", "\r")
                ):
                    raise ImproperlyConfigured(
                        f"Secret file for `{secret_name}` must be single-line."
                    )
        elif value is not _MISSING:
            resolved_value = value

        registry_value = resolved_value
        if registry_value not in (None, ""):
            registry_value = MASKED_SECRET_VALUE

        self._all_configvars[secret_name] = ConfigVariable(
            name=secret_name,
            desc=desc,
            default=default,
            value=registry_value,
            secret=True,
        )

        return resolved_value

    def config_variables(self):
        return self._all_configvars.values()


[docs] def as_list(value, separator=","): if value: if isinstance(value, (list, tuple)): return value else: return value.split(separator) else: return []
[docs] def as_bool(value): if isinstance(value, bool): return value try: return bool(int(value)) except (TypeError, ValueError): value_str = str(value) if value_str.lower() in ("false", "off", "disable"): return False else: return True
default_config = Config()
[docs] def initialize(local_settings_module=None, env_prefix=None): return default_config.initialize( local_settings_module=local_settings_module, env_prefix=env_prefix )
[docs] def config(var, default=None, desc=None): return default_config.config(key=var, default=default, desc=desc)
[docs] def secret(var=None, default=None, desc=None, file_var=None, allow_multiline=False): return default_config.secret( key=var, default=default, desc=desc, file_var=file_var, allow_multiline=allow_multiline, )
[docs] def get_config_variables(): return default_config.config_variables()