dynaconf 3.2.5__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 (155) hide show
  1. {dynaconf-3.2.5 → dynaconf-3.2.7}/CHANGELOG.md +23 -0
  2. {dynaconf-3.2.5 → dynaconf-3.2.7}/PKG-INFO +9 -31
  3. dynaconf-3.2.7/dynaconf/VERSION +1 -0
  4. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/base.py +34 -3
  5. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/cli.py +79 -35
  6. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/hooking.py +48 -11
  7. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/loaders/__init__.py +73 -36
  8. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/loaders/env_loader.py +29 -13
  9. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/loaders/ini_loader.py +17 -2
  10. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/loaders/json_loader.py +17 -2
  11. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/loaders/py_loader.py +10 -2
  12. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/loaders/redis_loader.py +9 -2
  13. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/loaders/toml_loader.py +17 -3
  14. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/loaders/yaml_loader.py +17 -2
  15. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/utils/__init__.py +50 -7
  16. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/utils/boxing.py +7 -0
  17. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/utils/inspect.py +9 -2
  18. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/utils/parse_conf.py +60 -7
  19. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/validator.py +25 -7
  20. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/vendor/click/_termui_impl.py +3 -3
  21. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/vendor/click/core.py +1 -1
  22. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/vendor/click/types.py +1 -1
  23. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/vendor/ruamel/yaml/compat.py +1 -1
  24. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/vendor/ruamel/yaml/error.py +6 -7
  25. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/vendor/ruamel/yaml/events.py +6 -11
  26. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/vendor/ruamel/yaml/resolver.py +3 -3
  27. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/vendor/ruamel/yaml/scalarstring.py +2 -3
  28. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/vendor/ruamel/yaml/tokens.py +18 -19
  29. dynaconf-3.2.7/dynaconf/vendor/tomllib/_types.py +4 -0
  30. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/vendor/tomllib/_writer.py +1 -1
  31. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf.egg-info/PKG-INFO +9 -31
  32. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf.egg-info/SOURCES.txt +0 -24
  33. dynaconf-3.2.5/dynaconf/VERSION +0 -1
  34. dynaconf-3.2.5/dynaconf/vendor/tomllib/_types.py +0 -4
  35. dynaconf-3.2.5/tests/test_base.py +0 -1510
  36. dynaconf-3.2.5/tests/test_basic.py +0 -7
  37. dynaconf-3.2.5/tests/test_cli.py +0 -881
  38. dynaconf-3.2.5/tests/test_compat.py +0 -56
  39. dynaconf-3.2.5/tests/test_django.py +0 -44
  40. dynaconf-3.2.5/tests/test_dynabox.py +0 -103
  41. dynaconf-3.2.5/tests/test_endtoend.py +0 -67
  42. dynaconf-3.2.5/tests/test_env_loader.py +0 -523
  43. dynaconf-3.2.5/tests/test_envvar_prefix.py +0 -46
  44. dynaconf-3.2.5/tests/test_feature_flag.py +0 -34
  45. dynaconf-3.2.5/tests/test_flask.py +0 -165
  46. dynaconf-3.2.5/tests/test_hooking.py +0 -268
  47. dynaconf-3.2.5/tests/test_ini_loader.py +0 -208
  48. dynaconf-3.2.5/tests/test_inspect.py +0 -823
  49. dynaconf-3.2.5/tests/test_json_loader.py +0 -230
  50. dynaconf-3.2.5/tests/test_nested_loading.py +0 -385
  51. dynaconf-3.2.5/tests/test_py_loader.py +0 -186
  52. dynaconf-3.2.5/tests/test_redis.py +0 -124
  53. dynaconf-3.2.5/tests/test_toml_loader.py +0 -256
  54. dynaconf-3.2.5/tests/test_utils.py +0 -538
  55. dynaconf-3.2.5/tests/test_validators.py +0 -887
  56. dynaconf-3.2.5/tests/test_validators_conditions.py +0 -64
  57. dynaconf-3.2.5/tests/test_vault.py +0 -131
  58. dynaconf-3.2.5/tests/test_yaml_loader.py +0 -580
  59. {dynaconf-3.2.5 → dynaconf-3.2.7}/3.x-release-notes.md +0 -0
  60. {dynaconf-3.2.5 → dynaconf-3.2.7}/CONTRIBUTING.md +0 -0
  61. {dynaconf-3.2.5 → dynaconf-3.2.7}/CONTRIBUTORS.md +0 -0
  62. {dynaconf-3.2.5 → dynaconf-3.2.7}/LICENSE +0 -0
  63. {dynaconf-3.2.5 → dynaconf-3.2.7}/MANIFEST.in +0 -0
  64. {dynaconf-3.2.5 → dynaconf-3.2.7}/README.md +0 -0
  65. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/__init__.py +0 -0
  66. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/constants.py +0 -0
  67. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/contrib/__init__.py +0 -0
  68. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/contrib/django_dynaconf_v2.py +0 -0
  69. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/contrib/flask_dynaconf.py +0 -0
  70. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/default_settings.py +0 -0
  71. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/loaders/base.py +0 -0
  72. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/loaders/vault_loader.py +0 -0
  73. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/strategies/__init__.py +0 -0
  74. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/strategies/filtering.py +0 -0
  75. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/test_settings.py +0 -0
  76. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/utils/files.py +0 -0
  77. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/utils/functional.py +0 -0
  78. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/validator_conditions.py +0 -0
  79. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/vendor/__init__.py +0 -0
  80. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/vendor/box/__init__.py +0 -0
  81. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/vendor/box/box.py +0 -0
  82. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/vendor/box/box_list.py +0 -0
  83. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/vendor/box/config_box.py +0 -0
  84. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/vendor/box/converters.py +0 -0
  85. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/vendor/box/exceptions.py +0 -0
  86. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/vendor/box/from_file.py +0 -0
  87. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/vendor/box/shorthand_box.py +0 -0
  88. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/vendor/click/__init__.py +0 -0
  89. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/vendor/click/_bashcomplete.py +0 -0
  90. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/vendor/click/_compat.py +0 -0
  91. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/vendor/click/_textwrap.py +0 -0
  92. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/vendor/click/_unicodefun.py +0 -0
  93. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/vendor/click/_winconsole.py +0 -0
  94. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/vendor/click/decorators.py +0 -0
  95. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/vendor/click/exceptions.py +0 -0
  96. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/vendor/click/formatting.py +0 -0
  97. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/vendor/click/globals.py +0 -0
  98. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/vendor/click/parser.py +0 -0
  99. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/vendor/click/termui.py +0 -0
  100. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/vendor/click/testing.py +0 -0
  101. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/vendor/click/utils.py +0 -0
  102. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/vendor/dotenv/__init__.py +0 -0
  103. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/vendor/dotenv/cli.py +0 -0
  104. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/vendor/dotenv/compat.py +0 -0
  105. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/vendor/dotenv/ipython.py +0 -0
  106. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/vendor/dotenv/main.py +0 -0
  107. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/vendor/dotenv/parser.py +0 -0
  108. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/vendor/dotenv/version.py +0 -0
  109. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/vendor/ruamel/__init__.py +0 -0
  110. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/vendor/ruamel/yaml/__init__.py +0 -0
  111. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/vendor/ruamel/yaml/anchor.py +0 -0
  112. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/vendor/ruamel/yaml/comments.py +0 -0
  113. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/vendor/ruamel/yaml/composer.py +0 -0
  114. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/vendor/ruamel/yaml/configobjwalker.py +0 -0
  115. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/vendor/ruamel/yaml/constructor.py +0 -0
  116. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/vendor/ruamel/yaml/cyaml.py +0 -0
  117. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/vendor/ruamel/yaml/dumper.py +0 -0
  118. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/vendor/ruamel/yaml/emitter.py +0 -0
  119. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/vendor/ruamel/yaml/loader.py +0 -0
  120. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/vendor/ruamel/yaml/main.py +0 -0
  121. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/vendor/ruamel/yaml/nodes.py +0 -0
  122. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/vendor/ruamel/yaml/parser.py +0 -0
  123. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/vendor/ruamel/yaml/reader.py +0 -0
  124. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/vendor/ruamel/yaml/representer.py +0 -0
  125. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/vendor/ruamel/yaml/scalarbool.py +0 -0
  126. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/vendor/ruamel/yaml/scalarfloat.py +0 -0
  127. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/vendor/ruamel/yaml/scalarint.py +0 -0
  128. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/vendor/ruamel/yaml/scanner.py +0 -0
  129. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/vendor/ruamel/yaml/serializer.py +0 -0
  130. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/vendor/ruamel/yaml/setup.py +0 -0
  131. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/vendor/ruamel/yaml/timestamp.py +0 -0
  132. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/vendor/ruamel/yaml/util.py +0 -0
  133. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/vendor/toml/__init__.py +0 -0
  134. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/vendor/toml/decoder.py +0 -0
  135. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/vendor/toml/encoder.py +0 -0
  136. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/vendor/toml/ordered.py +0 -0
  137. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/vendor/toml/tz.py +0 -0
  138. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/vendor/tomllib/__init__.py +0 -0
  139. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/vendor/tomllib/_parser.py +0 -0
  140. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf/vendor/tomllib/_re.py +0 -0
  141. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf.egg-info/dependency_links.txt +0 -0
  142. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf.egg-info/entry_points.txt +0 -0
  143. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf.egg-info/not-zip-safe +0 -0
  144. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf.egg-info/requires.txt +0 -0
  145. {dynaconf-3.2.5 → dynaconf-3.2.7}/dynaconf.egg-info/top_level.txt +0 -0
  146. {dynaconf-3.2.5 → dynaconf-3.2.7}/setup.cfg +0 -0
  147. {dynaconf-3.2.5 → dynaconf-3.2.7}/setup.py +0 -0
  148. {dynaconf-3.2.5 → dynaconf-3.2.7}/vendor_licenses/box-LICENSE.txt +0 -0
  149. {dynaconf-3.2.5 → dynaconf-3.2.7}/vendor_licenses/click-LICENSE.rst +0 -0
  150. {dynaconf-3.2.5 → dynaconf-3.2.7}/vendor_licenses/licenses.sh +0 -0
  151. {dynaconf-3.2.5 → dynaconf-3.2.7}/vendor_licenses/python-dotenv-LICENSE.txt +0 -0
  152. {dynaconf-3.2.5 → dynaconf-3.2.7}/vendor_licenses/ruamel.yaml-LICENSE.txt +0 -0
  153. {dynaconf-3.2.5 → dynaconf-3.2.7}/vendor_licenses/toml-LICENSE.txt +0 -0
  154. {dynaconf-3.2.5 → dynaconf-3.2.7}/vendor_licenses/tomli-LICENSE.txt +0 -0
  155. {dynaconf-3.2.5 → dynaconf-3.2.7}/vendor_licenses/vendor_versions.txt +0 -0
@@ -2,6 +2,29 @@ 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
+
26
+ ## [3.2.6](https://github.com/dynaconf/dynaconf/releases/tag/3.2.6) - 2024-07-19
27
+
5
28
  ## [3.2.5](https://github.com/pedro-psb/dynaconf/releases/tag/3.2.5) - 2024-03-18
6
29
 
7
30
  ### Bug Fixes
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dynaconf
3
- Version: 3.2.5
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
@@ -26,6 +26,14 @@ Classifier: Topic :: Software Development :: Libraries
26
26
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
27
27
  Requires-Python: >=3.8
28
28
  Description-Content-Type: text/markdown
29
+ Provides-Extra: redis
30
+ Provides-Extra: vault
31
+ Provides-Extra: yaml
32
+ Provides-Extra: toml
33
+ Provides-Extra: ini
34
+ Provides-Extra: configobj
35
+ Provides-Extra: all
36
+ Provides-Extra: test
29
37
  License-File: LICENSE
30
38
  License-File: vendor_licenses/box-LICENSE.txt
31
39
  License-File: vendor_licenses/click-LICENSE.rst
@@ -35,36 +43,6 @@ License-File: vendor_licenses/ruamel.yaml-LICENSE.txt
35
43
  License-File: vendor_licenses/toml-LICENSE.txt
36
44
  License-File: vendor_licenses/tomli-LICENSE.txt
37
45
  License-File: vendor_licenses/vendor_versions.txt
38
- Provides-Extra: redis
39
- Requires-Dist: redis; extra == "redis"
40
- Provides-Extra: vault
41
- Requires-Dist: hvac; extra == "vault"
42
- Provides-Extra: yaml
43
- Requires-Dist: ruamel.yaml; extra == "yaml"
44
- Provides-Extra: toml
45
- Requires-Dist: toml; extra == "toml"
46
- Provides-Extra: ini
47
- Requires-Dist: configobj; extra == "ini"
48
- Provides-Extra: configobj
49
- Requires-Dist: configobj; extra == "configobj"
50
- Provides-Extra: all
51
- Requires-Dist: redis; extra == "all"
52
- Requires-Dist: ruamel.yaml; extra == "all"
53
- Requires-Dist: configobj; extra == "all"
54
- Requires-Dist: hvac; extra == "all"
55
- Provides-Extra: test
56
- Requires-Dist: pytest; extra == "test"
57
- Requires-Dist: pytest-cov; extra == "test"
58
- Requires-Dist: pytest-xdist; extra == "test"
59
- Requires-Dist: pytest-mock; extra == "test"
60
- Requires-Dist: radon; extra == "test"
61
- Requires-Dist: flask>=0.12; extra == "test"
62
- Requires-Dist: django; extra == "test"
63
- Requires-Dist: python-dotenv; extra == "test"
64
- Requires-Dist: toml; extra == "test"
65
- Requires-Dist: redis; extra == "test"
66
- Requires-Dist: hvac>=1.1.0; extra == "test"
67
- Requires-Dist: configobj; extra == "test"
68
46
 
69
47
  <!-- [![Dynaconf](docs/img/logo_400.svg?sanitize=true)](http://dynaconf.com) -->
70
48
 
@@ -0,0 +1 @@
1
+ 3.2.7
@@ -229,7 +229,7 @@ class Settings:
229
229
  self._loaded_py_modules = []
230
230
  self._loaded_files = []
231
231
  self._deleted = set()
232
- self._store = DynaBox(box_settings=self)
232
+ self._store = kwargs.pop("_store", DynaBox(box_settings=self))
233
233
  self._env_cache = {}
234
234
  self._loaded_by_loaders: dict[SourceMetadata | str, Any] = {}
235
235
  self._loaders = []
@@ -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()
@@ -6,7 +6,6 @@ from functools import wraps
6
6
  from typing import Any
7
7
  from typing import Callable
8
8
 
9
- from dynaconf.base import RESERVED_ATTRS
10
9
  from dynaconf.base import Settings
11
10
  from dynaconf.loaders.base import SourceMetadata
12
11
 
@@ -77,17 +76,9 @@ def hookable(function=None, name=None):
77
76
  ):
78
77
  return fun(self, *args, **kwargs)
79
78
 
80
- # Create an unhookable (to avoid recursion)
79
+ # Create an unhook-able (to avoid recursion)
81
80
  # temporary settings to pass to the hooked function
82
- temp_settings = Settings(
83
- dynaconf_skip_loaders=True,
84
- dynaconf_skip_validators=True,
85
- )
86
- allowed_keys = self.__dict__.keys() - set(RESERVED_ATTRS)
87
- temp_data = {
88
- k: v for k, v in self.__dict__.items() if k in allowed_keys
89
- }
90
- temp_settings._store.update(temp_data)
81
+ temp_settings = TempSettingsHolder(self)
91
82
 
92
83
  def _hook(action: str, value: HookValue) -> HookValue:
93
84
  """executes the hooks for the given action"""
@@ -304,3 +295,49 @@ class HookableSettings(Settings):
304
295
  @hookable
305
296
  def get(self, *args, **kwargs):
306
297
  return Settings.get(self, *args, **kwargs)
298
+
299
+
300
+ class TempSettingsHolder:
301
+ """Holds settings to be passed down to hooks.
302
+ To save runtime resources initialize a copy of it only if accessed.
303
+ """
304
+
305
+ _settings = None
306
+
307
+ def __init__(self, settings):
308
+ self._original_settings = settings
309
+
310
+ def _initialize(self):
311
+ if self._settings is None:
312
+ self._settings = Settings(
313
+ dynaconf_skip_loaders=True,
314
+ dynaconf_skip_validators=True,
315
+ _store=self._original_settings._store._safe_copy(),
316
+ )
317
+
318
+ def __getattr__(self, attr):
319
+ self._initialize()
320
+ return getattr(self._settings, attr)
321
+
322
+ def __getitem__(self, item):
323
+ self._initialize()
324
+ return self._settings[item]
325
+
326
+ def __setitem__(self, item, value):
327
+ self._initialize()
328
+ self._settings[item] = value
329
+
330
+ def __iter__(self):
331
+ self._initialize()
332
+ return iter(self._settings)
333
+
334
+ def __contains__(self, item):
335
+ self._initialize()
336
+ return item in self._settings
337
+
338
+ def __setattr__(self, attr, value):
339
+ if attr in ["_original_settings", "_settings"]:
340
+ super().__setattr__(attr, value)
341
+ else:
342
+ self._initialize()
343
+ setattr(self._settings, attr, value)
@@ -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
@@ -126,12 +127,13 @@ def execute_module_hooks(
126
127
  hook_file = os.path.join(
127
128
  os.path.dirname(loaded_file), "dynaconf_hooks.py"
128
129
  )
130
+ if not os.path.exists(hook_file):
131
+ # Return early if file doesn't exist.
132
+ # Faster than attempting to import.
133
+ continue
129
134
  hook_module = py_loader.import_from_filename(
130
135
  obj, hook_file, silent=silent
131
136
  )
132
- if not hook_module:
133
- # There was no hook on the same path as a python file
134
- continue
135
137
  _run_hook_module(
136
138
  hook_type=hook,
137
139
  hook_module=hook_module,
@@ -221,6 +223,7 @@ def settings_loader(
221
223
  key=None,
222
224
  filename=None,
223
225
  validate=False,
226
+ identifier="settings_loader",
224
227
  ):
225
228
  """Loads from defined settings module
226
229
 
@@ -230,6 +233,8 @@ def settings_loader(
230
233
  :param silent: Boolean to raise loading errors
231
234
  :param key: Load a single key if provided
232
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
233
238
  """
234
239
  if filename is None:
235
240
  settings_module = settings_module or obj.settings_module
@@ -294,6 +299,9 @@ def settings_loader(
294
299
  continue
295
300
 
296
301
  if mod_file.endswith(loader["ext"]):
302
+ if isinstance(identifier, str):
303
+ # ensure it is always loader name
304
+ identifier = loader["name"].lower()
297
305
  loader["loader"].load(
298
306
  obj,
299
307
  filename=mod_file,
@@ -301,6 +309,7 @@ def settings_loader(
301
309
  silent=silent,
302
310
  key=key,
303
311
  validate=validate,
312
+ identifier=identifier,
304
313
  )
305
314
  continue
306
315
 
@@ -313,45 +322,73 @@ def settings_loader(
313
322
 
314
323
  # must be Python file or module
315
324
  # load from default defined module settings.py or .secrets.py if exists
316
- 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
+ )
317
328
 
318
329
  # load from the current env e.g: development_settings.py
330
+ # counting on the case where env is a comma separated string
319
331
  env = env or obj.current_env
320
- if mod_file.endswith(".py"):
321
- if ".secrets.py" == mod_file:
322
- tmpl = ".{0}_{1}{2}"
323
- mod_file = "secrets.py"
324
- else:
325
- tmpl = "{0}_{1}{2}"
326
-
327
- dirname = os.path.dirname(mod_file)
328
- filename, extension = os.path.splitext(os.path.basename(mod_file))
329
- new_filename = tmpl.format(env.lower(), filename, extension)
330
- env_mod_file = os.path.join(dirname, new_filename)
331
- global_filename = tmpl.format("global", filename, extension)
332
- 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"
333
345
  else:
334
- env_mod_file = f"{env.lower()}_{mod_file}"
335
- 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}"
336
361
 
337
- py_loader.load(
338
- obj,
339
- env_mod_file,
340
- identifier=f"py_{env.upper()}",
341
- silent=True,
342
- key=key,
343
- validate=validate,
344
- )
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}"
345
368
 
346
- # load from global_settings.py
347
- py_loader.load(
348
- obj,
349
- global_mod_file,
350
- identifier="py_global",
351
- silent=True,
352
- key=key,
353
- validate=validate,
354
- )
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
+ )
355
392
 
356
393
 
357
394
  def enable_external_loaders(obj):