1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146
|
import errno
import importlib
import inspect
import io
import types
from contextlib import suppress
from pathlib import Path
from dynaconf import default_settings
from dynaconf.utils import DynaconfDict
from dynaconf.utils import object_merge
from dynaconf.utils import upperfy
from dynaconf.utils.files import find_file
def load(obj, settings_module, identifier="py", silent=False, key=None):
"""Tries to import a python module"""
mod, loaded_from = get_module(obj, settings_module, silent)
if not (mod and loaded_from):
return
load_from_python_object(obj, mod, settings_module, key, identifier)
def load_from_python_object(
obj, mod, settings_module, key=None, identifier=None
):
file_merge = getattr(mod, "dynaconf_merge", False) or getattr(
mod, "DYNACONF_MERGE", False
)
for setting in dir(mod):
# A setting var in a Python file should start with upper case
# valid: A_value=1, ABC_value=3 A_BBB__default=1
# invalid: a_value=1, MyValue=3
# This is to avoid loading functions, classes and built-ins
if setting.split("__")[0].isupper():
if key is None or key == setting:
setting_value = getattr(mod, setting)
obj.set(
setting,
setting_value,
loader_identifier=identifier,
merge=file_merge,
)
obj._loaded_py_modules.append(mod.__name__)
obj._loaded_files.append(mod.__file__)
def try_to_load_from_py_module_name(
obj, name, key=None, identifier="py", silent=False
):
"""Try to load module by its string name.
Arguments:
obj {LAzySettings} -- Dynaconf settings instance
name {str} -- Name of the module e.g: foo.bar.zaz
Keyword Arguments:
key {str} -- Single key to be loaded (default: {None})
identifier {str} -- Name of identifier to store (default: 'py')
silent {bool} -- Weather to raise or silence exceptions.
"""
ctx = suppress(ImportError, TypeError) if silent else suppress()
with ctx:
mod = importlib.import_module(str(name))
load_from_python_object(obj, mod, name, key, identifier)
return True # loaded ok!
# if it reaches this point that means exception occurred, module not found.
return False
def get_module(obj, filename, silent=False):
try:
mod = importlib.import_module(filename)
loaded_from = "module"
mod.is_error = False
except (ImportError, TypeError):
mod = import_from_filename(obj, filename, silent=silent)
if mod and not mod._is_error:
loaded_from = "filename"
else:
# it is important to return None in case of not loaded
loaded_from = None
return mod, loaded_from
def import_from_filename(obj, filename, silent=False): # pragma: no cover
"""If settings_module is a filename path import it."""
if filename in [item.filename for item in inspect.stack()]:
raise ImportError(
"Looks like you are loading dynaconf "
f"from inside the {filename} file and then it is trying "
"to load itself entering in a circular reference "
"problem. To solve it you have to "
"invoke your program from another root folder "
"or rename your program file."
)
_find_file = getattr(obj, "find_file", find_file)
if not filename.endswith(".py"):
filename = f"{filename}.py"
if filename in default_settings.SETTINGS_FILE_FOR_DYNACONF:
silent = True
mod = types.ModuleType(filename.rstrip(".py"))
mod.__file__ = filename
mod._is_error = False
mod._error = None
try:
with io.open(
_find_file(filename),
encoding=default_settings.ENCODING_FOR_DYNACONF,
) as config_file:
exec(compile(config_file.read(), filename, "exec"), mod.__dict__)
except IOError as e:
e.strerror = (
f"py_loader: error loading file " f"({e.strerror} {filename})\n"
)
if silent and e.errno in (errno.ENOENT, errno.EISDIR):
return
mod._is_error = True
mod._error = e
return mod
def write(settings_path, settings_data, merge=True):
"""Write data to a settings file.
:param settings_path: the filepath
:param settings_data: a dictionary with data
:param merge: boolean if existing file should be merged with new data
"""
settings_path = Path(settings_path)
if settings_path.exists() and merge: # pragma: no cover
existing = DynaconfDict()
load(existing, str(settings_path))
object_merge(existing, settings_data)
with io.open(
str(settings_path),
"w",
encoding=default_settings.ENCODING_FOR_DYNACONF,
) as f:
f.writelines(
[f"{upperfy(k)} = {repr(v)}\n" for k, v in settings_data.items()]
)
|