dynaconf 3.3.2__tar.gz → 3.3.3__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 (172) hide show
  1. {dynaconf-3.3.2 → dynaconf-3.3.3}/CHANGELOG.md +25 -0
  2. {dynaconf-3.3.2 → dynaconf-3.3.3}/PKG-INFO +1 -1
  3. {dynaconf-3.3.2 → dynaconf-3.3.3}/RELEASING.md +1 -0
  4. dynaconf-3.3.3/dynaconf/VERSION +1 -0
  5. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/base.py +3 -0
  6. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/nodes.py +6 -0
  7. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/validator.py +35 -19
  8. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf.egg-info/PKG-INFO +1 -1
  9. {dynaconf-3.3.2 → dynaconf-3.3.3}/pyproject.toml +1 -1
  10. {dynaconf-3.3.2 → dynaconf-3.3.3}/tests/test_utils.py +81 -75
  11. dynaconf-3.3.2/dynaconf/VERSION +0 -1
  12. {dynaconf-3.3.2 → dynaconf-3.3.3}/CONTRIBUTING.md +0 -0
  13. {dynaconf-3.3.2 → dynaconf-3.3.3}/CONTRIBUTORS.md +0 -0
  14. {dynaconf-3.3.2 → dynaconf-3.3.3}/LICENSE +0 -0
  15. {dynaconf-3.3.2 → dynaconf-3.3.3}/MANIFEST.in +0 -0
  16. {dynaconf-3.3.2 → dynaconf-3.3.3}/README.md +0 -0
  17. {dynaconf-3.3.2 → dynaconf-3.3.3}/SECURITY.md +0 -0
  18. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/__init__.py +0 -0
  19. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/__main__.py +0 -0
  20. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/cli.py +0 -0
  21. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/constants.py +0 -0
  22. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/contrib/__init__.py +0 -0
  23. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/contrib/django_dynaconf_v2.py +0 -0
  24. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/contrib/flask_dynaconf.py +0 -0
  25. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/default_settings.py +0 -0
  26. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/hooking.py +0 -0
  27. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/loaders/__init__.py +0 -0
  28. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/loaders/base.py +0 -0
  29. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/loaders/env_loader.py +0 -0
  30. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/loaders/ini_loader.py +0 -0
  31. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/loaders/json_loader.py +0 -0
  32. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/loaders/py_loader.py +0 -0
  33. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/loaders/redis_loader.py +0 -0
  34. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/loaders/toml_loader.py +0 -0
  35. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/loaders/vault_loader.py +0 -0
  36. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/loaders/yaml_loader.py +0 -0
  37. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/strategies/__init__.py +0 -0
  38. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/strategies/filtering.py +0 -0
  39. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/test_settings.py +0 -0
  40. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/typed/__init__.py +0 -0
  41. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/typed/compat.py +0 -0
  42. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/typed/exceptions.py +0 -0
  43. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/typed/guards.py +0 -0
  44. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/typed/main.py +0 -0
  45. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/typed/types.py +0 -0
  46. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/typed/utils.py +0 -0
  47. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/typed/validators.py +0 -0
  48. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/utils/__init__.py +0 -0
  49. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/utils/boxing.py +0 -0
  50. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/utils/files.py +0 -0
  51. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/utils/functional.py +0 -0
  52. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/utils/inspect.py +0 -0
  53. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/utils/parse_conf.py +0 -0
  54. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/validator_conditions.py +0 -0
  55. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/vendor/__init__.py +0 -0
  56. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/vendor/box/__init__.py +0 -0
  57. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/vendor/box/box.py +0 -0
  58. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/vendor/box/box_list.py +0 -0
  59. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/vendor/box/config_box.py +0 -0
  60. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/vendor/box/converters.py +0 -0
  61. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/vendor/box/exceptions.py +0 -0
  62. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/vendor/box/from_file.py +0 -0
  63. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/vendor/box/shorthand_box.py +0 -0
  64. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/vendor/click/__init__.py +0 -0
  65. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/vendor/click/_bashcomplete.py +0 -0
  66. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/vendor/click/_compat.py +0 -0
  67. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/vendor/click/_termui_impl.py +0 -0
  68. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/vendor/click/_textwrap.py +0 -0
  69. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/vendor/click/_unicodefun.py +0 -0
  70. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/vendor/click/_winconsole.py +0 -0
  71. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/vendor/click/core.py +0 -0
  72. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/vendor/click/decorators.py +0 -0
  73. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/vendor/click/exceptions.py +0 -0
  74. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/vendor/click/formatting.py +0 -0
  75. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/vendor/click/globals.py +0 -0
  76. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/vendor/click/parser.py +0 -0
  77. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/vendor/click/termui.py +0 -0
  78. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/vendor/click/testing.py +0 -0
  79. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/vendor/click/types.py +0 -0
  80. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/vendor/click/utils.py +0 -0
  81. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/vendor/dotenv/__init__.py +0 -0
  82. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/vendor/dotenv/cli.py +0 -0
  83. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/vendor/dotenv/compat.py +0 -0
  84. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/vendor/dotenv/ipython.py +0 -0
  85. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/vendor/dotenv/main.py +0 -0
  86. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/vendor/dotenv/parser.py +0 -0
  87. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/vendor/dotenv/py.typed +0 -0
  88. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/vendor/dotenv/version.py +0 -0
  89. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/vendor/ruamel/__init__.py +0 -0
  90. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/vendor/ruamel/yaml/__init__.py +0 -0
  91. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/vendor/ruamel/yaml/anchor.py +0 -0
  92. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/vendor/ruamel/yaml/comments.py +0 -0
  93. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/vendor/ruamel/yaml/compat.py +0 -0
  94. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/vendor/ruamel/yaml/composer.py +0 -0
  95. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/vendor/ruamel/yaml/configobjwalker.py +0 -0
  96. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/vendor/ruamel/yaml/constructor.py +0 -0
  97. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/vendor/ruamel/yaml/cyaml.py +0 -0
  98. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/vendor/ruamel/yaml/dumper.py +0 -0
  99. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/vendor/ruamel/yaml/emitter.py +0 -0
  100. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/vendor/ruamel/yaml/error.py +0 -0
  101. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/vendor/ruamel/yaml/events.py +0 -0
  102. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/vendor/ruamel/yaml/loader.py +0 -0
  103. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/vendor/ruamel/yaml/main.py +0 -0
  104. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/vendor/ruamel/yaml/nodes.py +0 -0
  105. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/vendor/ruamel/yaml/parser.py +0 -0
  106. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/vendor/ruamel/yaml/py.typed +0 -0
  107. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/vendor/ruamel/yaml/reader.py +0 -0
  108. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/vendor/ruamel/yaml/representer.py +0 -0
  109. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/vendor/ruamel/yaml/resolver.py +0 -0
  110. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/vendor/ruamel/yaml/scalarbool.py +0 -0
  111. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/vendor/ruamel/yaml/scalarfloat.py +0 -0
  112. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/vendor/ruamel/yaml/scalarint.py +0 -0
  113. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/vendor/ruamel/yaml/scalarstring.py +0 -0
  114. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/vendor/ruamel/yaml/scanner.py +0 -0
  115. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/vendor/ruamel/yaml/serializer.py +0 -0
  116. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/vendor/ruamel/yaml/setup.py +0 -0
  117. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/vendor/ruamel/yaml/timestamp.py +0 -0
  118. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/vendor/ruamel/yaml/tokens.py +0 -0
  119. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/vendor/ruamel/yaml/util.py +0 -0
  120. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/vendor/toml/__init__.py +0 -0
  121. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/vendor/toml/decoder.py +0 -0
  122. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/vendor/toml/encoder.py +0 -0
  123. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/vendor/toml/ordered.py +0 -0
  124. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/vendor/toml/tz.py +0 -0
  125. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/vendor/tomllib/__init__.py +0 -0
  126. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/vendor/tomllib/_parser.py +0 -0
  127. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/vendor/tomllib/_re.py +0 -0
  128. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/vendor/tomllib/_types.py +0 -0
  129. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf/vendor/tomllib/_writer.py +0 -0
  130. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf.egg-info/SOURCES.txt +0 -0
  131. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf.egg-info/dependency_links.txt +0 -0
  132. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf.egg-info/entry_points.txt +0 -0
  133. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf.egg-info/not-zip-safe +0 -0
  134. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf.egg-info/requires.txt +0 -0
  135. {dynaconf-3.3.2 → dynaconf-3.3.3}/dynaconf.egg-info/top_level.txt +0 -0
  136. {dynaconf-3.3.2 → dynaconf-3.3.3}/setup.cfg +0 -0
  137. {dynaconf-3.3.2 → dynaconf-3.3.3}/tests/test_base.py +0 -0
  138. {dynaconf-3.3.2 → dynaconf-3.3.3}/tests/test_basic.py +0 -0
  139. {dynaconf-3.3.2 → dynaconf-3.3.3}/tests/test_cli.py +0 -0
  140. {dynaconf-3.3.2 → dynaconf-3.3.3}/tests/test_compat.py +0 -0
  141. {dynaconf-3.3.2 → dynaconf-3.3.3}/tests/test_django.py +0 -0
  142. {dynaconf-3.3.2 → dynaconf-3.3.3}/tests/test_dynabox.py +0 -0
  143. {dynaconf-3.3.2 → dynaconf-3.3.3}/tests/test_endtoend.py +0 -0
  144. {dynaconf-3.3.2 → dynaconf-3.3.3}/tests/test_env_loader.py +0 -0
  145. {dynaconf-3.3.2 → dynaconf-3.3.3}/tests/test_envvar_prefix.py +0 -0
  146. {dynaconf-3.3.2 → dynaconf-3.3.3}/tests/test_feature_flag.py +0 -0
  147. {dynaconf-3.3.2 → dynaconf-3.3.3}/tests/test_flask.py +0 -0
  148. {dynaconf-3.3.2 → dynaconf-3.3.3}/tests/test_hooking.py +0 -0
  149. {dynaconf-3.3.2 → dynaconf-3.3.3}/tests/test_ini_loader.py +0 -0
  150. {dynaconf-3.3.2 → dynaconf-3.3.3}/tests/test_inspect.py +0 -0
  151. {dynaconf-3.3.2 → dynaconf-3.3.3}/tests/test_json_loader.py +0 -0
  152. {dynaconf-3.3.2 → dynaconf-3.3.3}/tests/test_loaders.py +0 -0
  153. {dynaconf-3.3.2 → dynaconf-3.3.3}/tests/test_nested_loading.py +0 -0
  154. {dynaconf-3.3.2 → dynaconf-3.3.3}/tests/test_nodes.py +0 -0
  155. {dynaconf-3.3.2 → dynaconf-3.3.3}/tests/test_py_loader.py +0 -0
  156. {dynaconf-3.3.2 → dynaconf-3.3.3}/tests/test_redis.py +0 -0
  157. {dynaconf-3.3.2 → dynaconf-3.3.3}/tests/test_release_utility.py +0 -0
  158. {dynaconf-3.3.2 → dynaconf-3.3.3}/tests/test_settings_loader.py +0 -0
  159. {dynaconf-3.3.2 → dynaconf-3.3.3}/tests/test_toml_loader.py +0 -0
  160. {dynaconf-3.3.2 → dynaconf-3.3.3}/tests/test_typed.py +0 -0
  161. {dynaconf-3.3.2 → dynaconf-3.3.3}/tests/test_validators.py +0 -0
  162. {dynaconf-3.3.2 → dynaconf-3.3.3}/tests/test_validators_conditions.py +0 -0
  163. {dynaconf-3.3.2 → dynaconf-3.3.3}/tests/test_vault.py +0 -0
  164. {dynaconf-3.3.2 → dynaconf-3.3.3}/tests/test_yaml_loader.py +0 -0
  165. {dynaconf-3.3.2 → dynaconf-3.3.3}/vendor_licenses/box-LICENSE.txt +0 -0
  166. {dynaconf-3.3.2 → dynaconf-3.3.3}/vendor_licenses/click-LICENSE.rst +0 -0
  167. {dynaconf-3.3.2 → dynaconf-3.3.3}/vendor_licenses/licenses.sh +0 -0
  168. {dynaconf-3.3.2 → dynaconf-3.3.3}/vendor_licenses/python-dotenv-LICENSE.txt +0 -0
  169. {dynaconf-3.3.2 → dynaconf-3.3.3}/vendor_licenses/ruamel.yaml-LICENSE.txt +0 -0
  170. {dynaconf-3.3.2 → dynaconf-3.3.3}/vendor_licenses/toml-LICENSE.txt +0 -0
  171. {dynaconf-3.3.2 → dynaconf-3.3.3}/vendor_licenses/tomli-LICENSE.txt +0 -0
  172. {dynaconf-3.3.2 → dynaconf-3.3.3}/vendor_licenses/vendor_versions.txt +0 -0
@@ -2,6 +2,31 @@ Changelog
2
2
  =========
3
3
 
4
4
  <!-- insertion marker -->
5
+ ## [3.3.3](https://github.com/dynaconf/dynaconf/releases/tag/3.3.3) - 2026-07-22
6
+
7
+ ### Bug Fixes
8
+
9
+
10
+ - Fixed regression on lazy evaluation when using dynaboxify=False (#1429). By @pedro-psb.
11
+
12
+
13
+ ### Docs
14
+
15
+
16
+ - Clarify the values of True, False and None for must_exist check (#1416). By @pedro-psb.
17
+
18
+
19
+ ### Chore
20
+
21
+
22
+ - Fix netlify deploy command on publish workflow (#1420). By @pedro-psb.
23
+
24
+
25
+ - Consolidate docs-publishing workflow for rolling-release (#1417). By @pedro-psb.
26
+
27
+
28
+ - Simplify docs-only skip and include backport branches on main CI (#1415). By @pedro-psb.
29
+
5
30
  ## [3.3.2](https://github.com/dynaconf/dynaconf/releases/tag/3.3.2) - 2026-06-29
6
31
 
7
32
  ### Bug Fixes
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dynaconf
3
- Version: 3.3.2
3
+ Version: 3.3.3
4
4
  Summary: The dynamic configurator for your Python Project
5
5
  Author-email: Bruno Rocha <rochacbruno@gmail.com>
6
6
  License: MIT
@@ -60,6 +60,7 @@ To do so, run `Actions → Publish → Run workflow` with the tag.
60
60
  - The publication uses [Trusted Publishing](https://docs.pypi.org/trusted-publishers/), which must be configured in PyPI.
61
61
  - The release requires content write permissions (push tags). A [Github App](https://docs.github.com/en/apps/overview)
62
62
  (DynaconfBot) was created to enable emitting short-lived tokens with well-scoped permission.
63
+ - Docs publish can be triggered separately via `publish-docs.yml` workflow if required.
63
64
 
64
65
  ### Glossary
65
66
 
@@ -0,0 +1 @@
1
+ 3.3.3
@@ -31,6 +31,7 @@ from dynaconf.loaders import yaml_loader
31
31
  from dynaconf.loaders.base import SourceMetadata
32
32
  from dynaconf.nodes import DataDict
33
33
  from dynaconf.nodes import DataList
34
+ from dynaconf.nodes import recursively_evaluate_lazy_format
34
35
  from dynaconf.strategies.filtering import PrefixFilter
35
36
  from dynaconf.utils import BANNER
36
37
  from dynaconf.utils import ensure_a_list
@@ -641,6 +642,8 @@ class Settings:
641
642
  self.execute_loaders(key=key)
642
643
 
643
644
  data = _get_with_default(parent or core.store, key, default)
645
+ if config.dynaboxify is False:
646
+ data = recursively_evaluate_lazy_format(data, self)
644
647
  if cast:
645
648
  data = apply_converter(cast, data, box_settings=self)
646
649
  return data
@@ -757,6 +757,12 @@ def recursively_evaluate_lazy_format(value, settings):
757
757
  ]
758
758
  )
759
759
 
760
+ elif type(value) is dict:
761
+ value = {
762
+ k: recursively_evaluate_lazy_format(v, settings)
763
+ for k, v in value.items()
764
+ }
765
+
760
766
  return value
761
767
 
762
768
 
@@ -89,9 +89,14 @@ class Validator:
89
89
  # When the very first thing to be performed when passed.
90
90
  # if no env is passed to `when` it is inherited
91
91
 
92
- `must_exist` is alias to `required` requirement. (executed after when)::
92
+ `must_exist` controls existence checking (executed after when)::
93
93
 
94
- settings.get(value, empty) returns non empty
94
+ must_exist=True — variable must be present; raises if absent
95
+ must_exist=False — variable must be absent; raises if present
96
+ must_exist=None — no existence rule (default); absent variables
97
+ skip all further checks on that name
98
+
99
+ `required` is an alias for `must_exist`.
95
100
 
96
101
  condition is a callable to be executed and return boolean::
97
102
 
@@ -214,11 +219,11 @@ class Validator:
214
219
  return _repr
215
220
 
216
221
  @property
217
- def required(self) -> bool:
218
- return bool(self.must_exist)
222
+ def required(self) -> bool | None:
223
+ return self.must_exist
219
224
 
220
225
  @required.setter
221
- def required(self, value: bool):
226
+ def required(self, value: bool | None):
222
227
  self.must_exist = value
223
228
 
224
229
  @property
@@ -387,21 +392,32 @@ class Validator:
387
392
  else:
388
393
  value = settings.get(name, default_value)
389
394
 
390
- # is name required but not exists?
391
- if self.must_exist is True and value is empty:
392
- _message = self.messages["must_exist_true"].format(
393
- name=name, env=env
394
- )
395
- raise ValidationError(_message, details=[(self, _message)])
396
-
397
- if self.must_exist is False and value is not empty:
398
- _message = self.messages["must_exist_false"].format(
399
- name=name, env=env
395
+ # Existence checks
396
+ if self.must_exist is None: # must_exist is unset
397
+ if value is empty:
398
+ continue # no further checks required, as there is nothing to check
399
+ else:
400
+ pass # proceed with further checks
401
+ elif self.must_exist is True:
402
+ if value is empty:
403
+ _message = self.messages["must_exist_true"].format(
404
+ name=name, env=env
405
+ )
406
+ raise ValidationError(_message, details=[(self, _message)])
407
+ else:
408
+ pass
409
+ elif self.must_exist is False:
410
+ if value is empty:
411
+ continue # value absent as required — nothing further to check
412
+ else:
413
+ _message = self.messages["must_exist_false"].format(
414
+ name=name, env=env
415
+ )
416
+ raise ValidationError(_message, details=[(self, _message)])
417
+ else:
418
+ raise ValueError(
419
+ f"must_exist must be True, False, or None, got {self.must_exist!r}"
400
420
  )
401
- raise ValidationError(_message, details=[(self, _message)])
402
-
403
- if self.must_exist in (False, None) and value is empty:
404
- continue
405
421
 
406
422
  # value or default value already set
407
423
  # by settings.setdefault above
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dynaconf
3
- Version: 3.3.2
3
+ Version: 3.3.3
4
4
  Summary: The dynamic configurator for your Python Project
5
5
  Author-email: Bruno Rocha <rochacbruno@gmail.com>
6
6
  License: MIT
@@ -208,7 +208,7 @@ ignore_missing_imports = true
208
208
  # ===========
209
209
 
210
210
  [tool.bumpversion]
211
- current_version = "3.3.2"
211
+ current_version = "3.3.3"
212
212
  parse = """(?x)
213
213
  (?P<major>0|[1-9]\\d*)\\.
214
214
  (?P<minor>0|[1-9]\\d*)\\.
@@ -455,85 +455,104 @@ def test_upperfy():
455
455
  assert upperfy("foo_BAR") == "FOO_BAR"
456
456
 
457
457
 
458
- def test_lazy_format_class():
459
- value = Lazy("{this[FOO]}/bar")
460
- settings = {"FOO": "foo"}
461
- assert value(settings) == "foo/bar"
462
- assert str(value) == value.value
463
- assert repr(value) == f"'@{value.formatter} {value.value}'"
464
-
465
-
466
- def test_evaluate_lazy_format_decorator(settings):
467
- class Settings:
468
- FOO = "foo"
469
- AUTO_CAST_FOR_DYNACONF = True
470
-
471
- @evaluate_lazy_format
472
- def get(self, key, default=None):
473
- if key.endswith("_FOR_DYNACONF"):
474
- return getattr(self, key)
475
- return parse_conf_data("@format {this.FOO}/bar", box_settings=self)
458
+ class TestLazyInternal:
459
+ def test_format_class(self):
460
+ value = Lazy("{this[FOO]}/bar")
461
+ settings = {"FOO": "foo"}
462
+ assert value(settings) == "foo/bar"
463
+ assert str(value) == value.value
464
+ assert repr(value) == f"'@{value.formatter} {value.value}'"
465
+
466
+ def test_format_class_jinja(self):
467
+ value = Lazy(
468
+ "{{this['FOO']}}/bar", formatter=Formatters.jinja_formatter
469
+ )
470
+ settings = {"FOO": "foo"}
471
+ assert value(settings) == "foo/bar"
476
472
 
477
- def __contains__(self, key):
478
- value = getattr(self, key, missing)
479
- if value is missing:
480
- return False
481
- return True
473
+ def test_custom_function_formatter(self):
474
+ def custom_formatter(value, **context):
475
+ return f"custom:{value}"
482
476
 
483
- settings = Settings()
484
- assert settings.get("foo") == "foo/bar"
477
+ lazy_val = Lazy("test", formatter=custom_formatter)
478
+ assert lazy_val({}) == "custom:test"
485
479
 
480
+ def test_json_serializable(self):
481
+ value = Lazy("{this[FOO]}/bar")
482
+ assert (
483
+ json.dumps({"val": value}, cls=DynaconfEncoder)
484
+ == '{"val": "@format {this[FOO]}/bar"}'
485
+ )
486
486
 
487
- def test_lazy_format_on_settings(settings):
488
- os.environ["ENV_THING"] = "LazyFormat"
489
- settings.set("set_1", "really")
490
- settings.set("lazy", "@format {env[ENV_THING]}/{this[set_1]}/{this.SET_2}")
491
- settings.set("set_2", "works")
492
487
 
493
- assert settings.LAZY == settings.get("lazy") == "LazyFormat/really/works"
488
+ class TestLazyDecorator:
489
+ def _make_settings(self, template):
490
+ class Settings:
491
+ FOO = "foo"
492
+ AUTO_CAST_FOR_DYNACONF = True
494
493
 
494
+ @evaluate_lazy_format
495
+ def get(self, key, default=None):
496
+ if key.endswith("_FOR_DYNACONF"):
497
+ return getattr(self, key)
498
+ return parse_conf_data(template, box_settings=self)
495
499
 
496
- def test_lazy_format_class_jinja():
497
- value = Lazy("{{this['FOO']}}/bar", formatter=Formatters.jinja_formatter)
498
- settings = {"FOO": "foo"}
499
- assert value(settings) == "foo/bar"
500
+ def __contains__(self, key):
501
+ value = getattr(self, key, missing)
502
+ if value is missing:
503
+ return False
504
+ return True
500
505
 
506
+ return Settings()
501
507
 
502
- def test_evaluate_lazy_format_decorator_jinja(settings):
503
- class Settings:
504
- FOO = "foo"
508
+ def test_format(self):
509
+ settings = self._make_settings("@format {this.FOO}/bar")
510
+ assert settings.get("foo") == "foo/bar"
505
511
 
506
- AUTO_CAST_FOR_DYNACONF = True
512
+ def test_jinja(self):
513
+ settings = self._make_settings("@jinja {{this.FOO}}/bar")
514
+ assert settings.get("foo") == "foo/bar"
507
515
 
508
- @evaluate_lazy_format
509
- def get(self, key, default=None):
510
- if key.endswith("_FOR_DYNACONF"):
511
- return getattr(self, key)
512
- return parse_conf_data(
513
- "@jinja {{this.FOO}}/bar", box_settings=settings
514
- )
515
516
 
516
- settings = Settings()
517
- assert settings.get("foo") == "foo/bar"
517
+ class TestLazyUsage:
518
+ @pytest.fixture(params=[True, False], ids=["dynaboxify", "no_dynaboxify"])
519
+ def settings(self, request):
520
+ return Dynaconf(dynaboxify=request.param)
518
521
 
522
+ def test_format(self, settings):
523
+ os.environ["ENV_THING"] = "LazyFormat"
524
+ settings.set("set_1", "really")
525
+ settings.set(
526
+ "lazy", "@format {env[ENV_THING]}/{this[set_1]}/{this.SET_2}"
527
+ )
528
+ settings.set("set_2", "works")
519
529
 
520
- def test_lazy_format_on_settings_jinja(settings):
521
- os.environ["ENV_THING"] = "LazyFormat"
522
- settings.set("set_1", "really")
523
- settings.set(
524
- "lazy", "@jinja {{env.ENV_THING}}/{{this['set_1']}}/{{this.SET_2}}"
525
- )
526
- settings.set("set_2", "works")
530
+ assert (
531
+ settings.LAZY == settings.get("lazy") == "LazyFormat/really/works"
532
+ )
527
533
 
528
- assert settings.LAZY == settings.get("lazy") == "LazyFormat/really/works"
534
+ def test_format_nested_dict(self, settings):
535
+ settings.set("BASE_VALUE", "hello")
536
+ settings.set("NESTED", {"inner": "@format {this[BASE_VALUE]}/world"})
537
+ assert settings.get("NESTED")["inner"] == "hello/world"
538
+
539
+ def test_jinja(self, settings):
540
+ os.environ["ENV_THING"] = "LazyFormat"
541
+ settings.set("set_1", "really")
542
+ settings.set(
543
+ "lazy",
544
+ "@jinja {{env.ENV_THING}}/{{this['set_1']}}/{{this.SET_2}}",
545
+ )
546
+ settings.set("set_2", "works")
529
547
 
548
+ assert (
549
+ settings.LAZY == settings.get("lazy") == "LazyFormat/really/works"
550
+ )
530
551
 
531
- def test_lazy_format_is_json_serializable():
532
- value = Lazy("{this[FOO]}/bar")
533
- assert (
534
- json.dumps({"val": value}, cls=DynaconfEncoder)
535
- == '{"val": "@format {this[FOO]}/bar"}'
536
- )
552
+ def test_jinja_nested_dict(self, settings):
553
+ settings.set("BASE_VALUE", "hello")
554
+ settings.set("NESTED", {"inner": "@jinja {{this.BASE_VALUE}}/world"})
555
+ assert settings.get("NESTED")["inner"] == "hello/world"
537
556
 
538
557
 
539
558
  def test_try_to_encode():
@@ -1365,19 +1384,6 @@ def test_read_file_os_error(tmp_path, monkeypatch):
1365
1384
  settings.SECRET
1366
1385
 
1367
1386
 
1368
- def test_lazy_with_custom_function_formatter():
1369
- """Test Lazy with custom function (not BaseFormatter)"""
1370
- from dynaconf.utils.parse_conf import Lazy
1371
-
1372
- def custom_formatter(value, **context):
1373
- return f"custom:{value}"
1374
-
1375
- lazy_val = Lazy("test", formatter=custom_formatter)
1376
- settings = {}
1377
- result = lazy_val(settings)
1378
- assert result == "custom:test"
1379
-
1380
-
1381
1387
  def test_safe_json_parse_extract_objects(settings):
1382
1388
  """Test _safe_json_parse with extract_json_objects"""
1383
1389
  # Test case where extract_json_objects finds valid JSON
@@ -1 +0,0 @@
1
- 3.3.2
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes