Skip to content

Commit d4f24cc

Browse files
authored
Merge pull request #141 from libranet/feat/quiet-env-var
feat: AUTOREAD_DOTENV_QUIET env var to silence warnings at startup
2 parents 0935a5e + 071808b commit d4f24cc

6 files changed

Lines changed: 110 additions & 10 deletions

File tree

docs/changes.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,13 @@ All notable changes to this project will be documented in this file.
44

55
## 1.0.7 (unreleased)
66

7+
- Add an `AUTOREAD_DOTENV_QUIET` environment-variable. When truthy (`1`/`true`/`yes`,
8+
parsed by `str_to_bool()`), `entrypoint()` installs a process-wide `ignore` filter for the
9+
`AutoreadDotenvWarning` category before doing anything else, silencing every warning the
10+
package emits - the missing-`.env` notice and the genuine misconfiguration warnings alike.
11+
This is the startup-time knob that `PYTHONWARNINGS` cannot provide. Like the other
12+
`AUTOREAD_*` variables it must be set outside `.env`. Documented in `docs/configuration.md`.
13+
714
- Emit every runtime warning under a dedicated `AutoreadDotenvWarning` category (a
815
`UserWarning` subclass) instead of a bare `UserWarning`. It lives in
916
`autoread_dotenv.warnings` and is re-exported as `autoread_dotenv.AutoreadDotenvWarning`

docs/configuration.md

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,30 @@ Set it to `0` when the surrounding environment (CI secrets, systemd `Environment
5353
export AUTOREAD_ENFORCE_DOTENV=0
5454
```
5555

56+
## `AUTOREAD_DOTENV_QUIET`
57+
58+
Suppress every warning `autoread-dotenv` emits. When truthy, [`entrypoint()`](reference/index.md)
59+
installs a process-wide `ignore` filter for the
60+
[`AutoreadDotenvWarning`](reference/warnings.md) category before it does anything else.
61+
62+
- **Default:** `0` (false) - warnings are shown.
63+
- **True values:** `1`, `true`, `yes` (case-insensitive).
64+
- **False values:** `0`, `false`, `no`, `""` (empty).
65+
- **Anything else:** treated as false, *and a warning is emitted* (typo guard) - so a
66+
misspelled value like `AUTOREAD_DOTENV_QUIET=ture` still warns once.
67+
- **Read by:** [`entrypoint()`](reference/index.md), parsed via
68+
[`str_to_bool()`](reference/utils.md); the name lives in
69+
`autoread_dotenv.utils.AUTOREAD_DOTENV_QUIET_VAR`.
70+
71+
```bash
72+
export AUTOREAD_DOTENV_QUIET=1
73+
```
74+
75+
This is the blunt instrument: it hides the missing-`.env` notice together with the genuine
76+
misconfiguration warnings (`python-dotenv` not installed, an unreadable `.env`, a typo'd
77+
boolean elsewhere). Reach for it when the process legitimately runs without a `.env` and you
78+
have accepted that trade-off; otherwise prefer removing the cause (see below).
79+
5680
## Silencing warnings
5781

5882
`autoread-dotenv` never raises for a configuration problem - it emits a warning and records
@@ -82,19 +106,22 @@ of your code. An in-process `filterwarnings()` call therefore only affects a lat
82106
`entrypoint()` invocation - it cannot retroactively silence the startup pass. Use the options
83107
in the next section for that.
84108

85-
### At startup (`PYTHONWARNINGS` / `-W`)
109+
### At startup
86110

87-
`PYTHONWARNINGS` and `-W` **cannot** name `AutoreadDotenvWarning`. The interpreter parses
88-
warning filters before `site` puts `site-packages` on `sys.path`, so a third-party category
89-
cannot be imported yet and the whole filter is silently dropped
90-
(`Invalid -W option ignored: invalid module name: 'autoread_dotenv'`). Only built-in
91-
categories resolve there:
111+
Set [`AUTOREAD_DOTENV_QUIET=1`](#autoread_dotenv_quiet). It is read at the very top of
112+
`entrypoint()`, so it covers the startup pass that an in-process `filterwarnings()` call
113+
misses:
92114

93115
```bash
94-
# broad - silences every UserWarning in the process, not just ours
95-
export PYTHONWARNINGS="ignore::UserWarning"
116+
export AUTOREAD_DOTENV_QUIET=1
96117
```
97118

119+
`PYTHONWARNINGS` and `-W` are *not* an option here: they **cannot** name
120+
`AutoreadDotenvWarning`. The interpreter parses warning filters before `site` puts
121+
`site-packages` on `sys.path`, so a third-party category cannot be imported yet and the whole
122+
filter is silently dropped (`Invalid -W option ignored: invalid module name: 'autoread_dotenv'`).
123+
Only built-in categories resolve there, so the closest `PYTHONWARNINGS` equivalent is the much broader `ignore::UserWarning`.
124+
98125
### Better: remove the cause
99126

100127
The most common warning is the missing-`.env` notice. Rather than muting it, point the loader

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ test-pypi = "https://test.pypi.org/project/autoread-dotenv/"
127127

128128

129129
[tool.codespell]
130-
ignore-words-list = "fasle, hove"
130+
ignore-words-list = "fasle, hove,ture"
131131
skip = "uv.lock, var"
132132

133133

src/autoread_dotenv/__init__.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,12 @@
4343
import pathlib as pl
4444

4545
from autoread_dotenv.status import LoadStatus
46-
from autoread_dotenv.utils import get_dotenv_path, get_expected_dotenv_path, str_to_bool
46+
from autoread_dotenv.utils import (
47+
AUTOREAD_DOTENV_QUIET_VAR,
48+
get_dotenv_path,
49+
get_expected_dotenv_path,
50+
str_to_bool,
51+
)
4752
from autoread_dotenv.warnings import AutoreadDotenvWarning, simple_warning
4853

4954
__all__: list[str] = [
@@ -70,9 +75,16 @@ def entrypoint() -> LoadStatus:
7075
[`AutoreadDotenvWarning`][autoread_dotenv.AutoreadDotenvWarning] category (re-exported
7176
here from `autoread_dotenv.warnings`); see the "Silencing warnings" section of
7277
`docs/configuration.md` for how to filter it (and why `PYTHONWARNINGS` cannot).
78+
Setting `AUTOREAD_DOTENV_QUIET=1` suppresses every such warning for the process.
7379
"""
7480
global last_load_status # noqa: PLW0603
7581

82+
if str_to_bool(os.getenv(AUTOREAD_DOTENV_QUIET_VAR, "0")):
83+
# Opt-out: silence every AutoreadDotenvWarning for the rest of this process.
84+
# Installed as a real filter (rather than just skipped here) so warnings emitted
85+
# later in this call - and by any later entrypoint() call - are covered too.
86+
stdlib_warnings.filterwarnings("ignore", category=AutoreadDotenvWarning)
87+
7688
dotenv_file: pl.Path | None = get_dotenv_path()
7789

7890
if not dotenv_file:

src/autoread_dotenv/utils.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,12 @@
1919
#: "load an unexpected path" crosses no privilege boundary. See docs/security.md.
2020
AUTOREAD_DOTENV_PATH_VAR: str = "AUTOREAD_DOTENV_PATH"
2121

22+
#: When truthy (parsed by str_to_bool), entrypoint() installs a process-wide filter that
23+
#: silences every AutoreadDotenvWarning - the missing-.env notice and the genuine
24+
#: misconfiguration warnings alike. Like AUTOREAD_DOTENV_PATH it cannot live in .env itself
25+
#: (it is read before .env is parsed). See docs/configuration.md.
26+
AUTOREAD_DOTENV_QUIET_VAR: str = "AUTOREAD_DOTENV_QUIET"
27+
2228
#: Recognized spellings for str_to_bool(), case-insensitive.
2329
TRUE_VALUES: frozenset[str] = frozenset({"1", "true", "yes"})
2430
FALSE_VALUES: frozenset[str] = frozenset({"0", "false", "no", ""})

tests/test_autoread.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,54 @@ def test_entrypoint_missing_dotenv_reports_status(tmp_path: pl.Path, monkeypatch
185185
assert warning_list[-1].category is AutoreadDotenvWarning
186186

187187

188+
def test_entrypoint_quiet_env_suppresses_missing_warning(tmp_path: pl.Path, monkeypatch) -> None:
189+
"""AUTOREAD_DOTENV_QUIET=1 silences the warning but leaves the LoadStatus intact."""
190+
import autoread_dotenv
191+
from autoread_dotenv import LoadStatus, entrypoint
192+
193+
monkeypatch.setenv("AUTOREAD_DOTENV_PATH", str(tmp_path / "does-not-exist.env"))
194+
monkeypatch.setenv("AUTOREAD_DOTENV_QUIET", "1")
195+
196+
with warnings.catch_warnings(record=True) as warning_list:
197+
warnings.simplefilter("always")
198+
status = entrypoint()
199+
200+
assert status is LoadStatus.MISSING
201+
assert autoread_dotenv.last_load_status is LoadStatus.MISSING
202+
assert warning_list == []
203+
204+
205+
def test_entrypoint_quiet_env_falsey_still_warns(tmp_path: pl.Path, monkeypatch) -> None:
206+
"""An explicit AUTOREAD_DOTENV_QUIET=0 must not suppress anything."""
207+
from autoread_dotenv import entrypoint
208+
209+
monkeypatch.setenv("AUTOREAD_DOTENV_PATH", str(tmp_path / "does-not-exist.env"))
210+
monkeypatch.setenv("AUTOREAD_DOTENV_QUIET", "0")
211+
212+
with warnings.catch_warnings(record=True) as warning_list:
213+
warnings.simplefilter("always")
214+
entrypoint()
215+
216+
assert len(warning_list) == 1
217+
assert "does not exist" in str(warning_list[-1].message)
218+
219+
220+
@pytest.mark.usefixtures("dotenv_project")
221+
def test_entrypoint_quiet_env_suppresses_unrelated_warning(monkeypatch) -> None:
222+
"""QUIET covers every AutoreadDotenvWarning, not just the missing-.env one."""
223+
from autoread_dotenv import LoadStatus, entrypoint
224+
225+
monkeypatch.setenv("AUTOREAD_DOTENV_QUIET", "1")
226+
monkeypatch.setenv("AUTOREAD_ENFORCE_DOTENV", "ture") # would warn: unrecognized boolean
227+
228+
with warnings.catch_warnings(record=True) as warning_list:
229+
warnings.simplefilter("always")
230+
status = entrypoint()
231+
232+
assert status is LoadStatus.LOADED
233+
assert warning_list == []
234+
235+
188236
def test_get_dotenv_path_permission_error_on_stat(monkeypatch) -> None:
189237
"""A PermissionError raised by is_file() itself must not crash get_dotenv_path().
190238

0 commit comments

Comments
 (0)