1 2
import os
2 2
import warnings
3 2
from json import JSONDecoder
4

5

6 2
BANNER = """
7
██████╗ ██╗   ██╗███╗   ██╗ █████╗  ██████╗ ██████╗ ███╗   ██╗███████╗
8
██╔══██╗╚██╗ ██╔╝████╗  ██║██╔══██╗██╔════╝██╔═══██╗████╗  ██║██╔════╝
9
██║  ██║ ╚████╔╝ ██╔██╗ ██║███████║██║     ██║   ██║██╔██╗ ██║█████╗
10
██║  ██║  ╚██╔╝  ██║╚██╗██║██╔══██║██║     ██║   ██║██║╚██╗██║██╔══╝
11
██████╔╝   ██║   ██║ ╚████║██║  ██║╚██████╗╚██████╔╝██║ ╚████║██║
12
╚═════╝    ╚═╝   ╚═╝  ╚═══╝╚═╝  ╚═╝ ╚═════╝ ╚═════╝ ╚═╝  ╚═══╝╚═╝
13
"""
14

15
if os.name == "nt":  # pragma: no cover
16
    # windows can't handle the above charmap
17
    BANNER = "DYNACONF"
18

19

20 2
def object_merge(old, new, unique=False, full_path=None):
21
    """
22
    Recursively merge two data structures, new is mutated in-place.
23

24
    :param old: The existing data.
25
    :param new: The new data to get old values merged in to.
26
    :param unique: When set to True existing list items are not set.
27
    :param full_path: Indicates the elements of a tree.
28
    """
29 2
    if old == new or old is None or new is None:
30
        # Nothing to merge
31 2
        return new
32

33 2
    if isinstance(old, list) and isinstance(new, list):
34 2
        for item in old[::-1]:
35 2
            if unique and item in new:
36 2
                continue
37 2
            new.insert(0, item)
38

39 2
    if isinstance(old, dict) and isinstance(new, dict):
40 2
        existing_value = recursive_get(new, full_path)  # doesnt handle None
41
        # Need to make every `None` on `_store` to be an wrapped `LazyNone`
42

43 2
        for key, value in old.items():
44

45
            # if existing_value is not None and existing_value is value:
46
            #     continue
47

48 2
            if key not in new:
49 2
                new[key] = value
50
            else:
51 2
                object_merge(
52
                    value,
53
                    new[key],
54
                    full_path=full_path[1:] if full_path else None,
55
                )
56

57 2
        handle_metavalues(old, new)
58 2
        recursive_set(new, full_path, existing_value)
59

60 2
    return new
61

62

63 2
def recursive_set(obj, names, value):
64 2
    if not names:
65 2
        return
66 2
    head, tail = names[0], names[1:]
67 2
    if not tail:
68 2
        obj[head] = value
69
    else:
70 2
        recursive_set(obj[head], tail, value)
71

72

73 2
def recursive_get(obj, names):
74
    """Given a dot accessible object and a list of names `foo.bar.zaz`
75
    gets recursivelly all names one by one obj.foo.bar.zaz.
76
    """
77 2
    if not names:
78 2
        return
79 2
    head, tail = names[0], names[1:]
80 2
    result = getattr(obj, head, None)
81 2
    if not tail:
82 2
        return result
83 2
    return recursive_get(result, tail)
84

85

86 2
def handle_metavalues(old, new):
87
    """Cleanup of MetaValues on new dict"""
88

89 2
    for key in list(new.keys()):
90

91
        # MetaValue instances
92
        if getattr(new[key], "_dynaconf_reset", False):  # pragma: no cover
93
            # a Reset on `new` triggers reasign of existing data
94
            # @reset is deprecated on v3.0.0
95
            new[key] = new[key].unwrap()
96 2
        elif getattr(new[key], "_dynaconf_del", False):
97
            # a Del on `new` triggers deletion of existing data
98 2
            new.pop(key, None)
99 2
            old.pop(key, None)
100 2
        elif getattr(new[key], "_dynaconf_merge", False):
101
            # a Merge on `new` triggers merge with existing data
102 2
            new[key] = object_merge(
103
                old.get(key), new[key].unwrap(), unique=new[key].unique
104
            )
105

106
        # Data structures containing merge tokens
107 2
        if isinstance(new.get(key), (list, tuple)):
108 2
            if (
109
                "dynaconf_merge" in new[key]
110
                or "dynaconf_merge_unique" in new[key]
111
            ):
112 0
                value = list(new[key])
113 0
                unique = False
114

115 0
                try:
116 0
                    value.remove("dynaconf_merge")
117 0
                except ValueError:
118 0
                    value.remove("dynaconf_merge_unique")
119 0
                    unique = True
120

121 0
                for item in old.get(key)[::-1]:
122 0
                    if unique and item in value:
123 0
                        continue
124 0
                    value.insert(0, item)
125

126 0
                new[key] = value
127

128 2
        elif isinstance(new.get(key), dict):
129 2
            local_merge = new[key].pop(
130
                "dynaconf_merge", new[key].pop("dynaconf_merge_unique", None)
131
            )
132 2
            if local_merge not in (True, False, None) and not new[key]:
133
                # In case `dynaconf_merge:` holds value not boolean - ref #241
134 0
                new[key] = local_merge
135

136 2
            if local_merge:
137 2
                new[key] = object_merge(old.get(key), new[key])
138

139

140 2
class DynaconfDict(dict):
141
    """A dict representing en empty Dynaconf object
142
    useful to run loaders in to a dict for testing"""
143

144 2
    def __init__(self, *args, **kwargs):
145 2
        self._loaded_files = []
146 2
        super(DynaconfDict, self).__init__(*args, **kwargs)
147

148 2
    def set(self, key, value, *args, **kwargs):
149 2
        self[key] = value
150

151 2
    @staticmethod
152
    def get_environ(key, default=None):  # pragma: no cover
153
        return os.environ.get(key, default)
154

155 2
    def exists(self, key, **kwargs):
156 2
        return self.get(key, missing) is not missing
157

158

159 2
RENAMED_VARS = {
160
    # old: new
161
    "DYNACONF_NAMESPACE": "ENV_FOR_DYNACONF",
162
    "NAMESPACE_FOR_DYNACONF": "ENV_FOR_DYNACONF",
163
    "DYNACONF_SETTINGS_MODULE": "SETTINGS_FILE_FOR_DYNACONF",
164
    "DYNACONF_SETTINGS": "SETTINGS_FILE_FOR_DYNACONF",
165
    "SETTINGS_MODULE": "SETTINGS_FILE_FOR_DYNACONF",
166
    "SETTINGS_MODULE_FOR_DYNACONF": "SETTINGS_FILE_FOR_DYNACONF",
167
    "PROJECT_ROOT": "ROOT_PATH_FOR_DYNACONF",
168
    "PROJECT_ROOT_FOR_DYNACONF": "ROOT_PATH_FOR_DYNACONF",
169
    "DYNACONF_SILENT_ERRORS": "SILENT_ERRORS_FOR_DYNACONF",
170
    "DYNACONF_ALWAYS_FRESH_VARS": "FRESH_VARS_FOR_DYNACONF",
171
    "BASE_NAMESPACE_FOR_DYNACONF": "DEFAULT_ENV_FOR_DYNACONF",
172
    "GLOBAL_ENV_FOR_DYNACONF": "ENVVAR_PREFIX_FOR_DYNACONF",
173
}
174

175

176 2
def compat_kwargs(kwargs):
177
    """To keep backwards compat change the kwargs to new names"""
178 2
    warn_deprecations(kwargs)
179 2
    for old, new in RENAMED_VARS.items():
180 2
        if old in kwargs:
181 2
            kwargs[new] = kwargs[old]
182
            # update cross references
183 2
            for c_old, c_new in RENAMED_VARS.items():
184 2
                if c_new == new:
185 2
                    kwargs[c_old] = kwargs[new]
186

187

188 2
class Missing(object):
189
    """
190
    Sentinel value object/singleton used to differentiate between ambiguous
191
    situations where `None` is a valid value.
192
    """
193

194 2
    def __bool__(self):
195
        """Respond to boolean duck-typing."""
196 2
        return False
197

198 2
    def __eq__(self, other):
199
        """Equality check for a singleton."""
200

201 2
        return isinstance(other, self.__class__)
202

203
    # Ensure compatibility with Python 2.x
204 2
    __nonzero__ = __bool__
205

206 2
    def __repr__(self):
207
        """
208
        Unambiguously identify this string-based representation of Missing,
209
        used as a singleton.
210
        """
211 2
        return "<dynaconf.missing>"
212

213

214 2
missing = Missing()
215

216

217 2
def deduplicate(list_object):
218
    """Rebuild `list_object` removing duplicated and keeping order"""
219 2
    new = []
220 2
    for item in list_object:
221 2
        if item not in new:
222 2
            new.append(item)
223 2
    return new
224

225

226 2
def warn_deprecations(data):
227 2
    for old, new in RENAMED_VARS.items():
228 2
        if old in data:
229 2
            warnings.warn(
230
                f"You are using {old} which is a deprecated settings "
231
                f"replace it with {new}",
232
                DeprecationWarning,
233
            )
234

235

236 2
def trimmed_split(s, seps=(";", ",")):
237
    """Given a string s, split is by one of one of the seps."""
238 2
    for sep in seps:
239 2
        if sep not in s:
240 2
            continue
241 2
        data = [item.strip() for item in s.strip().split(sep)]
242 2
        return data
243 2
    return [s]  # raw un-splitted
244

245

246 2
def ensure_a_list(data):
247
    """Ensure data is a list or wrap it in a list"""
248 2
    if not data:
249 2
        return []
250 2
    if isinstance(data, (list, tuple, set)):
251 2
        return list(data)
252 2
    if isinstance(data, str):
253 2
        data = trimmed_split(data)  # settings.toml,other.yaml
254 2
        return data
255 2
    return [data]
256

257

258 2
def build_env_list(obj, env):
259
    """Build env list for loaders to iterate.
260

261
    Arguments:
262
        obj {LazySettings} -- A Dynaconf settings instance
263
        env {str} -- The current env to be loaded
264

265
    Returns:
266
        [str] -- A list of string names of the envs to load.
267
    """
268
    # add the [default] env
269 2
    env_list = [obj.get("DEFAULT_ENV_FOR_DYNACONF")]
270

271
    # compatibility with older versions that still uses [dynaconf] as
272
    # [default] env
273 2
    global_env = obj.get("ENVVAR_PREFIX_FOR_DYNACONF") or "DYNACONF"
274 2
    if global_env not in env_list:
275 2
        env_list.append(global_env)
276

277
    # add the current env
278 2
    if obj.current_env and obj.current_env not in env_list:
279 2
        env_list.append(obj.current_env)
280

281
    # add a manually set env
282 2
    if env and env not in env_list:
283 2
        env_list.append(env)
284

285
    # add the [global] env
286 2
    env_list.append("GLOBAL")
287

288
    # loaders are responsible to change to lower/upper cases
289 2
    return [env.lower() for env in env_list]
290

291

292 2
def upperfy(key):
293
    """Receive a string key and returns its upper version.
294

295
    Example:
296

297
      input: foo
298
      output: FOO
299

300
      input: foo_bar
301
      output: FOO_BAR
302

303
      input: foo__bar__ZAZ
304
      output: FOO__bar__ZAZ
305

306
    Arguments:
307
        key {str} -- A string key that may contain dunders `__`
308

309
    Returns:
310
        The key as upper case but keeping the nested elements.
311
    """
312 2
    key = str(key)
313 2
    if "__" in key:
314 2
        parts = key.split("__")
315 2
        return "__".join([parts[0].upper()] + parts[1:])
316 2
    return key.upper()
317

318

319 2
def multi_replace(text, patterns):
320
    """Replaces multiple pairs in a string
321

322
    Arguments:
323
        text {str} -- A "string text"
324
        patterns {dict} -- A dict of {"old text": "new text"}
325

326
    Returns:
327
        text -- str
328
    """
329 2
    for old, new in patterns.items():
330 2
        text = text.replace(old, new)
331 2
    return text
332

333

334 2
def extract_json_objects(text, decoder=JSONDecoder()):
335
    """Find JSON objects in text, and yield the decoded JSON data
336

337
    Does not attempt to look for JSON arrays, text, or other JSON types outside
338
    of a parent JSON object.
339

340
    """
341 2
    pos = 0
342 2
    while True:
343 2
        match = text.find("{", pos)
344 2
        if match == -1:
345 2
            break
346 2
        try:
347 2
            result, index = decoder.raw_decode(text[match:])
348 2
            yield result
349 2
            pos = match + index
350 2
        except ValueError:
351 2
            pos = match + 1
352

353

354 2
def recursively_evaluate_lazy_format(value, settings):
355
    """Given a value as a data structure, traverse all its members
356
    to find Lazy values and evaluate it.
357

358
    For example: Evaluate values inside lists and dicts
359
    """
360

361 2
    if getattr(value, "_dynaconf_lazy_format", None):
362 2
        value = value(settings)
363

364 2
    if isinstance(value, list):
365
        # Keep the original type, can be a BoxList
366 2
        value = value.__class__(
367
            [
368
                recursively_evaluate_lazy_format(item, settings)
369
                for item in value
370
            ]
371
        )
372

373 2
    return value

Read our documentation on viewing source code .

Loading