dynaconf 3.2.6__tar.gz → 3.2.7__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. {dynaconf-3.2.6 → dynaconf-3.2.7}/CHANGELOG.md +21 -0
  2. {dynaconf-3.2.6 → dynaconf-3.2.7}/PKG-INFO +1 -2
  3. dynaconf-3.2.7/dynaconf/VERSION +1 -0
  4. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/base.py +33 -2
  5. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/cli.py +79 -35
  6. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/loaders/__init__.py +69 -33
  7. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/loaders/env_loader.py +29 -13
  8. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/loaders/ini_loader.py +17 -2
  9. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/loaders/json_loader.py +17 -2
  10. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/loaders/py_loader.py +10 -2
  11. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/loaders/redis_loader.py +9 -2
  12. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/loaders/toml_loader.py +17 -3
  13. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/loaders/yaml_loader.py +17 -2
  14. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/utils/__init__.py +50 -7
  15. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/utils/inspect.py +9 -2
  16. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/utils/parse_conf.py +54 -1
  17. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/validator.py +25 -7
  18. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/vendor/click/_termui_impl.py +3 -3
  19. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/vendor/click/core.py +1 -1
  20. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/vendor/click/types.py +1 -1
  21. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/vendor/ruamel/yaml/error.py +6 -7
  22. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/vendor/ruamel/yaml/events.py +6 -11
  23. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/vendor/ruamel/yaml/scalarstring.py +2 -3
  24. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/vendor/ruamel/yaml/tokens.py +18 -19
  25. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf.egg-info/PKG-INFO +1 -2
  26. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf.egg-info/entry_points.txt +0 -1
  27. dynaconf-3.2.6/dynaconf/VERSION +0 -1
  28. {dynaconf-3.2.6 → dynaconf-3.2.7}/3.x-release-notes.md +0 -0
  29. {dynaconf-3.2.6 → dynaconf-3.2.7}/CONTRIBUTING.md +0 -0
  30. {dynaconf-3.2.6 → dynaconf-3.2.7}/CONTRIBUTORS.md +0 -0
  31. {dynaconf-3.2.6 → dynaconf-3.2.7}/LICENSE +0 -0
  32. {dynaconf-3.2.6 → dynaconf-3.2.7}/MANIFEST.in +0 -0
  33. {dynaconf-3.2.6 → dynaconf-3.2.7}/README.md +0 -0
  34. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/__init__.py +0 -0
  35. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/constants.py +0 -0
  36. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/contrib/__init__.py +0 -0
  37. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/contrib/django_dynaconf_v2.py +0 -0
  38. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/contrib/flask_dynaconf.py +0 -0
  39. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/default_settings.py +0 -0
  40. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/hooking.py +0 -0
  41. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/loaders/base.py +0 -0
  42. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/loaders/vault_loader.py +0 -0
  43. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/strategies/__init__.py +0 -0
  44. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/strategies/filtering.py +0 -0
  45. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/test_settings.py +0 -0
  46. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/utils/boxing.py +0 -0
  47. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/utils/files.py +0 -0
  48. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/utils/functional.py +0 -0
  49. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/validator_conditions.py +0 -0
  50. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/vendor/__init__.py +0 -0
  51. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/vendor/box/__init__.py +0 -0
  52. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/vendor/box/box.py +0 -0
  53. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/vendor/box/box_list.py +0 -0
  54. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/vendor/box/config_box.py +0 -0
  55. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/vendor/box/converters.py +0 -0
  56. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/vendor/box/exceptions.py +0 -0
  57. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/vendor/box/from_file.py +0 -0
  58. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/vendor/box/shorthand_box.py +0 -0
  59. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/vendor/click/__init__.py +0 -0
  60. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/vendor/click/_bashcomplete.py +0 -0
  61. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/vendor/click/_compat.py +0 -0
  62. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/vendor/click/_textwrap.py +0 -0
  63. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/vendor/click/_unicodefun.py +0 -0
  64. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/vendor/click/_winconsole.py +0 -0
  65. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/vendor/click/decorators.py +0 -0
  66. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/vendor/click/exceptions.py +0 -0
  67. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/vendor/click/formatting.py +0 -0
  68. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/vendor/click/globals.py +0 -0
  69. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/vendor/click/parser.py +0 -0
  70. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/vendor/click/termui.py +0 -0
  71. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/vendor/click/testing.py +0 -0
  72. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/vendor/click/utils.py +0 -0
  73. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/vendor/dotenv/__init__.py +0 -0
  74. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/vendor/dotenv/cli.py +0 -0
  75. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/vendor/dotenv/compat.py +0 -0
  76. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/vendor/dotenv/ipython.py +0 -0
  77. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/vendor/dotenv/main.py +0 -0
  78. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/vendor/dotenv/parser.py +0 -0
  79. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/vendor/dotenv/version.py +0 -0
  80. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/vendor/ruamel/__init__.py +0 -0
  81. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/vendor/ruamel/yaml/__init__.py +0 -0
  82. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/vendor/ruamel/yaml/anchor.py +0 -0
  83. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/vendor/ruamel/yaml/comments.py +0 -0
  84. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/vendor/ruamel/yaml/compat.py +0 -0
  85. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/vendor/ruamel/yaml/composer.py +0 -0
  86. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/vendor/ruamel/yaml/configobjwalker.py +0 -0
  87. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/vendor/ruamel/yaml/constructor.py +0 -0
  88. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/vendor/ruamel/yaml/cyaml.py +0 -0
  89. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/vendor/ruamel/yaml/dumper.py +0 -0
  90. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/vendor/ruamel/yaml/emitter.py +0 -0
  91. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/vendor/ruamel/yaml/loader.py +0 -0
  92. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/vendor/ruamel/yaml/main.py +0 -0
  93. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/vendor/ruamel/yaml/nodes.py +0 -0
  94. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/vendor/ruamel/yaml/parser.py +0 -0
  95. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/vendor/ruamel/yaml/reader.py +0 -0
  96. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/vendor/ruamel/yaml/representer.py +0 -0
  97. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/vendor/ruamel/yaml/resolver.py +0 -0
  98. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/vendor/ruamel/yaml/scalarbool.py +0 -0
  99. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/vendor/ruamel/yaml/scalarfloat.py +0 -0
  100. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/vendor/ruamel/yaml/scalarint.py +0 -0
  101. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/vendor/ruamel/yaml/scanner.py +0 -0
  102. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/vendor/ruamel/yaml/serializer.py +0 -0
  103. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/vendor/ruamel/yaml/setup.py +0 -0
  104. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/vendor/ruamel/yaml/timestamp.py +0 -0
  105. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/vendor/ruamel/yaml/util.py +0 -0
  106. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/vendor/toml/__init__.py +0 -0
  107. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/vendor/toml/decoder.py +0 -0
  108. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/vendor/toml/encoder.py +0 -0
  109. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/vendor/toml/ordered.py +0 -0
  110. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/vendor/toml/tz.py +0 -0
  111. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/vendor/tomllib/__init__.py +0 -0
  112. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/vendor/tomllib/_parser.py +0 -0
  113. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/vendor/tomllib/_re.py +0 -0
  114. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/vendor/tomllib/_types.py +0 -0
  115. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf/vendor/tomllib/_writer.py +0 -0
  116. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf.egg-info/SOURCES.txt +0 -0
  117. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf.egg-info/dependency_links.txt +0 -0
  118. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf.egg-info/not-zip-safe +0 -0
  119. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf.egg-info/requires.txt +0 -0
  120. {dynaconf-3.2.6 → dynaconf-3.2.7}/dynaconf.egg-info/top_level.txt +0 -0
  121. {dynaconf-3.2.6 → dynaconf-3.2.7}/setup.cfg +0 -0
  122. {dynaconf-3.2.6 → dynaconf-3.2.7}/setup.py +0 -0
  123. {dynaconf-3.2.6 → dynaconf-3.2.7}/vendor_licenses/box-LICENSE.txt +0 -0
  124. {dynaconf-3.2.6 → dynaconf-3.2.7}/vendor_licenses/click-LICENSE.rst +0 -0
  125. {dynaconf-3.2.6 → dynaconf-3.2.7}/vendor_licenses/licenses.sh +0 -0
  126. {dynaconf-3.2.6 → dynaconf-3.2.7}/vendor_licenses/python-dotenv-LICENSE.txt +0 -0
  127. {dynaconf-3.2.6 → dynaconf-3.2.7}/vendor_licenses/ruamel.yaml-LICENSE.txt +0 -0
  128. {dynaconf-3.2.6 → dynaconf-3.2.7}/vendor_licenses/toml-LICENSE.txt +0 -0
  129. {dynaconf-3.2.6 → dynaconf-3.2.7}/vendor_licenses/tomli-LICENSE.txt +0 -0
  130. {dynaconf-3.2.6 → dynaconf-3.2.7}/vendor_licenses/vendor_versions.txt +0 -0
@@ -2,6 +2,27 @@ Changelog
2
2
  =========
3
3
 
4
4
  <!-- insertion marker -->
5
+ ## [3.2.7](https://github.com/dynaconf/dynaconf/releases/tag/3.2.7) - 2025-01-21
6
+
7
+ ### Bug Fixes
8
+
9
+ - lazy validator's default value would evaluate early (#1198). *By Pedro Brochado*.
10
+ - Fixed an error that would raise when using get_history() with lazy values (#1184) (#1185). *By Pedro Brochado*.
11
+ - Fixed Redis loader when ENV prefix is `None`.
12
+ - Populate object method now takes `internal` attribute to filter out internal variables.
13
+ - On CLI `json.dumps` defaults to `repr` for types that cannot be serialized.
14
+ - Added an identifier to validator calls of `set` method
15
+ - Fix django app discovery using DJANGO_SETTINGS_MODULE variable
16
+
17
+ ### Features
18
+
19
+ - Added `@insert` token to call `list.insert`
20
+ - Allow env loader to load from multiple prefixes
21
+ - Allow multiple composable current environments
22
+ - Track more data on `load_file` method
23
+ - Added `--json` to dynaconf list CLI
24
+
25
+
5
26
  ## [3.2.6](https://github.com/dynaconf/dynaconf/releases/tag/3.2.6) - 2024-07-19
6
27
 
7
28
  ## [3.2.5](https://github.com/pedro-psb/dynaconf/releases/tag/3.2.5) - 2024-03-18
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dynaconf
3
- Version: 3.2.6
3
+ Version: 3.2.7
4
4
  Summary: The dynamic configurator for your Python Project
5
5
  Home-page: https://github.com/dynaconf/dynaconf
6
6
  Author: Bruno Rocha
@@ -180,4 +180,3 @@ Main discussions happens on [Discussions Tab](https://github.com/dynaconf/dynaco
180
180
  If you are looking for something similar to Dynaconf to use in your Rust projects: https://github.com/rubik/hydroconf
181
181
 
182
182
  And a special thanks to [Caneco](https://twitter.com/caneco) for the logo.
183
-
@@ -0,0 +1 @@
1
+ 3.2.7
@@ -968,6 +968,15 @@ class Settings:
968
968
  self.store.get(key, None) if not isinstance(parsed, Lazy) else None
969
969
  )
970
970
 
971
+ if getattr(parsed, "_dynaconf_insert", False):
972
+ # `@insert` calls insert in a list by index
973
+ if existing and isinstance(existing, list):
974
+ source_metadata = source_metadata._replace(merged=True)
975
+ existing.insert(parsed.index, parsed.unwrap())
976
+ parsed = existing
977
+ else:
978
+ parsed = [parsed.unwrap()]
979
+
971
980
  if getattr(parsed, "_dynaconf_del", None):
972
981
  self.unset(key, force=True) # `@del` in a first level var.
973
982
  return
@@ -1227,14 +1236,27 @@ class Settings:
1227
1236
  validate = self.get("VALIDATE_ON_UPDATE_FOR_DYNACONF")
1228
1237
 
1229
1238
  env = (env or self.current_env).upper()
1239
+
1230
1240
  files = ensure_a_list(path)
1231
1241
  if files:
1242
+ # Using inspect take the filename and line number of the caller
1243
+ # to be used in the source_metadata
1244
+ frame = inspect.currentframe()
1245
+ caller = inspect.getouterframes(frame)[1]
1232
1246
  already_loaded = set()
1233
1247
  for _filename in files:
1234
1248
  # load_file() will handle validation later
1235
1249
  with suppress(ValidationError):
1250
+ source_metadata = SourceMetadata(
1251
+ loader=f"load_file@{caller.filename}:{caller.lineno}",
1252
+ identifier=_filename,
1253
+ env=env,
1254
+ )
1236
1255
  if py_loader.try_to_load_from_py_module_name(
1237
- obj=self, name=_filename, silent=True
1256
+ obj=self,
1257
+ name=_filename,
1258
+ silent=True,
1259
+ identifier=source_metadata,
1238
1260
  ):
1239
1261
  # if it was possible to load from module name
1240
1262
  # continue the loop.
@@ -1265,6 +1287,11 @@ class Settings:
1265
1287
 
1266
1288
  # load_file() will handle validation later
1267
1289
  with suppress(ValidationError):
1290
+ source_metadata = SourceMetadata(
1291
+ loader=f"load_file@{caller.filename}:{caller.lineno}",
1292
+ identifier=path,
1293
+ env=env,
1294
+ )
1268
1295
  settings_loader(
1269
1296
  obj=self,
1270
1297
  env=env,
@@ -1272,6 +1299,7 @@ class Settings:
1272
1299
  key=key,
1273
1300
  filename=path,
1274
1301
  validate=validate,
1302
+ identifier=source_metadata,
1275
1303
  )
1276
1304
  already_loaded.add(path)
1277
1305
 
@@ -1357,7 +1385,7 @@ class Settings:
1357
1385
  value = self.get_fresh(key)
1358
1386
  return value is True or value in true_values
1359
1387
 
1360
- def populate_obj(self, obj, keys=None, ignore=None):
1388
+ def populate_obj(self, obj, keys=None, ignore=None, internal=False):
1361
1389
  """Given the `obj` populate it using self.store items.
1362
1390
 
1363
1391
  :param obj: An object to be populated, a class instance.
@@ -1366,6 +1394,9 @@ class Settings:
1366
1394
  """
1367
1395
  keys = keys or self.keys()
1368
1396
  for key in keys:
1397
+ if not internal:
1398
+ if key in UPPER_DEFAULT_SETTINGS:
1399
+ continue
1369
1400
  key = upperfy(key)
1370
1401
  if ignore and key in ignore:
1371
1402
  continue
@@ -1,22 +1,25 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import importlib
4
+ import inspect as python_inspect
4
5
  import json
5
6
  import os
6
7
  import pprint
7
8
  import sys
8
9
  import warnings
9
10
  import webbrowser
11
+ from contextlib import redirect_stdout
10
12
  from contextlib import suppress
11
13
  from pathlib import Path
12
- from typing import TYPE_CHECKING
13
14
 
14
15
  from dynaconf import constants
15
16
  from dynaconf import default_settings
16
17
  from dynaconf import LazySettings
17
18
  from dynaconf import loaders
18
19
  from dynaconf import settings as legacy_settings
20
+ from dynaconf.base import Settings
19
21
  from dynaconf.loaders.py_loader import get_module
22
+ from dynaconf.utils import prepare_json
20
23
  from dynaconf.utils import upperfy
21
24
  from dynaconf.utils.files import read_file
22
25
  from dynaconf.utils.functional import empty
@@ -33,9 +36,6 @@ from dynaconf.vendor import click
33
36
  from dynaconf.vendor import toml
34
37
  from dynaconf.vendor import tomllib
35
38
 
36
- if TYPE_CHECKING: # pragma: no cover
37
- from dynaconf.base import Settings # noqa: F401
38
-
39
39
  os.environ["PYTHONIOENCODING"] = "utf-8"
40
40
 
41
41
  CWD = None
@@ -55,6 +55,8 @@ def set_settings(ctx, instance=None):
55
55
  settings = None
56
56
 
57
57
  _echo_enabled = ctx.invoked_subcommand not in ["get", "inspect", None]
58
+ if "--json" in click.get_os_args():
59
+ _echo_enabled = False
58
60
 
59
61
  if instance is not None:
60
62
  if ctx.invoked_subcommand in ["init"]:
@@ -79,19 +81,16 @@ def set_settings(ctx, instance=None):
79
81
  )
80
82
  elif "DJANGO_SETTINGS_MODULE" in os.environ: # pragma: no cover
81
83
  sys.path.insert(0, os.path.abspath(os.getcwd()))
82
- try:
83
- # Django extension v2
84
- from django.conf import settings # noqa
85
- import dynaconf # noqa: F401
86
- import django
84
+ import django # noqa
87
85
 
88
- # see https://docs.djangoproject.com/en/4.2/ref/applications/
89
- # at #troubleshooting
90
- django.setup()
91
-
92
- settings.DYNACONF.configure()
93
- except AttributeError:
94
- settings = LazySettings()
86
+ django.setup() # ensure django is setup to avoid AppRegistryNotReady
87
+ settings_module = import__django_settings(
88
+ os.environ["DJANGO_SETTINGS_MODULE"]
89
+ )
90
+ for member in python_inspect.getmembers(settings_module):
91
+ if isinstance(member[1], (LazySettings, Settings)):
92
+ settings = member[1]
93
+ break
95
94
 
96
95
  if settings is not None and _echo_enabled:
97
96
  click.echo(
@@ -117,6 +116,18 @@ def set_settings(ctx, instance=None):
117
116
  settings = LazySettings()
118
117
 
119
118
 
119
+ def import__django_settings(django_settings_module):
120
+ """Import the Django settings module from the string importable path."""
121
+ try:
122
+ with redirect_stdout(None):
123
+ module = importlib.import_module(django_settings_module)
124
+ except ImportError as e:
125
+ raise click.UsageError(e)
126
+ except FileNotFoundError:
127
+ return
128
+ return module
129
+
130
+
120
131
  def import_settings(dotted_path):
121
132
  """Import settings instance from python dotted path.
122
133
 
@@ -130,8 +141,10 @@ def import_settings(dotted_path):
130
141
  raise click.UsageError(
131
142
  f"invalid path to settings instance: {dotted_path}"
132
143
  )
144
+
133
145
  try:
134
- module = importlib.import_module(module)
146
+ with redirect_stdout(None):
147
+ module = importlib.import_module(module)
135
148
  except ImportError as e:
136
149
  raise click.UsageError(e)
137
150
  except FileNotFoundError:
@@ -485,7 +498,7 @@ def get(key, default, env, unparse):
485
498
  result = unparse_conf_data(result)
486
499
 
487
500
  if isinstance(result, (dict, list, tuple)):
488
- result = json.dumps(result, sort_keys=True)
501
+ result = json.dumps(prepare_json(result), sort_keys=True, default=repr)
489
502
 
490
503
  click.echo(result, nl=False)
491
504
 
@@ -530,7 +543,24 @@ def get(key, default, env, unparse):
530
543
  default=False,
531
544
  help="Output file is flat (do not include [env] name)",
532
545
  )
533
- def _list(env, key, more, loader, _all=False, output=None, flat=False):
546
+ @click.option(
547
+ "--json",
548
+ "_json",
549
+ "-j",
550
+ is_flag=True,
551
+ default=False,
552
+ help="Prints out data serialized as JSON",
553
+ )
554
+ def _list(
555
+ env,
556
+ key,
557
+ more,
558
+ loader,
559
+ _all=False,
560
+ output=None,
561
+ flat=False,
562
+ _json=False,
563
+ ):
534
564
  """
535
565
  Lists user defined settings or all (including internal configs).
536
566
 
@@ -552,14 +582,15 @@ def _list(env, key, more, loader, _all=False, output=None, flat=False):
552
582
  if cur_env == "main":
553
583
  flat = True
554
584
 
555
- click.echo(
556
- click.style(
557
- f"Working in {cur_env} environment ",
558
- bold=True,
559
- bg="bright_blue",
560
- fg="bright_white",
585
+ if not _json:
586
+ click.echo(
587
+ click.style(
588
+ f"Working in {cur_env} environment ",
589
+ bold=True,
590
+ bg="bright_blue",
591
+ fg="bright_white",
592
+ )
561
593
  )
562
- )
563
594
 
564
595
  if not loader:
565
596
  data = settings.as_dict(env=env, internal=_all)
@@ -585,14 +616,20 @@ def _list(env, key, more, loader, _all=False, output=None, flat=False):
585
616
  return f"{key}{data_type} {value}"
586
617
 
587
618
  if not key:
588
- datalines = "\n".join(
589
- format_setting(k, v)
590
- for k, v in data.items()
591
- if k not in data.get("RENAMED_VARS", [])
592
- )
593
- (click.echo_via_pager if more else click.echo)(datalines)
619
+ if not _json:
620
+ datalines = "\n".join(
621
+ format_setting(k, v)
622
+ for k, v in data.items()
623
+ if k not in data.get("RENAMED_VARS", [])
624
+ )
625
+ (click.echo_via_pager if more else click.echo)(datalines)
594
626
  if output:
595
- loaders.write(output, data, env=not flat and cur_env)
627
+ loaders.write(output, prepare_json(data), env=not flat and cur_env)
628
+ if _json:
629
+ json_data = json.dumps(
630
+ prepare_json(data), sort_keys=True, default=repr
631
+ )
632
+ click.echo(json_data, nl=False)
596
633
  else:
597
634
  key = upperfy(key)
598
635
 
@@ -605,9 +642,16 @@ def _list(env, key, more, loader, _all=False, output=None, flat=False):
605
642
  click.secho("Key not found", bg="red", fg="white", err=True)
606
643
  return
607
644
 
608
- click.echo(format_setting(key, value))
645
+ if not _json:
646
+ click.echo(format_setting(key, value))
609
647
  if output:
610
- loaders.write(output, {key: value}, env=not flat and cur_env)
648
+ loaders.write(
649
+ output, prepare_json({key: value}), env=not flat and cur_env
650
+ )
651
+ if _json:
652
+ click.echo(
653
+ json.dumps(prepare_json({key: value}), default=repr), nl=True
654
+ )
611
655
 
612
656
  if env:
613
657
  settings.setenv()
@@ -12,6 +12,7 @@ from dynaconf.loaders import json_loader
12
12
  from dynaconf.loaders import py_loader
13
13
  from dynaconf.loaders import toml_loader
14
14
  from dynaconf.loaders import yaml_loader
15
+ from dynaconf.loaders.base import SourceMetadata
15
16
  from dynaconf.utils import deduplicate
16
17
  from dynaconf.utils import ensure_a_list
17
18
  from dynaconf.utils.boxing import DynaBox
@@ -222,6 +223,7 @@ def settings_loader(
222
223
  key=None,
223
224
  filename=None,
224
225
  validate=False,
226
+ identifier="settings_loader",
225
227
  ):
226
228
  """Loads from defined settings module
227
229
 
@@ -231,6 +233,8 @@ def settings_loader(
231
233
  :param silent: Boolean to raise loading errors
232
234
  :param key: Load a single key if provided
233
235
  :param filename: optional filename to override the settings_module
236
+ :param validate: If True validate the loaded data
237
+ :param identifier: A string or SourceMetadata to identify the loader
234
238
  """
235
239
  if filename is None:
236
240
  settings_module = settings_module or obj.settings_module
@@ -295,6 +299,9 @@ def settings_loader(
295
299
  continue
296
300
 
297
301
  if mod_file.endswith(loader["ext"]):
302
+ if isinstance(identifier, str):
303
+ # ensure it is always loader name
304
+ identifier = loader["name"].lower()
298
305
  loader["loader"].load(
299
306
  obj,
300
307
  filename=mod_file,
@@ -302,6 +309,7 @@ def settings_loader(
302
309
  silent=silent,
303
310
  key=key,
304
311
  validate=validate,
312
+ identifier=identifier,
305
313
  )
306
314
  continue
307
315
 
@@ -314,45 +322,73 @@ def settings_loader(
314
322
 
315
323
  # must be Python file or module
316
324
  # load from default defined module settings.py or .secrets.py if exists
317
- py_loader.load(obj, mod_file, key=key, validate=validate)
325
+ py_loader.load(
326
+ obj, mod_file, key=key, validate=validate, identifier=identifier
327
+ )
318
328
 
319
329
  # load from the current env e.g: development_settings.py
330
+ # counting on the case where env is a comma separated string
320
331
  env = env or obj.current_env
321
- if mod_file.endswith(".py"):
322
- if ".secrets.py" == mod_file:
323
- tmpl = ".{0}_{1}{2}"
324
- mod_file = "secrets.py"
325
- else:
326
- tmpl = "{0}_{1}{2}"
327
-
328
- dirname = os.path.dirname(mod_file)
329
- filename, extension = os.path.splitext(os.path.basename(mod_file))
330
- new_filename = tmpl.format(env.lower(), filename, extension)
331
- env_mod_file = os.path.join(dirname, new_filename)
332
- global_filename = tmpl.format("global", filename, extension)
333
- global_mod_file = os.path.join(dirname, global_filename)
332
+ if env and isinstance(env, str):
333
+ for env_name in env.split(","):
334
+ load_from_env_named_file(
335
+ obj, env_name, key, validate, identifier, mod_file
336
+ )
337
+
338
+
339
+ def load_from_env_named_file(obj, env, key, validate, identifier, mod_file):
340
+ """Load from env named file e.g: development_settings.py"""
341
+ if mod_file.endswith(".py"):
342
+ if ".secrets.py" == mod_file:
343
+ tmpl = ".{0}_{1}{2}"
344
+ mod_file = "secrets.py"
334
345
  else:
335
- env_mod_file = f"{env.lower()}_{mod_file}"
336
- global_mod_file = f"global_{mod_file}"
346
+ tmpl = "{0}_{1}{2}"
347
+
348
+ dirname = os.path.dirname(mod_file)
349
+ filename, extension = os.path.splitext(os.path.basename(mod_file))
350
+ new_filename = tmpl.format(env.lower(), filename, extension)
351
+ env_mod_file = os.path.join(dirname, new_filename)
352
+ global_filename = tmpl.format("global", filename, extension)
353
+ global_mod_file = os.path.join(dirname, global_filename)
354
+ else:
355
+ parts = mod_file.rsplit(".", 1)
356
+ if len(parts) > 1:
357
+ head, tail = parts
358
+ else:
359
+ head, tail = None, parts[0]
360
+ tail = env_mod_file = f"{env.lower()}_{tail}"
337
361
 
338
- py_loader.load(
339
- obj,
340
- env_mod_file,
341
- identifier=f"py_{env.upper()}",
342
- silent=True,
343
- key=key,
344
- validate=validate,
345
- )
362
+ if head:
363
+ env_mod_file = f"{head}.{tail}"
364
+ global_mod_file = f"{head}.global_{tail}"
365
+ else:
366
+ env_mod_file = tail
367
+ global_mod_file = f"global_{tail}"
346
368
 
347
- # load from global_settings.py
348
- py_loader.load(
349
- obj,
350
- global_mod_file,
351
- identifier="py_global",
352
- silent=True,
353
- key=key,
354
- validate=validate,
355
- )
369
+ source_metadata = SourceMetadata(
370
+ loader="py",
371
+ identifier=identifier,
372
+ env=env,
373
+ )
374
+ py_loader.load(
375
+ obj,
376
+ env_mod_file,
377
+ identifier=source_metadata,
378
+ silent=True,
379
+ key=key,
380
+ validate=validate,
381
+ )
382
+
383
+ # load from global_settings.py
384
+ py_loader.load(
385
+ obj,
386
+ global_mod_file,
387
+ identifier="py_global",
388
+ silent=True,
389
+ key=key,
390
+ validate=validate,
391
+ )
356
392
 
357
393
 
358
394
  def enable_external_loaders(obj):
@@ -15,34 +15,50 @@ with suppress(ImportError, FileNotFoundError):
15
15
 
16
16
  DOTENV_IMPORTED = True
17
17
 
18
- IDENTIFIER = "env"
18
+ IDENTIFIER_PREFIX = "env"
19
19
 
20
20
 
21
- def load(obj, env=None, silent=True, key=None, validate=False):
21
+ def load(
22
+ obj, env=None, silent=True, key=None, validate=False, identifier="global"
23
+ ):
22
24
  """Loads envvars with prefixes:
23
25
 
24
26
  `DYNACONF_` (default global) or `$(ENVVAR_PREFIX_FOR_DYNACONF)_`
27
+
28
+ if envvar_prefix is set to:
29
+ str: -> load {str}_*
30
+ str,str1 -> load [{str}_, {str1}_]
31
+ False -> load *
32
+ None -> return not loading anything
33
+
25
34
  """
26
35
  global_prefix = obj.get("ENVVAR_PREFIX_FOR_DYNACONF")
36
+ if global_prefix is None:
37
+ return
38
+
27
39
  if global_prefix is False or global_prefix.upper() != "DYNACONF":
28
40
  load_from_env(
29
41
  obj,
30
42
  "DYNACONF",
31
43
  key,
32
44
  silent,
33
- IDENTIFIER + "_global",
45
+ f"{IDENTIFIER_PREFIX}_{identifier}",
34
46
  validate=validate,
35
47
  )
36
48
 
37
49
  # Load the global env if exists and overwrite everything
38
- load_from_env(
39
- obj,
40
- global_prefix,
41
- key,
42
- silent,
43
- IDENTIFIER + "_global",
44
- validate=validate,
45
- )
50
+ # if the prefix is separated by comma then load all prefixes
51
+ # counting on the case where global_prefix is set to None, False or ""
52
+ prefixes = global_prefix.split(",") if global_prefix else [global_prefix]
53
+ for prefix in prefixes:
54
+ load_from_env(
55
+ obj,
56
+ prefix,
57
+ key,
58
+ silent,
59
+ f"{IDENTIFIER_PREFIX}_{identifier}",
60
+ validate=validate,
61
+ )
46
62
 
47
63
 
48
64
  def load_from_env(
@@ -50,7 +66,7 @@ def load_from_env(
50
66
  prefix=False,
51
67
  key=None,
52
68
  silent=False,
53
- identifier=IDENTIFIER,
69
+ identifier=IDENTIFIER_PREFIX,
54
70
  env=False, # backwards compatibility bc renamed param
55
71
  validate=False,
56
72
  ):
@@ -66,7 +82,7 @@ def load_from_env(
66
82
  env_ = f"{prefix}_"
67
83
 
68
84
  # set source metadata
69
- source_metadata = SourceMetadata(identifier, "unique", "global")
85
+ source_metadata = SourceMetadata(identifier, prefix, "global")
70
86
 
71
87
  # Load a single environment variable explicitly.
72
88
  if key:
@@ -5,6 +5,7 @@ from pathlib import Path
5
5
  from dynaconf import default_settings
6
6
  from dynaconf.constants import INI_EXTENSIONS
7
7
  from dynaconf.loaders.base import BaseLoader
8
+ from dynaconf.loaders.base import SourceMetadata
8
9
  from dynaconf.utils import object_merge
9
10
 
10
11
  try:
@@ -13,7 +14,15 @@ except ImportError: # pragma: no cover
13
14
  ConfigObj = None
14
15
 
15
16
 
16
- def load(obj, env=None, silent=True, key=None, filename=None, validate=False):
17
+ def load(
18
+ obj,
19
+ env=None,
20
+ silent=True,
21
+ key=None,
22
+ filename=None,
23
+ validate=False,
24
+ identifier="ini",
25
+ ):
17
26
  """
18
27
  Reads and loads in to "obj" a single key or all keys from source file.
19
28
 
@@ -28,10 +37,16 @@ def load(obj, env=None, silent=True, key=None, filename=None, validate=False):
28
37
  BaseLoader.warn_not_installed(obj, "ini")
29
38
  return
30
39
 
40
+ # when load_file function is called directly it comes with module and line number
41
+ if isinstance(identifier, SourceMetadata) and identifier.loader.startswith(
42
+ "load_file"
43
+ ):
44
+ identifier = identifier.loader
45
+
31
46
  loader = BaseLoader(
32
47
  obj=obj,
33
48
  env=env,
34
- identifier="ini",
49
+ identifier=identifier,
35
50
  extensions=INI_EXTENSIONS,
36
51
  file_reader=lambda fileobj: ConfigObj(fileobj).dict(),
37
52
  string_reader=lambda strobj: ConfigObj(strobj.split("\n")).dict(),
@@ -6,6 +6,7 @@ from pathlib import Path
6
6
  from dynaconf import default_settings
7
7
  from dynaconf.constants import JSON_EXTENSIONS
8
8
  from dynaconf.loaders.base import BaseLoader
9
+ from dynaconf.loaders.base import SourceMetadata
9
10
  from dynaconf.utils import object_merge
10
11
  from dynaconf.utils.parse_conf import try_to_encode
11
12
 
@@ -15,7 +16,15 @@ except ImportError: # pragma: no cover
15
16
  commentjson = None
16
17
 
17
18
 
18
- def load(obj, env=None, silent=True, key=None, filename=None, validate=False):
19
+ def load(
20
+ obj,
21
+ env=None,
22
+ silent=True,
23
+ key=None,
24
+ filename=None,
25
+ validate=False,
26
+ identifier="json",
27
+ ):
19
28
  """
20
29
  Reads and loads in to "obj" a single key or all keys from source file.
21
30
 
@@ -35,10 +44,16 @@ def load(obj, env=None, silent=True, key=None, filename=None, validate=False):
35
44
  file_reader = json.load
36
45
  string_reader = json.loads
37
46
 
47
+ # when load_file function is called directly it comes with module and line number
48
+ if isinstance(identifier, SourceMetadata) and identifier.loader.startswith(
49
+ "load_file"
50
+ ):
51
+ identifier = identifier.loader
52
+
38
53
  loader = BaseLoader(
39
54
  obj=obj,
40
55
  env=env,
41
- identifier="json",
56
+ identifier=identifier,
42
57
  extensions=JSON_EXTENSIONS,
43
58
  file_reader=file_reader,
44
59
  string_reader=string_reader,
@@ -37,7 +37,12 @@ def load(
37
37
  return
38
38
 
39
39
  # setup SourceMetadata (for inspecting)
40
- loader_identifier = SourceMetadata(identifier, mod.__name__, "global")
40
+ if isinstance(identifier, SourceMetadata):
41
+ loader_identifier = SourceMetadata(
42
+ identifier.loader, mod.__name__, identifier.env
43
+ )
44
+ else:
45
+ loader_identifier = SourceMetadata(identifier, mod.__name__, "global")
41
46
 
42
47
  load_from_python_object(
43
48
  obj, mod, settings_module, key, loader_identifier, validate=validate
@@ -88,7 +93,10 @@ def try_to_load_from_py_module_name(
88
93
  ctx = suppress(ImportError, TypeError) if silent else suppress()
89
94
 
90
95
  # setup SourceMetadata (for inspecting)
91
- loader_identifier = SourceMetadata(identifier, name, "global")
96
+ if isinstance(identifier, SourceMetadata):
97
+ loader_identifier = identifier
98
+ else:
99
+ loader_identifier = SourceMetadata(identifier, name, "global")
92
100
 
93
101
  with ctx:
94
102
  mod = importlib.import_module(str(name))