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.
- dotenvmodel-0.6.2/.release-please-manifest.json +3 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/CHANGELOG.md +7 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/PKG-INFO +1 -1
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/dotenvmodel/__init__.py +1 -1
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/dotenvmodel/caching.py +18 -5
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/dotenvmodel/config.py +20 -5
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/pyproject.toml +1 -1
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/tests/test_cached.py +106 -2
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/uv.lock +1 -1
- dotenvmodel-0.6.1/.release-please-manifest.json +0 -3
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/.github/CODEOWNERS +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/.github/dependabot.yml +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/.github/pull_request_template.md +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/.github/workflows/ci.yml +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/.github/workflows/docs.yml +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/.github/workflows/publish.yml +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/.github/workflows/release-please.yml +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/.gitignore +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/.python-version +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/CODE_OF_CONDUCT.md +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/CONTRIBUTING.md +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/LICENSE +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/Makefile +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/README.md +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/SECURITY.md +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/docs/api-reference/caching.md +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/docs/api-reference/coercion.md +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/docs/api-reference/config.md +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/docs/api-reference/constants.md +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/docs/api-reference/describe-formatters.md +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/docs/api-reference/describe-renderers.md +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/docs/api-reference/describe.md +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/docs/api-reference/dotenvmodel.md +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/docs/api-reference/exceptions.md +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/docs/api-reference/fields.md +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/docs/api-reference/loading.md +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/docs/api-reference/logging-config.md +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/docs/api-reference/metaclass.md +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/docs/api-reference/types.md +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/docs/api-reference/validation.md +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/docs/changelog.md +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/docs/examples/complete-app.md +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/docs/getting-started/installation.md +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/docs/getting-started/quick-start.md +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/docs/guides/configuration-docs.md +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/docs/guides/error-handling.md +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/docs/guides/fields.md +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/docs/guides/loading.md +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/docs/guides/logging.md +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/docs/guides/prefixes.md +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/docs/guides/types.md +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/docs/guides/validation.md +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/docs/index.md +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/dotenvmodel/_constants.py +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/dotenvmodel/_redaction.py +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/dotenvmodel/coercion.py +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/dotenvmodel/describe/__init__.py +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/dotenvmodel/describe/formatters.py +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/dotenvmodel/describe/renderers.py +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/dotenvmodel/exceptions.py +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/dotenvmodel/fields.py +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/dotenvmodel/loading.py +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/dotenvmodel/logging_config.py +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/dotenvmodel/metaclass.py +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/dotenvmodel/py.typed +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/dotenvmodel/types.py +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/dotenvmodel/validation.py +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/examples/advanced_types.py +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/examples/basic_usage.py +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/examples/describe_documentation.py +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/examples/generate_env_example.py +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/examples/logging_example.py +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/mkdocs.yml +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/release-please-config.json +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/tests/__init__.py +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/tests/conftest.py +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/tests/test_basic.py +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/tests/test_coercion.py +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/tests/test_collection_validators.py +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/tests/test_defaults_coercion.py +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/tests/test_describe.py +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/tests/test_edge_adoption.py +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/tests/test_edge_datetime.py +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/tests/test_edge_describe_output.py +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/tests/test_edge_enum.py +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/tests/test_edge_field_options.py +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/tests/test_edge_numbers.py +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/tests/test_edge_path_options.py +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/tests/test_edge_strings.py +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/tests/test_empty_collection_items.py +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/tests/test_empty_strings.py +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/tests/test_enum.py +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/tests/test_errors.py +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/tests/test_field_constraints.py +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/tests/test_future_annotations.py +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/tests/test_inheritance.py +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/tests/test_json.py +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/tests/test_json_and_optional.py +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/tests/test_loading.py +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/tests/test_logging_config.py +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/tests/test_nested_config.py +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/tests/test_post_load.py +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/tests/test_prefix.py +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/tests/test_reload.py +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/tests/test_secret_redaction.py +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/tests/test_secretstr_security.py +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/tests/test_string_affixes.py +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/tests/test_strip.py +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/tests/test_strip_integration.py +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/tests/test_types.py +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/tests/test_union_types.py +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/tests/test_url_dsn.py +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/tests/test_url_password_decoding.py +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/tests/test_url_unquote.py +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/tests/test_urls.py +0 -0
- {dotenvmodel-0.6.1 → dotenvmodel-0.6.2}/tests/test_validator.py +0 -0
|
@@ -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.
|
|
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
|
|
@@ -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
|
|
145
|
-
|
|
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
|
-
|
|
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
|
-
|
|
632
|
-
|
|
633
|
-
|
|
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
|
|
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
|
|
@@ -162,8 +162,10 @@ class TestCached:
|
|
|
162
162
|
assert_type(config, MyConfig)
|
|
163
163
|
assert config.value == "typed"
|
|
164
164
|
|
|
165
|
-
def
|
|
166
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|