import functools
import os
import warnings
BANNER = """
██████╗ ██╗ ██╗███╗ ██╗ █████╗ ██████╗ ██████╗ ███╗ ██╗███████╗
██╔══██╗╚██╗ ██╔╝████╗ ██║██╔══██╗██╔════╝██╔═══██╗████╗ ██║██╔════╝
██║ ██║ ╚████╔╝ ██╔██╗ ██║███████║██║ ██║ ██║██╔██╗ ██║█████╗
██║ ██║ ╚██╔╝ ██║╚██╗██║██╔══██║██║ ██║ ██║██║╚██╗██║██╔══╝
██████╔╝ ██║ ██║ ╚████║██║ ██║╚██████╗╚██████╔╝██║ ╚████║██║
╚═════╝ ╚═╝ ╚═╝ ╚═══╝╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═══╝╚═╝
"""
if os.name == "nt": # pragma: no cover
# windows can't handle the above charmap
BANNER = "DYNACONF"
[docs]def object_merge(old, new, unique=False):
"""
Recursively merge two data structures.
:param unique: When set to True existing list items are not set.
"""
if old == new:
# Nothing to merge
return
if isinstance(old, list) and isinstance(new, list):
for item in old[::-1]:
if unique and item in new:
continue
new.insert(0, item)
if isinstance(old, dict) and isinstance(new, dict):
for key, value in old.items():
if key not in new:
new[key] = value
else:
object_merge(value, new[key])
# Cleanup of MetaValues on New dict
for key, value in list(new.items()):
if getattr(new[key], "dynaconf_reset", False):
# new Reset triggers cleanup of existing data
new[key] = new[key].value
elif getattr(new[key], "dynaconf_del", False):
# new Del triggers deletion of existing data
new.pop(key, None)
[docs]class DynaconfDict(dict):
"""A dict representing en empty Dynaconf object
useful to run loaders in to a dict for testing"""
def __init__(self, *args, **kwargs):
self._loaded_files = []
super(DynaconfDict, self).__init__(*args, **kwargs)
@property
def logger(self):
return raw_logger()
[docs] def set(self, key, value, *args, **kwargs):
self[key] = value
[docs] @staticmethod
def get_environ(key, default=None): # pragma: no cover
return os.environ.get(key, default)
[docs] def exists(self, key, **kwargs):
return self.get(key, missing) is not missing
@functools.lru_cache()
def _logger(level):
import logging
formatter = logging.Formatter(
fmt=(
"%(asctime)s,%(msecs)d %(levelname)-8s "
"[%(filename)s:%(lineno)d - %(funcName)s] %(message)s"
),
datefmt="%Y-%m-%d:%H:%M:%S",
)
handler = logging.StreamHandler()
handler.setFormatter(formatter)
logger = logging.getLogger("dynaconf")
logger.addHandler(handler)
logger.setLevel(level=getattr(logging, level, "DEBUG"))
return logger
[docs]def raw_logger(level=None):
"""Get or create inner logger"""
level = level or os.environ.get("DEBUG_LEVEL_FOR_DYNACONF", "ERROR")
return _logger(level)
RENAMED_VARS = {
# old: new
"DYNACONF_NAMESPACE": "ENV_FOR_DYNACONF",
"NAMESPACE_FOR_DYNACONF": "ENV_FOR_DYNACONF",
"DYNACONF_SETTINGS_MODULE": "SETTINGS_FILE_FOR_DYNACONF",
"DYNACONF_SETTINGS": "SETTINGS_FILE_FOR_DYNACONF",
"SETTINGS_MODULE": "SETTINGS_FILE_FOR_DYNACONF",
"SETTINGS_MODULE_FOR_DYNACONF": "SETTINGS_FILE_FOR_DYNACONF",
"PROJECT_ROOT": "ROOT_PATH_FOR_DYNACONF",
"PROJECT_ROOT_FOR_DYNACONF": "ROOT_PATH_FOR_DYNACONF",
"DYNACONF_SILENT_ERRORS": "SILENT_ERRORS_FOR_DYNACONF",
"DYNACONF_ALWAYS_FRESH_VARS": "FRESH_VARS_FOR_DYNACONF",
"BASE_NAMESPACE_FOR_DYNACONF": "DEFAULT_ENV_FOR_DYNACONF",
"GLOBAL_ENV_FOR_DYNACONF": "ENVVAR_PREFIX_FOR_DYNACONF",
}
[docs]def compat_kwargs(kwargs):
"""To keep backwards compat change the kwargs to new names"""
warn_deprecations(kwargs)
for old, new in RENAMED_VARS.items():
if old in kwargs:
kwargs[new] = kwargs[old]
# update cross references
for c_old, c_new in RENAMED_VARS.items():
if c_new == new:
kwargs[c_old] = kwargs[new]
[docs]class Missing(object):
"""
Sentinel value object/singleton used to differentiate between ambiguous
situations where `None` is a valid value.
"""
def __bool__(self):
"""Respond to boolean duck-typing."""
return False
def __eq__(self, other):
"""Equality check for a singleton."""
return isinstance(other, self.__class__)
# Ensure compatibility with Python 2.x
__nonzero__ = __bool__
def __repr__(self):
"""
Unambiguously identify this string-based representation of Missing,
used as a singleton.
"""
return "<dynaconf.missing>"
missing = Missing()
[docs]def deduplicate(list_object):
"""Rebuild `list_object` removing duplicated and keeping order"""
new = []
for item in list_object:
if item not in new:
new.append(item)
return new
[docs]def warn_deprecations(data):
for old, new in RENAMED_VARS.items():
if old in data:
warnings.warn(
"You are using %s which is a deprecated settings "
"replace it with %s" % (old, new),
DeprecationWarning,
)
[docs]def trimmed_split(s, seps=(";", ",")):
"""Given a string s, split is by one of one of the seps."""
for sep in seps:
if sep not in s:
continue
data = [item.strip() for item in s.strip().split(sep)]
return data
return [s] # raw un-splitted
[docs]def ensure_a_list(data):
"""Ensure data is a list or wrap it in a list"""
if not data:
return []
if isinstance(data, (list, tuple, set)):
return list(data)
if isinstance(data, str):
data = trimmed_split(data) # settings.toml,other.yaml
return data
return [data]
[docs]def build_env_list(obj, env):
"""Build env list for loaders to iterate.
Arguments:
obj {LazySettings} -- A Dynaconf settings instance
env {str} -- The current env to be loaded
Returns:
[str] -- A list of string names of the envs to load.
"""
# add the [default] env
env_list = [obj.get("DEFAULT_ENV_FOR_DYNACONF")]
# compatibility with older versions that still uses [dynaconf] as
# [default] env
global_env = obj.get("ENVVAR_PREFIX_FOR_DYNACONF") or "DYNACONF"
if global_env not in env_list:
env_list.append(global_env)
# add the current env
if obj.current_env and obj.current_env not in env_list:
env_list.append(obj.current_env)
# add a manually set env
if env and env not in env_list:
env_list.append(env)
# add the [global] env
env_list.append("GLOBAL")
# loaders are responsible to change to lower/upper cases
return [env.lower() for env in env_list]
[docs]def upperfy(key):
"""Receive a string key and returns its upper version.
Example:
input: foo
output: FOO
input: foo_bar
output: FOO_BAR
input: foo__bar__ZAZ
output: FOO__bar__ZAZ
Arguments:
key {str} -- A string key that may contain dunders `__`
Returns:
The key as upper case but keeping the nested elements.
"""
if "__" in key:
parts = key.split("__")
return "__".join([parts[0].upper()] + parts[1:])
return key.upper()