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
|