summaryrefslogtreecommitdiffhomepage
path: root/libs/dynaconf/loaders/env_loader.py
blob: 779e9a4f6f163faf971614e3740682415f66b85a (plain)
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
from __future__ import annotations

from os import environ

from dynaconf.utils import missing
from dynaconf.utils import upperfy
from dynaconf.utils.parse_conf import parse_conf_data

DOTENV_IMPORTED = False
try:
    from dynaconf.vendor.dotenv import cli as dotenv_cli

    DOTENV_IMPORTED = True
except ImportError:
    pass
except FileNotFoundError:
    pass


IDENTIFIER = "env"


def load(obj, env=None, silent=True, key=None):
    """Loads envvars with prefixes:

    `DYNACONF_` (default global) or `$(ENVVAR_PREFIX_FOR_DYNACONF)_`
    """
    global_prefix = obj.get("ENVVAR_PREFIX_FOR_DYNACONF")
    if global_prefix is False or global_prefix.upper() != "DYNACONF":
        load_from_env(obj, "DYNACONF", key, silent, IDENTIFIER + "_global")

    # Load the global env if exists and overwrite everything
    load_from_env(obj, global_prefix, key, silent, IDENTIFIER + "_global")


def load_from_env(
    obj,
    prefix=False,
    key=None,
    silent=False,
    identifier=IDENTIFIER,
    env=False,  # backwards compatibility bc renamed param
):
    if prefix is False and env is not False:
        prefix = env

    env_ = ""
    if prefix is not False:
        if not isinstance(prefix, str):
            raise TypeError("`prefix/env` must be str or False")

        prefix = prefix.upper()
        env_ = f"{prefix}_"

    # Load a single environment variable explicitly.
    if key:
        key = upperfy(key)
        value = environ.get(f"{env_}{key}")
        if value:
            try:  # obj is a Settings
                obj.set(key, value, loader_identifier=identifier, tomlfy=True)
            except AttributeError:  # obj is a dict
                obj[key] = parse_conf_data(
                    value, tomlfy=True, box_settings=obj
                )

    # Load environment variables in bulk (when matching).
    else:
        # Only known variables should be loaded from environment?
        ignore_unknown = obj.get("IGNORE_UNKNOWN_ENVVARS_FOR_DYNACONF")

        trim_len = len(env_)
        data = {
            key[trim_len:]: parse_conf_data(
                data, tomlfy=True, box_settings=obj
            )
            for key, data in environ.items()
            if key.startswith(env_)
            and not (
                # Ignore environment variables that haven't been
                # pre-defined in settings space.
                ignore_unknown
                and obj.get(key[trim_len:], default=missing) is missing
            )
        }
        # Update the settings space based on gathered data from environment.
        if data:
            filter_strategy = obj.get("FILTER_STRATEGY")
            if filter_strategy:
                data = filter_strategy(data)
            obj.update(data, loader_identifier=identifier)


def write(settings_path, settings_data, **kwargs):
    """Write data to .env file"""
    if not DOTENV_IMPORTED:
        return
    for key, value in settings_data.items():
        quote_mode = (
            isinstance(value, str)
            and (value.startswith("'") or value.startswith('"'))
        ) or isinstance(value, (list, dict))
        dotenv_cli.set_key(
            str(settings_path),
            key,
            str(value),
            quote_mode="always" if quote_mode else "none",
        )