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(old, 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 2
            if existing_value is not None and existing_value is value:
46 2
                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

59 2
    return new
60

61

62 2
def recursive_get(obj, names):
63
    """Given a dot accessible object and a list of names `foo.bar.zaz`
64
    gets recursivelly all names one by one obj.foo.bar.zaz.
65
    """
66 2
    if not names:
67 2
        return
68 2
    head, tail = names[0], names[1:]
69 2
    result = getattr(obj, head, None)
70 2
    if not tail:
71 2
        return result
72 2
    return recursive_get(result, tail)
73

74

75 2
def handle_metavalues(old, new):
76
    """Cleanup of MetaValues on new dict"""
77

78 2
    for key in list(new.keys()):
79

80
        # MetaValue instances
81
        if getattr(new[key], "_dynaconf_reset", False):  # pragma: no cover
82
            # a Reset on `new` triggers reasign of existing data
83
            # @reset is deprecated on v3.0.0
84
            new[key] = new[key].unwrap()
85 2
        elif getattr(new[key], "_dynaconf_del", False):
86
            # a Del on `new` triggers deletion of existing data
87 2
            new.pop(key, None)
88 2
            old.pop(key, None)
89 2
        elif getattr(new[key], "_dynaconf_merge", False):
90
            # a Merge on `new` triggers merge with existing data
91 2
            new[key] = object_merge(
92
                old.get(key), new[key].unwrap(), unique=new[key].unique
93
            )
94

95
        # Data structures containing merge tokens
96 2
        if isinstance(new.get(key), (list, tuple)):
97 2
            if (
98
                "dynaconf_merge" in new[key]
99
                or "dynaconf_merge_unique" in new[key]
100
            ):
101 2
                value = list(new[key])
102 2
                unique = False
103

104 2
                try:
105 2
                    value.remove("dynaconf_merge")
106 2
                except ValueError:
107 2
                    value.remove("dynaconf_merge_unique")
108 2
                    unique = True
109

110 2
                for item in old.get(key)[::-1]:
111 2
                    if unique and item in value:
112 2
                        continue
113 2
                    value.insert(0, item)
114

115 2
                new[key] = value
116

117 2
        elif isinstance(new.get(key), dict):
118 2
            local_merge = new[key].pop(
119
                "dynaconf_merge", new[key].pop("dynaconf_merge_unique", None)
120
            )
121 2
            if local_merge not in (True, False, None) and not new[key]:
122
                # In case `dynaconf_merge:` holds value not boolean - ref #241
123 2
                new[key] = local_merge
124

125 2
            if local_merge:
126 2
                new[key] = object_merge(old.get(key), new[key])
127

128

129 2
class DynaconfDict(dict):
130
    """A dict representing en empty Dynaconf object
131
    useful to run loaders in to a dict for testing"""
132

133 2
    def __init__(self, *args, **kwargs):
134 2
        self._loaded_files = []
135 2
        super(DynaconfDict, self).__init__(*args, **kwargs)
136

137 2
    def set(self, key, value, *args, **kwargs):
138 2
        self[key] = value
139

140 2
    @staticmethod
141
    def get_environ(key, default=None):  # pragma: no cover
142
        return os.environ.get(key, default)
143

144 2
    def exists(self, key, **kwargs):
145 2
        return self.get(key, missing) is not missing
146

147

148 2
RENAMED_VARS = {
149
    # old: new
150
    "DYNACONF_NAMESPACE": "ENV_FOR_DYNACONF",
151
    "NAMESPACE_FOR_DYNACONF": "ENV_FOR_DYNACONF",
152
    "DYNACONF_SETTINGS_MODULE": "SETTINGS_FILE_FOR_DYNACONF",
153
    "DYNACONF_SETTINGS": "SETTINGS_FILE_FOR_DYNACONF",
154
    "SETTINGS_MODULE": "SETTINGS_FILE_FOR_DYNACONF",
155
    "SETTINGS_MODULE_FOR_DYNACONF": "SETTINGS_FILE_FOR_DYNACONF",
156
    "PROJECT_ROOT": "ROOT_PATH_FOR_DYNACONF",
157
    "PROJECT_ROOT_FOR_DYNACONF": "ROOT_PATH_FOR_DYNACONF",
158
    "DYNACONF_SILENT_ERRORS": "SILENT_ERRORS_FOR_DYNACONF",
159
    "DYNACONF_ALWAYS_FRESH_VARS": "FRESH_VARS_FOR_DYNACONF",
160
    "BASE_NAMESPACE_FOR_DYNACONF": "DEFAULT_ENV_FOR_DYNACONF",
161
    "GLOBAL_ENV_FOR_DYNACONF": "ENVVAR_PREFIX_FOR_DYNACONF",
162
}
163

164

165 2
def compat_kwargs(kwargs):
166
    """To keep backwards compat change the kwargs to new names"""
167 2
    warn_deprecations(kwargs)
168 2
    for old, new in RENAMED_VARS.items():
169 2
        if old in kwargs:
170 2
            kwargs[new] = kwargs[old]
171
            # update cross references
172 2
            for c_old, c_new in RENAMED_VARS.items():
173 2
                if c_new == new:
174 2
                    kwargs[c_old] = kwargs[new]
175

176

177 2
class Missing(object):
178
    """
179
    Sentinel value object/singleton used to differentiate between ambiguous
180
    situations where `None` is a valid value.
181
    """
182

183 2
    def __bool__(self):
184
        """Respond to boolean duck-typing."""
185 2
        return False
186

187 2
    def __eq__(self, other):
188
        """Equality check for a singleton."""
189

190 2
        return isinstance(other, self.__class__)
191

192
    # Ensure compatibility with Python 2.x
193 2
    __nonzero__ = __bool__
194

195 2
    def __repr__(self):
196
        """
197
        Unambiguously identify this string-based representation of Missing,
198
        used as a singleton.
199
        """
200 2
        return "<dynaconf.missing>"
201

202

203 2
missing = Missing()
204

205

206 2
def deduplicate(list_object):
207
    """Rebuild `list_object` removing duplicated and keeping order"""
208 2
    new = []
209 2
    for item in list_object:
210 2
        if item not in new:
211 2
            new.append(item)
212 2
    return new
213

214

215 2
def warn_deprecations(data):
216 2
    for old, new in RENAMED_VARS.items():
217 2
        if old in data:
218 2
            warnings.warn(
219
                f"You are using {old} which is a deprecated settings "
220
                f"replace it with {new}",
221
                DeprecationWarning,
222
            )
223

224

225 2
def trimmed_split(s, seps=(";", ",")):
226
    """Given a string s, split is by one of one of the seps."""
227 2
    for sep in seps:
228 2
        if sep not in s:
229 2
            continue
230 2
        data = [item.strip() for item in s.strip().split(sep)]
231 2
        return data
232 2
    return [s]  # raw un-splitted
233

234

235 2
def ensure_a_list(data):
236
    """Ensure data is a list or wrap it in a list"""
237 2
    if not data:
238 2
        return []
239 2
    if isinstance(data, (list, tuple, set)):
240 2
        return list(data)
241 2
    if isinstance(data, str):
242 2
        data = trimmed_split(data)  # settings.toml,other.yaml
243 2
        return data
244 2
    return [data]
245

246

247 2
def build_env_list(obj, env):
248
    """Build env list for loaders to iterate.
249

250
    Arguments:
251
        obj {LazySettings} -- A Dynaconf settings instance
252
        env {str} -- The current env to be loaded
253

254
    Returns:
255
        [str] -- A list of string names of the envs to load.
256
    """
257
    # add the [default] env
258 2
    env_list = [obj.get("DEFAULT_ENV_FOR_DYNACONF")]
259

260
    # compatibility with older versions that still uses [dynaconf] as
261
    # [default] env
262 2
    global_env = obj.get("ENVVAR_PREFIX_FOR_DYNACONF") or "DYNACONF"
263 2
    if global_env not in env_list:
264 2
        env_list.append(global_env)
265

266
    # add the current env
267 2
    if obj.current_env and obj.current_env not in env_list:
268 2
        env_list.append(obj.current_env)
269

270
    # add a manually set env
271 2
    if env and env not in env_list:
272 2
        env_list.append(env)
273

274
    # add the [global] env
275 2
    env_list.append("GLOBAL")
276

277
    # loaders are responsible to change to lower/upper cases
278 2
    return [env.lower() for env in env_list]
279

280

281 2
def upperfy(key):
282
    """Receive a string key and returns its upper version.
283

284
    Example:
285

286
      input: foo
287
      output: FOO
288

289
      input: foo_bar
290
      output: FOO_BAR
291

292
      input: foo__bar__ZAZ
293
      output: FOO__bar__ZAZ
294

295
    Arguments:
296
        key {str} -- A string key that may contain dunders `__`
297

298
    Returns:
299
        The key as upper case but keeping the nested elements.
300
    """
301 2
    key = str(key)
302 2
    if "__" in key:
303 2
        parts = key.split("__")
304 2
        return "__".join([parts[0].upper()] + parts[1:])
305 2
    return key.upper()
306

307

308 2
def multi_replace(text, patterns):
309
    """Replaces multiple pairs in a string
310

311
    Arguments:
312
        text {str} -- A "string text"
313
        patterns {dict} -- A dict of {"old text": "new text"}
314

315
    Returns:
316
        text -- str
317
    """
318 2
    for old, new in patterns.items():
319 2
        text = text.replace(old, new)
320 2
    return text
321

322

323 2
def extract_json_objects(text, decoder=JSONDecoder()):
324
    """Find JSON objects in text, and yield the decoded JSON data
325

326
    Does not attempt to look for JSON arrays, text, or other JSON types outside
327
    of a parent JSON object.
328

329
    """
330 2
    pos = 0
331 2
    while True:
332 2
        match = text.find("{", pos)
333 2
        if match == -1:
334 2
            break
335 2
        try:
336 2
            result, index = decoder.raw_decode(text[match:])
337 2
            yield result
338 2
            pos = match + index
339 2
        except ValueError:
340 2
            pos = match + 1
341

342

343 2
def recursively_evaluate_lazy_format(value, settings):
344
    """Given a value as a data structure, traverse all its members
345
    to find Lazy values and evaluate it.
346

347
    For example: Evaluate values inside lists and dicts
348
    """
349

350 2
    if getattr(value, "_dynaconf_lazy_format", None):
351 2
        value = value(settings)
352

353 2
    if isinstance(value, list):
354
        # Keep the original type, can be a BoxList
355 2
        value = value.__class__(
356
            [
357
                recursively_evaluate_lazy_format(item, settings)
358
                for item in value
359
            ]
360
        )
361

362 2
    return value

Read our documentation on viewing source code .

Loading