dynaconf 3.3.1__tar.gz → 3.3.2__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.1 → dynaconf-3.3.2}/CHANGELOG.md +25 -0
  2. {dynaconf-3.3.1 → dynaconf-3.3.2}/PKG-INFO +1 -1
  3. dynaconf-3.3.2/dynaconf/VERSION +1 -0
  4. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/base.py +3 -2
  5. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/cli.py +2 -2
  6. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/utils/__init__.py +1 -1
  7. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/utils/parse_conf.py +1 -1
  8. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf.egg-info/PKG-INFO +1 -1
  9. {dynaconf-3.3.1 → dynaconf-3.3.2}/pyproject.toml +5 -2
  10. {dynaconf-3.3.1 → dynaconf-3.3.2}/tests/test_cli.py +73 -35
  11. {dynaconf-3.3.1 → dynaconf-3.3.2}/tests/test_utils.py +49 -0
  12. {dynaconf-3.3.1 → dynaconf-3.3.2}/tests/test_validators.py +16 -0
  13. dynaconf-3.3.1/dynaconf/VERSION +0 -1
  14. {dynaconf-3.3.1 → dynaconf-3.3.2}/CONTRIBUTING.md +0 -0
  15. {dynaconf-3.3.1 → dynaconf-3.3.2}/CONTRIBUTORS.md +0 -0
  16. {dynaconf-3.3.1 → dynaconf-3.3.2}/LICENSE +0 -0
  17. {dynaconf-3.3.1 → dynaconf-3.3.2}/MANIFEST.in +0 -0
  18. {dynaconf-3.3.1 → dynaconf-3.3.2}/README.md +0 -0
  19. {dynaconf-3.3.1 → dynaconf-3.3.2}/RELEASING.md +0 -0
  20. {dynaconf-3.3.1 → dynaconf-3.3.2}/SECURITY.md +0 -0
  21. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/__init__.py +0 -0
  22. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/__main__.py +0 -0
  23. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/constants.py +0 -0
  24. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/contrib/__init__.py +0 -0
  25. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/contrib/django_dynaconf_v2.py +0 -0
  26. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/contrib/flask_dynaconf.py +0 -0
  27. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/default_settings.py +0 -0
  28. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/hooking.py +0 -0
  29. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/loaders/__init__.py +0 -0
  30. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/loaders/base.py +0 -0
  31. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/loaders/env_loader.py +0 -0
  32. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/loaders/ini_loader.py +0 -0
  33. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/loaders/json_loader.py +0 -0
  34. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/loaders/py_loader.py +0 -0
  35. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/loaders/redis_loader.py +0 -0
  36. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/loaders/toml_loader.py +0 -0
  37. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/loaders/vault_loader.py +0 -0
  38. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/loaders/yaml_loader.py +0 -0
  39. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/nodes.py +0 -0
  40. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/strategies/__init__.py +0 -0
  41. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/strategies/filtering.py +0 -0
  42. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/test_settings.py +0 -0
  43. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/typed/__init__.py +0 -0
  44. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/typed/compat.py +0 -0
  45. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/typed/exceptions.py +0 -0
  46. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/typed/guards.py +0 -0
  47. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/typed/main.py +0 -0
  48. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/typed/types.py +0 -0
  49. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/typed/utils.py +0 -0
  50. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/typed/validators.py +0 -0
  51. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/utils/boxing.py +0 -0
  52. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/utils/files.py +0 -0
  53. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/utils/functional.py +0 -0
  54. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/utils/inspect.py +0 -0
  55. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/validator.py +0 -0
  56. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/validator_conditions.py +0 -0
  57. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/vendor/__init__.py +0 -0
  58. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/vendor/box/__init__.py +0 -0
  59. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/vendor/box/box.py +0 -0
  60. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/vendor/box/box_list.py +0 -0
  61. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/vendor/box/config_box.py +0 -0
  62. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/vendor/box/converters.py +0 -0
  63. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/vendor/box/exceptions.py +0 -0
  64. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/vendor/box/from_file.py +0 -0
  65. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/vendor/box/shorthand_box.py +0 -0
  66. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/vendor/click/__init__.py +0 -0
  67. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/vendor/click/_bashcomplete.py +0 -0
  68. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/vendor/click/_compat.py +0 -0
  69. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/vendor/click/_termui_impl.py +0 -0
  70. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/vendor/click/_textwrap.py +0 -0
  71. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/vendor/click/_unicodefun.py +0 -0
  72. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/vendor/click/_winconsole.py +0 -0
  73. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/vendor/click/core.py +0 -0
  74. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/vendor/click/decorators.py +0 -0
  75. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/vendor/click/exceptions.py +0 -0
  76. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/vendor/click/formatting.py +0 -0
  77. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/vendor/click/globals.py +0 -0
  78. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/vendor/click/parser.py +0 -0
  79. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/vendor/click/termui.py +0 -0
  80. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/vendor/click/testing.py +0 -0
  81. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/vendor/click/types.py +0 -0
  82. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/vendor/click/utils.py +0 -0
  83. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/vendor/dotenv/__init__.py +0 -0
  84. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/vendor/dotenv/cli.py +0 -0
  85. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/vendor/dotenv/compat.py +0 -0
  86. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/vendor/dotenv/ipython.py +0 -0
  87. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/vendor/dotenv/main.py +0 -0
  88. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/vendor/dotenv/parser.py +0 -0
  89. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/vendor/dotenv/py.typed +0 -0
  90. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/vendor/dotenv/version.py +0 -0
  91. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/vendor/ruamel/__init__.py +0 -0
  92. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/vendor/ruamel/yaml/__init__.py +0 -0
  93. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/vendor/ruamel/yaml/anchor.py +0 -0
  94. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/vendor/ruamel/yaml/comments.py +0 -0
  95. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/vendor/ruamel/yaml/compat.py +0 -0
  96. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/vendor/ruamel/yaml/composer.py +0 -0
  97. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/vendor/ruamel/yaml/configobjwalker.py +0 -0
  98. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/vendor/ruamel/yaml/constructor.py +0 -0
  99. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/vendor/ruamel/yaml/cyaml.py +0 -0
  100. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/vendor/ruamel/yaml/dumper.py +0 -0
  101. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/vendor/ruamel/yaml/emitter.py +0 -0
  102. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/vendor/ruamel/yaml/error.py +0 -0
  103. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/vendor/ruamel/yaml/events.py +0 -0
  104. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/vendor/ruamel/yaml/loader.py +0 -0
  105. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/vendor/ruamel/yaml/main.py +0 -0
  106. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/vendor/ruamel/yaml/nodes.py +0 -0
  107. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/vendor/ruamel/yaml/parser.py +0 -0
  108. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/vendor/ruamel/yaml/py.typed +0 -0
  109. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/vendor/ruamel/yaml/reader.py +0 -0
  110. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/vendor/ruamel/yaml/representer.py +0 -0
  111. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/vendor/ruamel/yaml/resolver.py +0 -0
  112. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/vendor/ruamel/yaml/scalarbool.py +0 -0
  113. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/vendor/ruamel/yaml/scalarfloat.py +0 -0
  114. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/vendor/ruamel/yaml/scalarint.py +0 -0
  115. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/vendor/ruamel/yaml/scalarstring.py +0 -0
  116. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/vendor/ruamel/yaml/scanner.py +0 -0
  117. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/vendor/ruamel/yaml/serializer.py +0 -0
  118. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/vendor/ruamel/yaml/setup.py +0 -0
  119. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/vendor/ruamel/yaml/timestamp.py +0 -0
  120. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/vendor/ruamel/yaml/tokens.py +0 -0
  121. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/vendor/ruamel/yaml/util.py +0 -0
  122. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/vendor/toml/__init__.py +0 -0
  123. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/vendor/toml/decoder.py +0 -0
  124. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/vendor/toml/encoder.py +0 -0
  125. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/vendor/toml/ordered.py +0 -0
  126. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/vendor/toml/tz.py +0 -0
  127. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/vendor/tomllib/__init__.py +0 -0
  128. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/vendor/tomllib/_parser.py +0 -0
  129. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/vendor/tomllib/_re.py +0 -0
  130. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/vendor/tomllib/_types.py +0 -0
  131. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf/vendor/tomllib/_writer.py +0 -0
  132. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf.egg-info/SOURCES.txt +0 -0
  133. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf.egg-info/dependency_links.txt +0 -0
  134. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf.egg-info/entry_points.txt +0 -0
  135. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf.egg-info/not-zip-safe +0 -0
  136. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf.egg-info/requires.txt +0 -0
  137. {dynaconf-3.3.1 → dynaconf-3.3.2}/dynaconf.egg-info/top_level.txt +0 -0
  138. {dynaconf-3.3.1 → dynaconf-3.3.2}/setup.cfg +0 -0
  139. {dynaconf-3.3.1 → dynaconf-3.3.2}/tests/test_base.py +0 -0
  140. {dynaconf-3.3.1 → dynaconf-3.3.2}/tests/test_basic.py +0 -0
  141. {dynaconf-3.3.1 → dynaconf-3.3.2}/tests/test_compat.py +0 -0
  142. {dynaconf-3.3.1 → dynaconf-3.3.2}/tests/test_django.py +0 -0
  143. {dynaconf-3.3.1 → dynaconf-3.3.2}/tests/test_dynabox.py +0 -0
  144. {dynaconf-3.3.1 → dynaconf-3.3.2}/tests/test_endtoend.py +0 -0
  145. {dynaconf-3.3.1 → dynaconf-3.3.2}/tests/test_env_loader.py +0 -0
  146. {dynaconf-3.3.1 → dynaconf-3.3.2}/tests/test_envvar_prefix.py +0 -0
  147. {dynaconf-3.3.1 → dynaconf-3.3.2}/tests/test_feature_flag.py +0 -0
  148. {dynaconf-3.3.1 → dynaconf-3.3.2}/tests/test_flask.py +0 -0
  149. {dynaconf-3.3.1 → dynaconf-3.3.2}/tests/test_hooking.py +0 -0
  150. {dynaconf-3.3.1 → dynaconf-3.3.2}/tests/test_ini_loader.py +0 -0
  151. {dynaconf-3.3.1 → dynaconf-3.3.2}/tests/test_inspect.py +0 -0
  152. {dynaconf-3.3.1 → dynaconf-3.3.2}/tests/test_json_loader.py +0 -0
  153. {dynaconf-3.3.1 → dynaconf-3.3.2}/tests/test_loaders.py +0 -0
  154. {dynaconf-3.3.1 → dynaconf-3.3.2}/tests/test_nested_loading.py +0 -0
  155. {dynaconf-3.3.1 → dynaconf-3.3.2}/tests/test_nodes.py +0 -0
  156. {dynaconf-3.3.1 → dynaconf-3.3.2}/tests/test_py_loader.py +0 -0
  157. {dynaconf-3.3.1 → dynaconf-3.3.2}/tests/test_redis.py +0 -0
  158. {dynaconf-3.3.1 → dynaconf-3.3.2}/tests/test_release_utility.py +0 -0
  159. {dynaconf-3.3.1 → dynaconf-3.3.2}/tests/test_settings_loader.py +0 -0
  160. {dynaconf-3.3.1 → dynaconf-3.3.2}/tests/test_toml_loader.py +0 -0
  161. {dynaconf-3.3.1 → dynaconf-3.3.2}/tests/test_typed.py +0 -0
  162. {dynaconf-3.3.1 → dynaconf-3.3.2}/tests/test_validators_conditions.py +0 -0
  163. {dynaconf-3.3.1 → dynaconf-3.3.2}/tests/test_vault.py +0 -0
  164. {dynaconf-3.3.1 → dynaconf-3.3.2}/tests/test_yaml_loader.py +0 -0
  165. {dynaconf-3.3.1 → dynaconf-3.3.2}/vendor_licenses/box-LICENSE.txt +0 -0
  166. {dynaconf-3.3.1 → dynaconf-3.3.2}/vendor_licenses/click-LICENSE.rst +0 -0
  167. {dynaconf-3.3.1 → dynaconf-3.3.2}/vendor_licenses/licenses.sh +0 -0
  168. {dynaconf-3.3.1 → dynaconf-3.3.2}/vendor_licenses/python-dotenv-LICENSE.txt +0 -0
  169. {dynaconf-3.3.1 → dynaconf-3.3.2}/vendor_licenses/ruamel.yaml-LICENSE.txt +0 -0
  170. {dynaconf-3.3.1 → dynaconf-3.3.2}/vendor_licenses/toml-LICENSE.txt +0 -0
  171. {dynaconf-3.3.1 → dynaconf-3.3.2}/vendor_licenses/tomli-LICENSE.txt +0 -0
  172. {dynaconf-3.3.1 → dynaconf-3.3.2}/vendor_licenses/vendor_versions.txt +0 -0
@@ -2,6 +2,31 @@ Changelog
2
2
  =========
3
3
 
4
4
  <!-- insertion marker -->
5
+ ## [3.3.2](https://github.com/dynaconf/dynaconf/releases/tag/3.3.2) - 2026-06-29
6
+
7
+ ### Bug Fixes
8
+
9
+
10
+ - Don't treat a string sharing a converter prefix as a cast (#1412). By @sarathfrancis90.
11
+
12
+
13
+ - Fixed 'dynaconf validate' edge cases with validation_file.toml (#1411). By @pedro-psb.
14
+
15
+
16
+ - Avoid RecursionError in from_env with validate_on_update (#1409). By @Redbot.
17
+
18
+
19
+ - Avoid crash merging a dynaconf_merge list into a missing key (#1410). By @sarathfrancis90.
20
+
21
+
22
+ ### Chore
23
+
24
+
25
+ - Fix check-release command from release utility. By @pedro-psb.
26
+
27
+
28
+ - Improve changelog generation output. By @pedro-psb.
29
+
5
30
  ## [3.3.1](https://github.com/dynaconf/dynaconf/releases/tag/3.3.1) - 2026-06-26
6
31
 
7
32
  ### Bug Fixes
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dynaconf
3
- Version: 3.3.1
3
+ Version: 3.3.2
4
4
  Summary: The dynamic configurator for your Python Project
5
5
  Author-email: Bruno Rocha <rochacbruno@gmail.com>
6
6
  License: MIT
@@ -0,0 +1 @@
1
+ 3.3.2
@@ -880,8 +880,9 @@ class Settings:
880
880
  return self.MAIN_ENV_FOR_DYNACONF.lower()
881
881
 
882
882
  if self.FORCE_ENV_FOR_DYNACONF is not None:
883
- self.ENV_FOR_DYNACONF = self.FORCE_ENV_FOR_DYNACONF
884
- return self.FORCE_ENV_FOR_DYNACONF
883
+ forced = self.FORCE_ENV_FOR_DYNACONF
884
+ self.set("ENV_FOR_DYNACONF", forced, validate=False)
885
+ return forced
885
886
 
886
887
  try:
887
888
  return self.loaded_envs[-1]
@@ -820,9 +820,9 @@ def validate(path): # pragma: no cover
820
820
  )
821
821
  sys.exit(1)
822
822
 
823
- # guarantee there is an environment
824
823
  validation_data = {k.lower(): v for k, v in validation_data.items()}
825
- if not validation_data.get("default"):
824
+ if not settings.ENVIRONMENTS_FOR_DYNACONF:
825
+ # flat file: top-level keys are field names, not environments
826
826
  validation_data = {"default": validation_data}
827
827
 
828
828
  success = True
@@ -237,7 +237,7 @@ def handle_metavalues(
237
237
  value.remove("dynaconf_merge_unique")
238
238
  unique = True
239
239
 
240
- for item in old.get(key)[::-1]:
240
+ for item in (old.get(key) or [])[::-1]:
241
241
  if unique and item in value:
242
242
  continue
243
243
  value.insert(0, item)
@@ -761,7 +761,7 @@ def _parse_conf_data(data, tomlfy=False, box_settings=None):
761
761
  castenabled
762
762
  and data
763
763
  and isinstance(data, str)
764
- and data.startswith(tuple(converters.keys()))
764
+ and data.partition(" ")[0] in converters
765
765
  ):
766
766
  # Check combination token is used
767
767
  comb_token = re.match(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dynaconf
3
- Version: 3.3.1
3
+ Version: 3.3.2
4
4
  Summary: The dynamic configurator for your Python Project
5
5
  Author-email: Bruno Rocha <rochacbruno@gmail.com>
6
6
  License: MIT
@@ -170,7 +170,6 @@ omit = [
170
170
  # ======
171
171
 
172
172
  [tool.pytest.ini_options]
173
- pythonpath = [".github/scripts"]
174
173
  markers = [
175
174
  "integration: marks tests as integration (deselect with '-m \"not integration\"')",
176
175
  ]
@@ -209,7 +208,7 @@ ignore_missing_imports = true
209
208
  # ===========
210
209
 
211
210
  [tool.bumpversion]
212
- current_version = "3.3.1"
211
+ current_version = "3.3.2"
213
212
  parse = """(?x)
214
213
  (?P<major>0|[1-9]\\d*)\\.
215
214
  (?P<minor>0|[1-9]\\d*)\\.
@@ -311,6 +310,7 @@ lint = [
311
310
  "typos>=1.42.3",
312
311
  ]
313
312
  test = [
313
+ "dynaconf-release-utility",
314
314
  "boto3>=1.40.67",
315
315
  "commentjson>=0.9.0",
316
316
  "configobj>=5.0.9",
@@ -342,3 +342,6 @@ release = [
342
342
  "toml>=0.10.2",
343
343
  "twine>=6.2.0",
344
344
  ]
345
+
346
+ [tool.uv.sources]
347
+ dynaconf-release-utility = { path = ".github/scripts/", editable = true }
@@ -465,48 +465,86 @@ host = "test.com"
465
465
  """
466
466
 
467
467
 
468
- def test_validate(tmpdir):
469
- validation_file = tmpdir.join("dynaconf_validators.toml")
470
- validation_file.write(VALIDATION)
468
+ class TestValidate:
469
+ def _run_validate(
470
+ self,
471
+ *,
472
+ settings_mod: str,
473
+ validation_file: str,
474
+ env: dict | None = None,
475
+ ) -> str:
476
+ return run(
477
+ ["-i", settings_mod, "validate", "-p", validation_file], env
478
+ )
471
479
 
472
- toml_valid = tmpdir.mkdir("valid").join("settings.toml")
473
- toml_valid.write(TOML_VALID)
480
+ def test_basic(self, create_file):
481
+ validators_toml = create_file("dynaconf_validators.toml", VALIDATION)
482
+ toml_valid = create_file("settings_valid.toml", TOML_VALID)
483
+ toml_invalid = create_file("settings_invalid.toml", TOML_INVALID)
474
484
 
475
- toml_invalid = tmpdir.mkdir("invalid").join("settings.toml")
476
- toml_invalid.write(TOML_INVALID)
485
+ result = self._run_validate(
486
+ settings_mod="tests.config.settingsenv",
487
+ validation_file=str(validators_toml),
488
+ env={"SETTINGS_FILE_FOR_DYNACONF": str(toml_valid)},
489
+ )
490
+ assert "Validation success!" in result
477
491
 
478
- result = run(
479
- [
480
- "-i",
481
- "tests.config.settingsenv",
482
- "validate",
483
- "-p",
484
- str(validation_file),
485
- ],
486
- {"SETTINGS_FILE_FOR_DYNACONF": str(toml_valid)},
487
- )
488
- assert "Validation success!" in result
492
+ result = self._run_validate(
493
+ settings_mod="tests.test_cli.settings",
494
+ validation_file=str(validators_toml.parent),
495
+ env={"SETTINGS_FILE_FOR_DYNACONF": str(toml_invalid)},
496
+ )
497
+ assert "age must lte 30 but it is 35 in env default" in result
498
+ assert (
499
+ "project must eq hello_world but it is This is not hello_world "
500
+ "in env production" in result
501
+ )
502
+ assert (
503
+ "host must is_not_in ['test.com'] but it is test.com in env "
504
+ "production" in result
505
+ )
506
+ assert "Validation success!" not in result
489
507
 
490
- result = run(
508
+ @pytest.mark.parametrize(
509
+ "environments, expected",
491
510
  [
492
- "-i",
493
- "tests.test_cli.settings",
494
- "validate",
495
- "-p",
496
- str(Path(str(validation_file)).parent),
511
+ pytest.param(True, "Validation error!", id="environments-enabled"),
512
+ pytest.param(
513
+ False, "Validation success!", id="environments-disabled"
514
+ ),
515
+ # In this last case, 'main' is considered a regular field, and it doesn't have
516
+ # a validator a must_exist rule. 'staryear' must exist, but if it's
517
+ # parent "main" (not an env here!) doesnt exist, which is fine, the
518
+ # validator doesn't apply. This is the most strict interpretation, and
519
+ # having the 'must_exist' rule propagate, that's it, require 'main' to exist,
520
+ # can be considered a new feature
497
521
  ],
498
- {"SETTINGS_FILE_FOR_DYNACONF": str(toml_invalid)},
499
522
  )
500
- assert "age must lte 30 but it is 35 in env default" in result
501
- assert (
502
- "project must eq hello_world but it is This is not hello_world "
503
- "in env production" in result
504
- )
505
- assert (
506
- "host must is_not_in ['test.com'] but it is test.com in env "
507
- "production" in result
508
- )
509
- assert "Validation success!" not in result
523
+ def test_issue_1368(self, create_file, environments: bool, expected: str):
524
+ """Section-based validators in dynaconf_validators.toml with a non-default section name.
525
+
526
+ https://github.com/dynaconf/dynaconf/issues/1368
527
+ """
528
+ module = f"config_{environments}"
529
+ create_file(
530
+ f"{module}.py",
531
+ f"""
532
+ from dynaconf import Dynaconf
533
+ settings = Dynaconf(environments={environments})
534
+ """,
535
+ )
536
+ validators_toml = create_file(
537
+ "dynaconf_validators.toml",
538
+ """
539
+ [main]
540
+ startyear = {must_exist=true}
541
+ """,
542
+ )
543
+ result = self._run_validate(
544
+ settings_mod=f"{module}.settings",
545
+ validation_file=str(validators_toml),
546
+ )
547
+ assert expected in result
510
548
 
511
549
 
512
550
  def create_file(filename: str | Path, data: str):
@@ -128,6 +128,39 @@ def test_casting_str(settings):
128
128
  assert isinstance(res, str) and res == "7"
129
129
 
130
130
 
131
+ def test_string_starting_with_converter_prefix_is_not_cast(settings):
132
+ """A plain string that merely shares a prefix with a converter token
133
+ (e.g. ``@interface`` starts with ``@int``) must be kept verbatim, not
134
+ treated as a cast. Previously this raised ``KeyError``.
135
+ """
136
+ # these all start with a real converter token as a *prefix* only
137
+ for value in (
138
+ "@interface", # @int
139
+ "@integer", # @int
140
+ "@formatter", # @format
141
+ "@gettext", # @get
142
+ "@uppercase", # @upper
143
+ "@stringify", # @str
144
+ "@noneofthat", # @none
145
+ ):
146
+ assert parse_conf_data(value, box_settings=settings) == value
147
+ assert (
148
+ parse_conf_data(value, tomlfy=True, box_settings=settings) == value
149
+ )
150
+
151
+ # a prefix collision followed by more words must also pass through
152
+ assert (
153
+ parse_conf_data("@formatter blah", tomlfy=True, box_settings=settings)
154
+ == "@formatter blah"
155
+ )
156
+
157
+ # real converter tokens must still cast (regression guard)
158
+ assert parse_conf_data("@int 5", box_settings=settings) == 5
159
+ assert parse_conf_data("@none", box_settings=settings) is None
160
+ settings.set("value", 5)
161
+ assert parse_conf_data("@int @format {this.value}")(settings) == 5
162
+
163
+
131
164
  def test_casting_int(settings):
132
165
  res = parse_conf_data("@int 2")
133
166
  assert isinstance(res, int) and res == 2
@@ -334,6 +367,22 @@ def test_merge_dict_with_meta_values(settings):
334
367
  assert new == {"A": 1, "C": 4}
335
368
 
336
369
 
370
+ def test_merge_list_token_when_key_absent_in_old():
371
+ # A list carrying the `dynaconf_merge` marker must not crash when the key
372
+ # holding it is absent from the existing data (there is nothing to merge
373
+ # into). The marker is consumed and the provided list is kept as-is.
374
+ old = {"existing": 1}
375
+ new = {"ports": [8080, "dynaconf_merge"]}
376
+ object_merge(old, new)
377
+ assert new == {"existing": 1, "ports": [8080]}
378
+
379
+ # Same for the `dynaconf_merge_unique` marker.
380
+ old = {"existing": 1}
381
+ new = {"ports": [8080, "dynaconf_merge_unique"]}
382
+ object_merge(old, new)
383
+ assert new == {"existing": 1, "ports": [8080]}
384
+
385
+
337
386
  def test_trimmed_split():
338
387
  # No sep
339
388
  assert trimmed_split("hello") == ["hello"]
@@ -921,3 +921,19 @@ def test_validation_after_setenv_or_from_env(tmp_path):
921
921
  with pytest.raises(ValidationError):
922
922
  other_settings = settings.from_env("production")
923
923
  other_settings.validators.validate_all()
924
+
925
+
926
+ def test_from_env_with_validate_on_update_does_not_recurse():
927
+ """from_env must not infinitely recurse via current_env -> validation."""
928
+ from dynaconf import Dynaconf
929
+ from dynaconf import Validator
930
+
931
+ settings = Dynaconf(
932
+ environments=True,
933
+ validate_on_update=True,
934
+ validators=[Validator("FOO", default="bar")],
935
+ )
936
+
937
+ # Before the fix this raised RecursionError.
938
+ other_settings = settings.from_env("development")
939
+ assert other_settings.current_env.lower() == "development"
@@ -1 +0,0 @@
1
- 3.3.1
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
File without changes