dotenvmodel 0.6.1__tar.gz → 0.6.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 (118) hide show
  1. dotenvmodel-0.6.2/.release-please-manifest.json +3 -0
  2. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/CHANGELOG.md +7 -0
  3. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/PKG-INFO +1 -1
  4. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/dotenvmodel/__init__.py +1 -1
  5. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/dotenvmodel/caching.py +18 -5
  6. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/dotenvmodel/config.py +20 -5
  7. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/pyproject.toml +1 -1
  8. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/tests/test_cached.py +106 -2
  9. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/uv.lock +1 -1
  10. dotenvmodel-0.6.1/.release-please-manifest.json +0 -3
  11. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/.github/CODEOWNERS +0 -0
  12. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  13. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  14. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/.github/dependabot.yml +0 -0
  15. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/.github/pull_request_template.md +0 -0
  16. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/.github/workflows/ci.yml +0 -0
  17. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/.github/workflows/docs.yml +0 -0
  18. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/.github/workflows/publish.yml +0 -0
  19. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/.github/workflows/release-please.yml +0 -0
  20. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/.gitignore +0 -0
  21. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/.python-version +0 -0
  22. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/CODE_OF_CONDUCT.md +0 -0
  23. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/CONTRIBUTING.md +0 -0
  24. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/LICENSE +0 -0
  25. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/Makefile +0 -0
  26. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/README.md +0 -0
  27. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/SECURITY.md +0 -0
  28. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/docs/api-reference/caching.md +0 -0
  29. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/docs/api-reference/coercion.md +0 -0
  30. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/docs/api-reference/config.md +0 -0
  31. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/docs/api-reference/constants.md +0 -0
  32. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/docs/api-reference/describe-formatters.md +0 -0
  33. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/docs/api-reference/describe-renderers.md +0 -0
  34. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/docs/api-reference/describe.md +0 -0
  35. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/docs/api-reference/dotenvmodel.md +0 -0
  36. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/docs/api-reference/exceptions.md +0 -0
  37. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/docs/api-reference/fields.md +0 -0
  38. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/docs/api-reference/loading.md +0 -0
  39. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/docs/api-reference/logging-config.md +0 -0
  40. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/docs/api-reference/metaclass.md +0 -0
  41. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/docs/api-reference/types.md +0 -0
  42. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/docs/api-reference/validation.md +0 -0
  43. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/docs/changelog.md +0 -0
  44. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/docs/examples/complete-app.md +0 -0
  45. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/docs/getting-started/installation.md +0 -0
  46. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/docs/getting-started/quick-start.md +0 -0
  47. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/docs/guides/configuration-docs.md +0 -0
  48. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/docs/guides/error-handling.md +0 -0
  49. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/docs/guides/fields.md +0 -0
  50. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/docs/guides/loading.md +0 -0
  51. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/docs/guides/logging.md +0 -0
  52. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/docs/guides/prefixes.md +0 -0
  53. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/docs/guides/types.md +0 -0
  54. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/docs/guides/validation.md +0 -0
  55. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/docs/index.md +0 -0
  56. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/dotenvmodel/_constants.py +0 -0
  57. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/dotenvmodel/_redaction.py +0 -0
  58. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/dotenvmodel/coercion.py +0 -0
  59. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/dotenvmodel/describe/__init__.py +0 -0
  60. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/dotenvmodel/describe/formatters.py +0 -0
  61. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/dotenvmodel/describe/renderers.py +0 -0
  62. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/dotenvmodel/exceptions.py +0 -0
  63. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/dotenvmodel/fields.py +0 -0
  64. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/dotenvmodel/loading.py +0 -0
  65. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/dotenvmodel/logging_config.py +0 -0
  66. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/dotenvmodel/metaclass.py +0 -0
  67. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/dotenvmodel/py.typed +0 -0
  68. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/dotenvmodel/types.py +0 -0
  69. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/dotenvmodel/validation.py +0 -0
  70. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/examples/advanced_types.py +0 -0
  71. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/examples/basic_usage.py +0 -0
  72. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/examples/describe_documentation.py +0 -0
  73. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/examples/generate_env_example.py +0 -0
  74. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/examples/logging_example.py +0 -0
  75. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/mkdocs.yml +0 -0
  76. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/release-please-config.json +0 -0
  77. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/tests/__init__.py +0 -0
  78. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/tests/conftest.py +0 -0
  79. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/tests/test_basic.py +0 -0
  80. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/tests/test_coercion.py +0 -0
  81. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/tests/test_collection_validators.py +0 -0
  82. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/tests/test_defaults_coercion.py +0 -0
  83. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/tests/test_describe.py +0 -0
  84. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/tests/test_edge_adoption.py +0 -0
  85. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/tests/test_edge_datetime.py +0 -0
  86. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/tests/test_edge_describe_output.py +0 -0
  87. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/tests/test_edge_enum.py +0 -0
  88. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/tests/test_edge_field_options.py +0 -0
  89. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/tests/test_edge_numbers.py +0 -0
  90. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/tests/test_edge_path_options.py +0 -0
  91. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/tests/test_edge_strings.py +0 -0
  92. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/tests/test_empty_collection_items.py +0 -0
  93. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/tests/test_empty_strings.py +0 -0
  94. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/tests/test_enum.py +0 -0
  95. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/tests/test_errors.py +0 -0
  96. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/tests/test_field_constraints.py +0 -0
  97. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/tests/test_future_annotations.py +0 -0
  98. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/tests/test_inheritance.py +0 -0
  99. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/tests/test_json.py +0 -0
  100. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/tests/test_json_and_optional.py +0 -0
  101. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/tests/test_loading.py +0 -0
  102. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/tests/test_logging_config.py +0 -0
  103. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/tests/test_nested_config.py +0 -0
  104. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/tests/test_post_load.py +0 -0
  105. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/tests/test_prefix.py +0 -0
  106. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/tests/test_reload.py +0 -0
  107. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/tests/test_secret_redaction.py +0 -0
  108. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/tests/test_secretstr_security.py +0 -0
  109. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/tests/test_string_affixes.py +0 -0
  110. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/tests/test_strip.py +0 -0
  111. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/tests/test_strip_integration.py +0 -0
  112. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/tests/test_types.py +0 -0
  113. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/tests/test_union_types.py +0 -0
  114. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/tests/test_url_dsn.py +0 -0
  115. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/tests/test_url_password_decoding.py +0 -0
  116. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/tests/test_url_unquote.py +0 -0
  117. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/tests/test_urls.py +0 -0
  118. {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/tests/test_validator.py +0 -0
@@ -0,0 +1,3 @@
1
+ {
2
+ ".": "0.6.2"
3
+ }
@@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.6.2](https://github.com/AZX-PBC-OSS/dotenvmodel/compare/v0.6.1...v0.6.2) (2026-07-28)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * warn on cached() argument disagreement, not on non-default args ([#54](https://github.com/AZX-PBC-OSS/dotenvmodel/issues/54)) ([ba80777](https://github.com/AZX-PBC-OSS/dotenvmodel/commit/ba80777e7486797d8a33d27b124b069f90664de6))
14
+
8
15
  ## [0.6.1](https://github.com/AZX-PBC-OSS/dotenvmodel/compare/v0.6.0...v0.6.1) (2026-07-27)
9
16
 
10
17
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dotenvmodel
3
- Version: 0.6.1
3
+ Version: 0.6.2
4
4
  Summary: Type-safe environment configuration with automatic .env file loading
5
5
  Project-URL: Homepage, https://github.com/AZX-PBC-OSS/dotenvmodel
6
6
  Project-URL: Repository, https://github.com/AZX-PBC-OSS/dotenvmodel
@@ -37,7 +37,7 @@ Public API:
37
37
  Exception hierarchy
38
38
  """
39
39
 
40
- __version__ = "0.6.1" # x-release-please-version
40
+ __version__ = "0.6.2" # x-release-please-version
41
41
  __author__ = "AZX, PBC."
42
42
  __email__ = "oss@azx.io"
43
43
  __license__ = "MIT"
@@ -141,8 +141,8 @@ def acquire_cached(
141
141
 
142
142
  Implements double-checked locking: a lock-free fast path checks
143
143
  ``cls.__dict__``; if the cache is warm, the existing instance is returned
144
- immediately (with a warning if non-default arguments were passed against
145
- an already-warm cache). If the cache is cold, a module-level lock is
144
+ immediately (with a warning if arguments that disagree with the ones
145
+ that populated the cache are passed). If the cache is cold, a module-level lock is
146
146
  acquired and the check is repeated before calling ``cls.load()``.
147
147
 
148
148
  Reentrant calls for the same class from within that class's own
@@ -168,15 +168,28 @@ def acquire_cached(
168
168
  """
169
169
  cached = get_cached(cls)
170
170
  if cached is not None:
171
- if env is not None or override is not True or env_dir is not None:
171
+ # Warn on DISAGREEMENT, not on non-default arguments. An application accessor that
172
+ # consistently passes the same non-default arguments (e.g. `override=False`) on every
173
+ # call is asking for exactly what it already got, and warning it every time would train
174
+ # readers to filter the message out. A caller passing something the cache was NOT built
175
+ # with is the real bug this catches, and it still fires.
176
+ #
177
+ # Compared against the INSTANCE's own record of how it was loaded — the same fields
178
+ # `reload()` reuses — rather than a second copy kept beside the cache. One source of
179
+ # truth, and self-correcting: `reload(env="prod")` updates them, so a later `cached()`
180
+ # is judged against what the cached object actually holds now, not against whatever
181
+ # populated it originally.
182
+ loaded = cached.loaded_with()
183
+ if (env, override, env_dir) != loaded:
172
184
  logger.warning(
173
185
  "cached() called on %s with arguments (env=%r, override=%r, "
174
- "env_dir=%r) but the cache is already populated; "
175
- "arguments were ignored.",
186
+ "env_dir=%r) but the cache is already populated with "
187
+ "(env=%r, override=%r, env_dir=%r); arguments were ignored.",
176
188
  cls.__name__,
177
189
  env,
178
190
  override,
179
191
  env_dir,
192
+ *loaded,
180
193
  )
181
194
  return cached
182
195
 
@@ -567,6 +567,20 @@ class DotEnvConfig(metaclass=ConfigMeta):
567
567
  instance._load_env_dir = env_dir
568
568
  return instance
569
569
 
570
+ def loaded_with(self) -> tuple[str | None, bool, Path | None]:
571
+ """The ``(env, override, env_dir)`` this instance was last loaded with.
572
+
573
+ `reload()` uses it to repeat a load without restating its arguments — so a SIGHUP
574
+ handler calling `reload()` with no arguments keeps the original precedence rather than
575
+ silently reverting to `override=True`. `cached()`'s warm path uses it to tell a caller
576
+ who agrees with how the cache was built from one who disagrees.
577
+
578
+ Exposed rather than read field-by-field so there is one definition of "how was this
579
+ loaded", and callers outside this class do not reach into three private attributes.
580
+ Values reflect the most recent `reload()`, not only the original `load()`.
581
+ """
582
+ return (self._load_env, self._load_override, self._load_env_dir)
583
+
570
584
  def reload(
571
585
  self,
572
586
  env: str | None = None,
@@ -628,9 +642,10 @@ class DotEnvConfig(metaclass=ConfigMeta):
628
642
  """
629
643
  logger.info(f"Reloading {self.__class__.__name__} configuration")
630
644
 
631
- reload_env = env if env is not None else self._load_env
632
- reload_override = override if override is not None else self._load_override
633
- reload_env_dir = env_dir if env_dir is not None else self._load_env_dir
645
+ loaded_env, loaded_override, loaded_env_dir = self.loaded_with()
646
+ reload_env = env if env is not None else loaded_env
647
+ reload_override = override if override is not None else loaded_override
648
+ reload_env_dir = env_dir if env_dir is not None else loaded_env_dir
634
649
 
635
650
  load_env_files(env=reload_env, override=reload_override, env_dir=reload_env_dir)
636
651
 
@@ -709,8 +724,8 @@ class DotEnvConfig(metaclass=ConfigMeta):
709
724
  calls `load()`, the rest block and receive the same instance. Subsequent
710
725
  calls (from any thread) return the cached instance immediately without
711
726
  re-reading the environment, ignoring any arguments passed after the first
712
- call (a warning is logged if non-default arguments are passed against an
713
- already-warm cache).
727
+ call (a warning is logged if arguments that disagree with the ones
728
+ that populated the cache are passed against an already-warm cache).
714
729
 
715
730
  The cached instance is stored as a private class attribute on the config
716
731
  class itself (not in a module-level registry), so its lifetime is tied
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "dotenvmodel"
7
- version = "0.6.1"
7
+ version = "0.6.2"
8
8
  description = "Type-safe environment configuration with automatic .env file loading"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -162,8 +162,10 @@ class TestCached:
162
162
  assert_type(config, MyConfig)
163
163
  assert config.value == "typed"
164
164
 
165
- def test_warning_logged_when_non_default_args_on_warm_cache(self, monkeypatch, caplog) -> None:
166
- """cached() logs a warning when non-default args are passed against a warm cache."""
165
+ def test_warning_logged_when_args_disagree_with_the_warm_cache(
166
+ self, monkeypatch, caplog
167
+ ) -> None:
168
+ """cached() warns when a caller's args differ from the ones that populated the cache."""
167
169
 
168
170
  class Config(DotEnvConfig):
169
171
  value: str = Field(default="x")
@@ -176,6 +178,108 @@ class TestCached:
176
178
  assert result is Config.cached()
177
179
  assert any("arguments were ignored" in r.message for r in caplog.records)
178
180
 
181
+ def test_repeating_the_arguments_the_cache_was_built_with_is_silent(
182
+ self, monkeypatch, caplog
183
+ ) -> None:
184
+ """Asking for what you already got is not a mistake and must not be reported as one.
185
+
186
+ The motivating case is an application accessor that must pass ``override=False`` (the
187
+ process environment beats ``.env`` files, per 12-factor) on every call. Warning it every
188
+ time emits a line per settings access for entirely correct usage — and a warning that
189
+ fires on correct usage is one readers learn to filter out, costing it the job it exists
190
+ for, which ``test_warning_logged_when_args_disagree_with_the_warm_cache`` still covers.
191
+
192
+ Asserts on WHETHER anything was reported, never on the wording: the promise is silence,
193
+ not a particular sentence, and a reworded message must not fail this test.
194
+ """
195
+
196
+ class Config(DotEnvConfig):
197
+ value: str = Field(default="x")
198
+
199
+ args = {"env": "prod", "override": False, "env_dir": Path("/tmp")}
200
+ first = Config.cached(**args)
201
+
202
+ # Window opened AFTER the populating call, so only the repeats are observed.
203
+ with caplog.at_level("WARNING", logger="dotenvmodel"):
204
+ caplog.clear()
205
+ second = Config.cached(**args)
206
+ third = Config.cached(**args)
207
+ repeats_reported = list(caplog.records)
208
+
209
+ assert second is first
210
+ assert third is first
211
+ assert repeats_reported == []
212
+
213
+ def test_a_bare_reload_keeps_the_precedence_the_cache_was_loaded_with(
214
+ self, monkeypatch, caplog
215
+ ) -> None:
216
+ """The SIGHUP shape: ``reload()`` with no arguments must not revert to ``override=True``.
217
+
218
+ A hot-reload handler calls ``reload()`` bare. If that silently reset precedence to
219
+ "``.env`` files beat the process environment", a running service would flip to the
220
+ opposite configuration on a signal it was told was a no-op refresh — and an accessor
221
+ passing ``override=False`` would then also start warning, having never changed.
222
+ """
223
+
224
+ class Config(DotEnvConfig):
225
+ value: str = Field(default="x")
226
+
227
+ instance = Config.cached(override=False, env_dir=Path("/tmp"))
228
+ assert instance.loaded_with() == (None, False, Path("/tmp"))
229
+
230
+ instance.reload()
231
+
232
+ assert instance.loaded_with() == (None, False, Path("/tmp"))
233
+ with caplog.at_level("WARNING", logger="dotenvmodel"):
234
+ caplog.clear()
235
+ assert Config.cached(override=False, env_dir=Path("/tmp")) is instance
236
+ reported = list(caplog.records)
237
+ assert reported == []
238
+
239
+ def test_a_reset_cache_is_not_judged_against_the_arguments_it_dropped(
240
+ self, monkeypatch, caplog
241
+ ) -> None:
242
+ """After ``reset_cached()`` the next call repopulates, so it cannot disagree with anything."""
243
+
244
+ class Config(DotEnvConfig):
245
+ value: str = Field(default="x")
246
+
247
+ Config.cached(override=False)
248
+ Config.reset_cached()
249
+ # Repopulate OUTSIDE the window: a real load emits its own unrelated warnings (e.g. "no
250
+ # .env files found"), and this test is about the warm path, not about loading.
251
+ Config.cached(env="prod")
252
+
253
+ with caplog.at_level("WARNING", logger="dotenvmodel"):
254
+ caplog.clear()
255
+ Config.cached(env="prod")
256
+ reported = list(caplog.records)
257
+
258
+ assert reported == []
259
+
260
+ def test_a_scoped_override_leaves_the_warm_cache_judged_as_before(
261
+ self, monkeypatch, caplog
262
+ ) -> None:
263
+ """Exiting ``cached_override()`` restores the pre-override state, warnings included."""
264
+
265
+ class Config(DotEnvConfig):
266
+ value: str = Field(default="x")
267
+
268
+ Config.cached(override=False)
269
+ with Config.cached_override(Config.load_from_dict({"VALUE": "y"})):
270
+ pass
271
+
272
+ with caplog.at_level("WARNING", logger="dotenvmodel"):
273
+ caplog.clear()
274
+ Config.cached(override=False)
275
+ agreeing = list(caplog.records)
276
+ caplog.clear()
277
+ Config.cached(env="prod")
278
+ disagreeing = list(caplog.records)
279
+
280
+ assert agreeing == []
281
+ assert len(disagreeing) == 1
282
+
179
283
  def test_thread_safety_concurrent_first_access(self, monkeypatch) -> None:
180
284
  """Concurrent first callers all receive the same single instance."""
181
285
  import time
@@ -245,7 +245,7 @@ wheels = [
245
245
 
246
246
  [[package]]
247
247
  name = "dotenvmodel"
248
- version = "0.6.1"
248
+ version = "0.6.2"
249
249
  source = { editable = "." }
250
250
  dependencies = [
251
251
  { name = "python-dotenv" },
@@ -1,3 +0,0 @@
1
- {
2
- ".": "0.6.1"
3
- }
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