envbool 0.4.1__tar.gz → 0.5.0__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.
- envbool-0.5.0/LICENSE +21 -0
- {envbool-0.4.1 → envbool-0.5.0}/PKG-INFO +118 -15
- {envbool-0.4.1 → envbool-0.5.0}/README.md +116 -14
- {envbool-0.4.1 → envbool-0.5.0}/pyproject.toml +2 -1
- {envbool-0.4.1 → envbool-0.5.0}/pyproject.toml.orig +2 -1
- {envbool-0.4.1 → envbool-0.5.0}/src/envbool/__init__.py +14 -11
- {envbool-0.4.1 → envbool-0.5.0}/src/envbool/_cli.py +6 -6
- {envbool-0.4.1 → envbool-0.5.0}/src/envbool/_core.py +33 -16
- {envbool-0.4.1 → envbool-0.5.0}/src/envbool/_defaults.py +16 -16
- {envbool-0.4.1 → envbool-0.5.0}/src/envbool/_env.py +6 -2
- {envbool-0.4.1 → envbool-0.5.0}/src/envbool/exceptions.py +23 -0
- {envbool-0.4.1 → envbool-0.5.0}/src/envbool/__main__.py +0 -0
- {envbool-0.4.1 → envbool-0.5.0}/src/envbool/py.typed +0 -0
envbool-0.5.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Kyle
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: envbool
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Summary: A small Python library and CLI tool for coercing environment variables (and arbitrary strings) into boolean values.
|
|
5
5
|
Keywords: environment variables,boolean,configuration,env,coerce
|
|
6
6
|
Author: Kyle O'Malley
|
|
7
7
|
Author-email: Kyle O'Malley <j.kyle.omalley@gmail.com>
|
|
8
8
|
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
9
10
|
Classifier: Development Status :: 4 - Beta
|
|
10
11
|
Classifier: Intended Audience :: Developers
|
|
11
12
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
@@ -44,9 +45,9 @@ Reading a boolean out of the environment is the kind of thing every project
|
|
|
44
45
|
reinvents, slightly differently, in slightly buggy ways:
|
|
45
46
|
|
|
46
47
|
```python
|
|
47
|
-
DEBUG
|
|
48
|
+
DEBUG = os.environ.get("DEBUG", "").lower() in ("1", "true", "yes")
|
|
48
49
|
VERBOSE = os.environ.get("VERBOSE", "").lower() in ("1", "true", "yes")
|
|
49
|
-
CACHE
|
|
50
|
+
CACHE = os.environ.get("CACHE", "").lower() in ("1", "true", "yes")
|
|
50
51
|
```
|
|
51
52
|
|
|
52
53
|
`envbool` is that snippet, done once and done properly:
|
|
@@ -54,9 +55,9 @@ CACHE = os.environ.get("CACHE", "").lower() in ("1", "true", "yes")
|
|
|
54
55
|
```python
|
|
55
56
|
from envbool import envbool
|
|
56
57
|
|
|
57
|
-
DEBUG
|
|
58
|
+
DEBUG = envbool("DEBUG")
|
|
58
59
|
VERBOSE = envbool("VERBOSE")
|
|
59
|
-
CACHE
|
|
60
|
+
CACHE = envbool("CACHE")
|
|
60
61
|
```
|
|
61
62
|
|
|
62
63
|
## Features
|
|
@@ -90,6 +91,22 @@ pip install envbool
|
|
|
90
91
|
uv add envbool
|
|
91
92
|
```
|
|
92
93
|
|
|
94
|
+
### Snap
|
|
95
|
+
|
|
96
|
+
On Linux, the `envbool` command is also available as a strictly confined
|
|
97
|
+
[snap](https://snapcraft.io/):
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
sudo snap install envbool
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
The snap ships the CLI only; to `import envbool` from your own code, install it
|
|
104
|
+
with `pip` or `uv`. It reads ordinary environment variables unchanged, but
|
|
105
|
+
snapd sets a few variables for every snap, so the CLI sees snapd's values for
|
|
106
|
+
`HOME`, `PATH`, `TMPDIR`, `XDG_RUNTIME_DIR`, and `SNAP_*` rather than yours. If
|
|
107
|
+
both the snap and a pip install are present, whichever of `/snap/bin` or your
|
|
108
|
+
pip `bin` directory comes first on `PATH` wins.
|
|
109
|
+
|
|
93
110
|
## Usage
|
|
94
111
|
|
|
95
112
|
### The basics
|
|
@@ -100,8 +117,8 @@ uv add envbool
|
|
|
100
117
|
```python
|
|
101
118
|
from envbool import envbool
|
|
102
119
|
|
|
103
|
-
DEBUG = envbool("DEBUG")
|
|
104
|
-
CACHE = envbool("CACHE", default=True)
|
|
120
|
+
DEBUG = envbool("DEBUG") # False if unset or empty
|
|
121
|
+
CACHE = envbool("CACHE", default=True) # True if unset or empty
|
|
105
122
|
```
|
|
106
123
|
|
|
107
124
|
The built-in truthy values are `true`, `1`, `yes`, `on`; the falsy values are
|
|
@@ -112,6 +129,9 @@ surrounding whitespace.
|
|
|
112
129
|
|
|
113
130
|
Pass `strict=True` to raise `InvalidBoolValueError` on anything outside the
|
|
114
131
|
truthy/falsy sets — ideal for failing fast on a misconfigured deployment.
|
|
132
|
+
Strict mode also raises `ConflictingValuesError` if the effective truthy and
|
|
133
|
+
falsy sets overlap (e.g. `extend_falsy={"on"}`), on every call, whatever the
|
|
134
|
+
value. Lenient mode instead logs a warning and lets truthy win.
|
|
115
135
|
|
|
116
136
|
```python
|
|
117
137
|
import sys
|
|
@@ -136,6 +156,16 @@ FEATURE = envbool("FEATURE_FLAG", extend_truthy={"enabled", "y"})
|
|
|
136
156
|
LOCALE = envbool("USE_METRIC", truthy={"metric"}, falsy={"imperial"})
|
|
137
157
|
```
|
|
138
158
|
|
|
159
|
+
Each set is built in order: start from the base set (the built-ins, or
|
|
160
|
+
whatever `set_defaults()` configured), swap it out if `truthy`/`falsy` is
|
|
161
|
+
given, then add anything in `extend_truthy`/`extend_falsy`. Passing both
|
|
162
|
+
`truthy` and `extend_truthy` therefore gives you exactly their union:
|
|
163
|
+
|
|
164
|
+
```python
|
|
165
|
+
to_bool("y", truthy={"yes"}, extend_truthy={"y"}) # True
|
|
166
|
+
to_bool("true", truthy={"yes"}, extend_truthy={"y"}) # False: built-ins replaced
|
|
167
|
+
```
|
|
168
|
+
|
|
139
169
|
### Coercing arbitrary strings
|
|
140
170
|
|
|
141
171
|
Use `to_bool` for values that don't come from the environment. It accepts the
|
|
@@ -144,8 +174,8 @@ same keyword arguments as `envbool`.
|
|
|
144
174
|
```python
|
|
145
175
|
from envbool import to_bool
|
|
146
176
|
|
|
147
|
-
to_bool("yes")
|
|
148
|
-
to_bool("0")
|
|
177
|
+
to_bool("yes") # True
|
|
178
|
+
to_bool("0") # False
|
|
149
179
|
to_bool("maybe", strict=True) # raises InvalidBoolValueError
|
|
150
180
|
```
|
|
151
181
|
|
|
@@ -176,6 +206,52 @@ built-in defaults → set_defaults() → function arguments / CLI flags
|
|
|
176
206
|
`reset_defaults()` restores the built-ins — call it in a test fixture (see
|
|
177
207
|
[Testing code that uses envbool](#testing-code-that-uses-envbool)).
|
|
178
208
|
|
|
209
|
+
### Loading application settings
|
|
210
|
+
|
|
211
|
+
In a real application, read every flag once at startup into a single settings
|
|
212
|
+
object, with the policy set up front. With strict mode on, a typo like
|
|
213
|
+
`DEBUG=ture` stops startup instead of quietly reading as `False`:
|
|
214
|
+
|
|
215
|
+
```python
|
|
216
|
+
import sys
|
|
217
|
+
from dataclasses import dataclass
|
|
218
|
+
|
|
219
|
+
import envbool
|
|
220
|
+
from envbool import EnvBoolError
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
@dataclass(frozen=True)
|
|
224
|
+
class Settings:
|
|
225
|
+
debug: bool
|
|
226
|
+
use_cache: bool
|
|
227
|
+
new_checkout: bool
|
|
228
|
+
send_emails: bool
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def load_settings() -> Settings:
|
|
232
|
+
envbool.set_defaults(
|
|
233
|
+
strict=True,
|
|
234
|
+
extend_truthy=["enabled"],
|
|
235
|
+
extend_falsy=["disabled"],
|
|
236
|
+
)
|
|
237
|
+
return Settings(
|
|
238
|
+
debug=envbool.envbool("DEBUG"), # off unless set
|
|
239
|
+
use_cache=envbool.envbool("USE_CACHE", default=True), # on unless set
|
|
240
|
+
new_checkout=envbool.envbool("FEATURE_NEW_CHECKOUT"),
|
|
241
|
+
send_emails=envbool.envbool("SEND_EMAILS", required=True), # must be set
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
try:
|
|
246
|
+
SETTINGS = load_settings()
|
|
247
|
+
except EnvBoolError as e:
|
|
248
|
+
sys.exit(f"Invalid configuration: {e}")
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
Catching `EnvBoolError` covers every failure: a bad value, a missing
|
|
252
|
+
`required` variable, or overlapping value sets. The rest of the application
|
|
253
|
+
reads `SETTINGS.debug` and never touches `os.environ` again.
|
|
254
|
+
|
|
179
255
|
> Through 0.3.x, envbool read TOML config files (`envbool.toml`,
|
|
180
256
|
> `[tool.envbool]`). 0.4.0 removed them in favor of `set_defaults()` — see
|
|
181
257
|
> `CHANGELOG.md` for the rationale and migration note.
|
|
@@ -226,8 +302,10 @@ options:
|
|
|
226
302
|
--truthy VALUE Replace the truthy set with VALUE (repeatable).
|
|
227
303
|
--falsy VALUE Replace the falsy set with VALUE (repeatable).
|
|
228
304
|
--extend-truthy VALUE
|
|
229
|
-
Add VALUE to the truthy set
|
|
230
|
-
|
|
305
|
+
Add VALUE to the truthy set, after any --truthy
|
|
306
|
+
(repeatable).
|
|
307
|
+
--extend-falsy VALUE Add VALUE to the falsy set, after any --falsy
|
|
308
|
+
(repeatable).
|
|
231
309
|
```
|
|
232
310
|
|
|
233
311
|
A few rules worth knowing:
|
|
@@ -241,6 +319,29 @@ A few rules worth knowing:
|
|
|
241
319
|
- With no `VAR_NAME`, `--value`, or non-empty piped stdin, the CLI prints
|
|
242
320
|
usage and exits `2`.
|
|
243
321
|
|
|
322
|
+
### Scripts using `set -e`
|
|
323
|
+
|
|
324
|
+
Under `set -e` (errexit), a falsy result is a failing command: a bare
|
|
325
|
+
`envbool FLAG` on its own line aborts the script when the flag is off. Check
|
|
326
|
+
the status inside a condition instead, where errexit doesn't apply, or use
|
|
327
|
+
`--print` to get the answer as text:
|
|
328
|
+
|
|
329
|
+
```bash
|
|
330
|
+
set -e
|
|
331
|
+
|
|
332
|
+
envbool FLAG # aborts the script when FLAG is falsy
|
|
333
|
+
|
|
334
|
+
if envbool FLAG; then # safe: the status is the condition
|
|
335
|
+
echo "on"
|
|
336
|
+
fi
|
|
337
|
+
|
|
338
|
+
flag=$(envbool --print FLAG) # safe: always exits 0 unless there's an error
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
`--print` still exits `2` on an error, so `set -e` catches a missing
|
|
342
|
+
`--required` variable or a bad value under `--strict`, while a falsy value
|
|
343
|
+
doesn't stop the script.
|
|
344
|
+
|
|
244
345
|
## API reference
|
|
245
346
|
|
|
246
347
|
| Symbol | Description |
|
|
@@ -255,6 +356,7 @@ A few rules worth knowing:
|
|
|
255
356
|
| `DEFAULT_FALSY` | `frozenset` of the built-in falsy strings. |
|
|
256
357
|
| `EnvBoolError` | Base class for every exception the library raises. |
|
|
257
358
|
| `InvalidBoolValueError` | Raised in strict mode for unrecognized values. Also a `ValueError`. |
|
|
359
|
+
| `ConflictingValuesError` | Raised in strict mode when the truthy and falsy sets overlap. Also a `ValueError`. |
|
|
258
360
|
| `MissingEnvVarError` | Raised by `envbool(required=True)` when the variable is unset. Also a `KeyError`. |
|
|
259
361
|
|
|
260
362
|
`envbool()` and `to_bool()` share the same keyword-only options:
|
|
@@ -265,7 +367,7 @@ A few rules worth knowing:
|
|
|
265
367
|
| `strict` | `bool \| None` | `None` | Raise on unrecognized values (`None` defers to `set_defaults()`). |
|
|
266
368
|
| `warn` | `bool \| None` | `None` | Log a warning on unrecognized values (`None` defers to `set_defaults()`). |
|
|
267
369
|
| `truthy` / `falsy` | `Iterable[str] \| None` | `None` | **Replace** the effective set. |
|
|
268
|
-
| `extend_truthy` / `extend_falsy` | `Iterable[str] \| None` | `None` | **Extend** the effective set. |
|
|
370
|
+
| `extend_truthy` / `extend_falsy` | `Iterable[str] \| None` | `None` | **Extend** the effective set, after any replacement. |
|
|
269
371
|
|
|
270
372
|
`envbool()` also accepts `required` (`bool`, default `False`): when `True`, a
|
|
271
373
|
variable that is unset raises `MissingEnvVarError` before `default` is applied. A
|
|
@@ -284,9 +386,9 @@ from envbool import envbool, InvalidBoolValueError
|
|
|
284
386
|
try:
|
|
285
387
|
result = envbool("MY_VAR", strict=True)
|
|
286
388
|
except InvalidBoolValueError as e:
|
|
287
|
-
print(e.var)
|
|
389
|
+
print(e.var) # "MY_VAR" — env var name, or None when raised from to_bool()
|
|
288
390
|
print(e.value) # "maybe" — the normalized (stripped, lowercased) value
|
|
289
|
-
print(e.truthy)
|
|
391
|
+
print(e.truthy) # frozenset({"true", "1", "yes", "on"}) — effective truthy set
|
|
290
392
|
print(e.falsy) # frozenset({"false", "0", "no", "off"}) — effective falsy set
|
|
291
393
|
```
|
|
292
394
|
|
|
@@ -316,7 +418,7 @@ logging.getLogger("envbool").addHandler(logging.StreamHandler())
|
|
|
316
418
|
| Level | When |
|
|
317
419
|
| --- | --- |
|
|
318
420
|
| `WARNING` | An unrecognized value fell through in lenient mode (only when `warn=True`). |
|
|
319
|
-
| `WARNING` | The truthy and falsy sets overlap (truthy wins). |
|
|
421
|
+
| `WARNING` | The truthy and falsy sets overlap in lenient mode (truthy wins; strict mode raises `ConflictingValuesError` instead). |
|
|
320
422
|
|
|
321
423
|
### The unset-vs-empty distinction
|
|
322
424
|
|
|
@@ -345,6 +447,7 @@ fixture so overrides don't leak across the suite:
|
|
|
345
447
|
import pytest
|
|
346
448
|
from envbool import reset_defaults
|
|
347
449
|
|
|
450
|
+
|
|
348
451
|
@pytest.fixture(autouse=True)
|
|
349
452
|
def _reset_envbool_defaults():
|
|
350
453
|
yield
|
|
@@ -17,9 +17,9 @@ Reading a boolean out of the environment is the kind of thing every project
|
|
|
17
17
|
reinvents, slightly differently, in slightly buggy ways:
|
|
18
18
|
|
|
19
19
|
```python
|
|
20
|
-
DEBUG
|
|
20
|
+
DEBUG = os.environ.get("DEBUG", "").lower() in ("1", "true", "yes")
|
|
21
21
|
VERBOSE = os.environ.get("VERBOSE", "").lower() in ("1", "true", "yes")
|
|
22
|
-
CACHE
|
|
22
|
+
CACHE = os.environ.get("CACHE", "").lower() in ("1", "true", "yes")
|
|
23
23
|
```
|
|
24
24
|
|
|
25
25
|
`envbool` is that snippet, done once and done properly:
|
|
@@ -27,9 +27,9 @@ CACHE = os.environ.get("CACHE", "").lower() in ("1", "true", "yes")
|
|
|
27
27
|
```python
|
|
28
28
|
from envbool import envbool
|
|
29
29
|
|
|
30
|
-
DEBUG
|
|
30
|
+
DEBUG = envbool("DEBUG")
|
|
31
31
|
VERBOSE = envbool("VERBOSE")
|
|
32
|
-
CACHE
|
|
32
|
+
CACHE = envbool("CACHE")
|
|
33
33
|
```
|
|
34
34
|
|
|
35
35
|
## Features
|
|
@@ -63,6 +63,22 @@ pip install envbool
|
|
|
63
63
|
uv add envbool
|
|
64
64
|
```
|
|
65
65
|
|
|
66
|
+
### Snap
|
|
67
|
+
|
|
68
|
+
On Linux, the `envbool` command is also available as a strictly confined
|
|
69
|
+
[snap](https://snapcraft.io/):
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
sudo snap install envbool
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
The snap ships the CLI only; to `import envbool` from your own code, install it
|
|
76
|
+
with `pip` or `uv`. It reads ordinary environment variables unchanged, but
|
|
77
|
+
snapd sets a few variables for every snap, so the CLI sees snapd's values for
|
|
78
|
+
`HOME`, `PATH`, `TMPDIR`, `XDG_RUNTIME_DIR`, and `SNAP_*` rather than yours. If
|
|
79
|
+
both the snap and a pip install are present, whichever of `/snap/bin` or your
|
|
80
|
+
pip `bin` directory comes first on `PATH` wins.
|
|
81
|
+
|
|
66
82
|
## Usage
|
|
67
83
|
|
|
68
84
|
### The basics
|
|
@@ -73,8 +89,8 @@ uv add envbool
|
|
|
73
89
|
```python
|
|
74
90
|
from envbool import envbool
|
|
75
91
|
|
|
76
|
-
DEBUG = envbool("DEBUG")
|
|
77
|
-
CACHE = envbool("CACHE", default=True)
|
|
92
|
+
DEBUG = envbool("DEBUG") # False if unset or empty
|
|
93
|
+
CACHE = envbool("CACHE", default=True) # True if unset or empty
|
|
78
94
|
```
|
|
79
95
|
|
|
80
96
|
The built-in truthy values are `true`, `1`, `yes`, `on`; the falsy values are
|
|
@@ -85,6 +101,9 @@ surrounding whitespace.
|
|
|
85
101
|
|
|
86
102
|
Pass `strict=True` to raise `InvalidBoolValueError` on anything outside the
|
|
87
103
|
truthy/falsy sets — ideal for failing fast on a misconfigured deployment.
|
|
104
|
+
Strict mode also raises `ConflictingValuesError` if the effective truthy and
|
|
105
|
+
falsy sets overlap (e.g. `extend_falsy={"on"}`), on every call, whatever the
|
|
106
|
+
value. Lenient mode instead logs a warning and lets truthy win.
|
|
88
107
|
|
|
89
108
|
```python
|
|
90
109
|
import sys
|
|
@@ -109,6 +128,16 @@ FEATURE = envbool("FEATURE_FLAG", extend_truthy={"enabled", "y"})
|
|
|
109
128
|
LOCALE = envbool("USE_METRIC", truthy={"metric"}, falsy={"imperial"})
|
|
110
129
|
```
|
|
111
130
|
|
|
131
|
+
Each set is built in order: start from the base set (the built-ins, or
|
|
132
|
+
whatever `set_defaults()` configured), swap it out if `truthy`/`falsy` is
|
|
133
|
+
given, then add anything in `extend_truthy`/`extend_falsy`. Passing both
|
|
134
|
+
`truthy` and `extend_truthy` therefore gives you exactly their union:
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
to_bool("y", truthy={"yes"}, extend_truthy={"y"}) # True
|
|
138
|
+
to_bool("true", truthy={"yes"}, extend_truthy={"y"}) # False: built-ins replaced
|
|
139
|
+
```
|
|
140
|
+
|
|
112
141
|
### Coercing arbitrary strings
|
|
113
142
|
|
|
114
143
|
Use `to_bool` for values that don't come from the environment. It accepts the
|
|
@@ -117,8 +146,8 @@ same keyword arguments as `envbool`.
|
|
|
117
146
|
```python
|
|
118
147
|
from envbool import to_bool
|
|
119
148
|
|
|
120
|
-
to_bool("yes")
|
|
121
|
-
to_bool("0")
|
|
149
|
+
to_bool("yes") # True
|
|
150
|
+
to_bool("0") # False
|
|
122
151
|
to_bool("maybe", strict=True) # raises InvalidBoolValueError
|
|
123
152
|
```
|
|
124
153
|
|
|
@@ -149,6 +178,52 @@ built-in defaults → set_defaults() → function arguments / CLI flags
|
|
|
149
178
|
`reset_defaults()` restores the built-ins — call it in a test fixture (see
|
|
150
179
|
[Testing code that uses envbool](#testing-code-that-uses-envbool)).
|
|
151
180
|
|
|
181
|
+
### Loading application settings
|
|
182
|
+
|
|
183
|
+
In a real application, read every flag once at startup into a single settings
|
|
184
|
+
object, with the policy set up front. With strict mode on, a typo like
|
|
185
|
+
`DEBUG=ture` stops startup instead of quietly reading as `False`:
|
|
186
|
+
|
|
187
|
+
```python
|
|
188
|
+
import sys
|
|
189
|
+
from dataclasses import dataclass
|
|
190
|
+
|
|
191
|
+
import envbool
|
|
192
|
+
from envbool import EnvBoolError
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
@dataclass(frozen=True)
|
|
196
|
+
class Settings:
|
|
197
|
+
debug: bool
|
|
198
|
+
use_cache: bool
|
|
199
|
+
new_checkout: bool
|
|
200
|
+
send_emails: bool
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def load_settings() -> Settings:
|
|
204
|
+
envbool.set_defaults(
|
|
205
|
+
strict=True,
|
|
206
|
+
extend_truthy=["enabled"],
|
|
207
|
+
extend_falsy=["disabled"],
|
|
208
|
+
)
|
|
209
|
+
return Settings(
|
|
210
|
+
debug=envbool.envbool("DEBUG"), # off unless set
|
|
211
|
+
use_cache=envbool.envbool("USE_CACHE", default=True), # on unless set
|
|
212
|
+
new_checkout=envbool.envbool("FEATURE_NEW_CHECKOUT"),
|
|
213
|
+
send_emails=envbool.envbool("SEND_EMAILS", required=True), # must be set
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
try:
|
|
218
|
+
SETTINGS = load_settings()
|
|
219
|
+
except EnvBoolError as e:
|
|
220
|
+
sys.exit(f"Invalid configuration: {e}")
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
Catching `EnvBoolError` covers every failure: a bad value, a missing
|
|
224
|
+
`required` variable, or overlapping value sets. The rest of the application
|
|
225
|
+
reads `SETTINGS.debug` and never touches `os.environ` again.
|
|
226
|
+
|
|
152
227
|
> Through 0.3.x, envbool read TOML config files (`envbool.toml`,
|
|
153
228
|
> `[tool.envbool]`). 0.4.0 removed them in favor of `set_defaults()` — see
|
|
154
229
|
> `CHANGELOG.md` for the rationale and migration note.
|
|
@@ -199,8 +274,10 @@ options:
|
|
|
199
274
|
--truthy VALUE Replace the truthy set with VALUE (repeatable).
|
|
200
275
|
--falsy VALUE Replace the falsy set with VALUE (repeatable).
|
|
201
276
|
--extend-truthy VALUE
|
|
202
|
-
Add VALUE to the truthy set
|
|
203
|
-
|
|
277
|
+
Add VALUE to the truthy set, after any --truthy
|
|
278
|
+
(repeatable).
|
|
279
|
+
--extend-falsy VALUE Add VALUE to the falsy set, after any --falsy
|
|
280
|
+
(repeatable).
|
|
204
281
|
```
|
|
205
282
|
|
|
206
283
|
A few rules worth knowing:
|
|
@@ -214,6 +291,29 @@ A few rules worth knowing:
|
|
|
214
291
|
- With no `VAR_NAME`, `--value`, or non-empty piped stdin, the CLI prints
|
|
215
292
|
usage and exits `2`.
|
|
216
293
|
|
|
294
|
+
### Scripts using `set -e`
|
|
295
|
+
|
|
296
|
+
Under `set -e` (errexit), a falsy result is a failing command: a bare
|
|
297
|
+
`envbool FLAG` on its own line aborts the script when the flag is off. Check
|
|
298
|
+
the status inside a condition instead, where errexit doesn't apply, or use
|
|
299
|
+
`--print` to get the answer as text:
|
|
300
|
+
|
|
301
|
+
```bash
|
|
302
|
+
set -e
|
|
303
|
+
|
|
304
|
+
envbool FLAG # aborts the script when FLAG is falsy
|
|
305
|
+
|
|
306
|
+
if envbool FLAG; then # safe: the status is the condition
|
|
307
|
+
echo "on"
|
|
308
|
+
fi
|
|
309
|
+
|
|
310
|
+
flag=$(envbool --print FLAG) # safe: always exits 0 unless there's an error
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
`--print` still exits `2` on an error, so `set -e` catches a missing
|
|
314
|
+
`--required` variable or a bad value under `--strict`, while a falsy value
|
|
315
|
+
doesn't stop the script.
|
|
316
|
+
|
|
217
317
|
## API reference
|
|
218
318
|
|
|
219
319
|
| Symbol | Description |
|
|
@@ -228,6 +328,7 @@ A few rules worth knowing:
|
|
|
228
328
|
| `DEFAULT_FALSY` | `frozenset` of the built-in falsy strings. |
|
|
229
329
|
| `EnvBoolError` | Base class for every exception the library raises. |
|
|
230
330
|
| `InvalidBoolValueError` | Raised in strict mode for unrecognized values. Also a `ValueError`. |
|
|
331
|
+
| `ConflictingValuesError` | Raised in strict mode when the truthy and falsy sets overlap. Also a `ValueError`. |
|
|
231
332
|
| `MissingEnvVarError` | Raised by `envbool(required=True)` when the variable is unset. Also a `KeyError`. |
|
|
232
333
|
|
|
233
334
|
`envbool()` and `to_bool()` share the same keyword-only options:
|
|
@@ -238,7 +339,7 @@ A few rules worth knowing:
|
|
|
238
339
|
| `strict` | `bool \| None` | `None` | Raise on unrecognized values (`None` defers to `set_defaults()`). |
|
|
239
340
|
| `warn` | `bool \| None` | `None` | Log a warning on unrecognized values (`None` defers to `set_defaults()`). |
|
|
240
341
|
| `truthy` / `falsy` | `Iterable[str] \| None` | `None` | **Replace** the effective set. |
|
|
241
|
-
| `extend_truthy` / `extend_falsy` | `Iterable[str] \| None` | `None` | **Extend** the effective set. |
|
|
342
|
+
| `extend_truthy` / `extend_falsy` | `Iterable[str] \| None` | `None` | **Extend** the effective set, after any replacement. |
|
|
242
343
|
|
|
243
344
|
`envbool()` also accepts `required` (`bool`, default `False`): when `True`, a
|
|
244
345
|
variable that is unset raises `MissingEnvVarError` before `default` is applied. A
|
|
@@ -257,9 +358,9 @@ from envbool import envbool, InvalidBoolValueError
|
|
|
257
358
|
try:
|
|
258
359
|
result = envbool("MY_VAR", strict=True)
|
|
259
360
|
except InvalidBoolValueError as e:
|
|
260
|
-
print(e.var)
|
|
361
|
+
print(e.var) # "MY_VAR" — env var name, or None when raised from to_bool()
|
|
261
362
|
print(e.value) # "maybe" — the normalized (stripped, lowercased) value
|
|
262
|
-
print(e.truthy)
|
|
363
|
+
print(e.truthy) # frozenset({"true", "1", "yes", "on"}) — effective truthy set
|
|
263
364
|
print(e.falsy) # frozenset({"false", "0", "no", "off"}) — effective falsy set
|
|
264
365
|
```
|
|
265
366
|
|
|
@@ -289,7 +390,7 @@ logging.getLogger("envbool").addHandler(logging.StreamHandler())
|
|
|
289
390
|
| Level | When |
|
|
290
391
|
| --- | --- |
|
|
291
392
|
| `WARNING` | An unrecognized value fell through in lenient mode (only when `warn=True`). |
|
|
292
|
-
| `WARNING` | The truthy and falsy sets overlap (truthy wins). |
|
|
393
|
+
| `WARNING` | The truthy and falsy sets overlap in lenient mode (truthy wins; strict mode raises `ConflictingValuesError` instead). |
|
|
293
394
|
|
|
294
395
|
### The unset-vs-empty distinction
|
|
295
396
|
|
|
@@ -318,6 +419,7 @@ fixture so overrides don't leak across the suite:
|
|
|
318
419
|
import pytest
|
|
319
420
|
from envbool import reset_defaults
|
|
320
421
|
|
|
422
|
+
|
|
321
423
|
@pytest.fixture(autouse=True)
|
|
322
424
|
def _reset_envbool_defaults():
|
|
323
425
|
yield
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "envbool"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.5.0"
|
|
4
4
|
description = "A small Python library and CLI tool for coercing environment variables (and arbitrary strings) into boolean values."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.11"
|
|
7
7
|
license = "MIT"
|
|
8
|
+
license-files = ["LICENSE"]
|
|
8
9
|
keywords = [
|
|
9
10
|
"environment variables",
|
|
10
11
|
"boolean",
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "envbool"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.5.0"
|
|
4
4
|
description = "A small Python library and CLI tool for coercing environment variables (and arbitrary strings) into boolean values."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
authors = [{ name = "Kyle O'Malley", email = "j.kyle.omalley@gmail.com" }]
|
|
7
7
|
requires-python = ">=3.11"
|
|
8
8
|
license = "MIT"
|
|
9
|
+
license-files = ["LICENSE"]
|
|
9
10
|
keywords = ["environment variables", "boolean", "configuration", "env", "coerce"]
|
|
10
11
|
dependencies = []
|
|
11
12
|
classifiers = [
|
|
@@ -9,17 +9,18 @@ For except clauses, envbool.exceptions is also importable by name:
|
|
|
9
9
|
from envbool.exceptions import InvalidBoolValueError
|
|
10
10
|
|
|
11
11
|
Available names:
|
|
12
|
-
envbool()
|
|
13
|
-
to_bool()
|
|
14
|
-
set_defaults()
|
|
15
|
-
get_defaults()
|
|
16
|
-
reset_defaults()
|
|
17
|
-
Defaults
|
|
18
|
-
DEFAULT_TRUTHY
|
|
19
|
-
DEFAULT_FALSY
|
|
20
|
-
EnvBoolError
|
|
21
|
-
InvalidBoolValueError
|
|
22
|
-
|
|
12
|
+
envbool() -- read an env var and coerce to bool (primary API)
|
|
13
|
+
to_bool() -- coerce an arbitrary string to bool (no os.environ)
|
|
14
|
+
set_defaults() -- set process-level strict/warn/truthy/falsy defaults
|
|
15
|
+
get_defaults() -- inspect the active process-level Defaults
|
|
16
|
+
reset_defaults() -- restore built-in defaults (for test fixtures)
|
|
17
|
+
Defaults -- frozen dataclass returned by get_defaults()
|
|
18
|
+
DEFAULT_TRUTHY -- built-in truthy set (frozenset)
|
|
19
|
+
DEFAULT_FALSY -- built-in falsy set (frozenset)
|
|
20
|
+
EnvBoolError -- base exception for all envbool errors
|
|
21
|
+
InvalidBoolValueError -- raised in strict mode for unrecognized values
|
|
22
|
+
ConflictingValuesError -- raised in strict mode when truthy/falsy overlap
|
|
23
|
+
MissingEnvVarError -- raised by envbool(required=True) when a var is unset
|
|
23
24
|
"""
|
|
24
25
|
# All implementation lives in private underscore-prefixed modules so the public
|
|
25
26
|
# surface can be reshaped without breaking imports. Do not import from _core,
|
|
@@ -36,6 +37,7 @@ from envbool._defaults import (
|
|
|
36
37
|
)
|
|
37
38
|
from envbool._env import envbool
|
|
38
39
|
from envbool.exceptions import (
|
|
40
|
+
ConflictingValuesError,
|
|
39
41
|
EnvBoolError,
|
|
40
42
|
InvalidBoolValueError,
|
|
41
43
|
MissingEnvVarError,
|
|
@@ -44,6 +46,7 @@ from envbool.exceptions import (
|
|
|
44
46
|
__all__ = [
|
|
45
47
|
"DEFAULT_FALSY",
|
|
46
48
|
"DEFAULT_TRUTHY",
|
|
49
|
+
"ConflictingValuesError",
|
|
47
50
|
"Defaults",
|
|
48
51
|
"EnvBoolError",
|
|
49
52
|
"InvalidBoolValueError",
|
|
@@ -18,8 +18,8 @@ Omitting --strict or --warn defers to the process-level defaults
|
|
|
18
18
|
(envbool.set_defaults(); default: lenient, no warnings).
|
|
19
19
|
|
|
20
20
|
Value sets: --truthy/--falsy (repeatable) replace the truthy/falsy set;
|
|
21
|
-
--extend-truthy/--extend-falsy (repeatable) add to it
|
|
22
|
-
|
|
21
|
+
--extend-truthy/--extend-falsy (repeatable) add to it, after any
|
|
22
|
+
replacement.
|
|
23
23
|
|
|
24
24
|
Public surface:
|
|
25
25
|
main() -- entry point registered as the "envbool" command
|
|
@@ -36,7 +36,7 @@ import sys
|
|
|
36
36
|
|
|
37
37
|
from envbool._core import to_bool
|
|
38
38
|
from envbool._env import envbool
|
|
39
|
-
from envbool.exceptions import
|
|
39
|
+
from envbool.exceptions import EnvBoolError
|
|
40
40
|
|
|
41
41
|
|
|
42
42
|
def _build_parser() -> argparse.ArgumentParser:
|
|
@@ -114,13 +114,13 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
114
114
|
"--extend-truthy",
|
|
115
115
|
metavar="VALUE",
|
|
116
116
|
action="append",
|
|
117
|
-
help="Add VALUE to the truthy set (repeatable).",
|
|
117
|
+
help="Add VALUE to the truthy set, after any --truthy (repeatable).",
|
|
118
118
|
)
|
|
119
119
|
parser.add_argument(
|
|
120
120
|
"--extend-falsy",
|
|
121
121
|
metavar="VALUE",
|
|
122
122
|
action="append",
|
|
123
|
-
help="Add VALUE to the falsy set (repeatable).",
|
|
123
|
+
help="Add VALUE to the falsy set, after any --falsy (repeatable).",
|
|
124
124
|
)
|
|
125
125
|
return parser
|
|
126
126
|
|
|
@@ -213,7 +213,7 @@ def main() -> None:
|
|
|
213
213
|
# SystemExit, which is not caught here and so propagates as intended.
|
|
214
214
|
try:
|
|
215
215
|
result = _coerce_from_source(parser, args)
|
|
216
|
-
except
|
|
216
|
+
except EnvBoolError as e:
|
|
217
217
|
print(f"error: {e}", file=sys.stderr)
|
|
218
218
|
sys.exit(2)
|
|
219
219
|
|
|
@@ -21,10 +21,10 @@ from collections.abc import Iterable
|
|
|
21
21
|
from envbool._defaults import (
|
|
22
22
|
DEFAULT_FALSY,
|
|
23
23
|
DEFAULT_TRUTHY,
|
|
24
|
-
|
|
24
|
+
_apply_replace_then_extend,
|
|
25
25
|
get_defaults,
|
|
26
26
|
)
|
|
27
|
-
from envbool.exceptions import InvalidBoolValueError
|
|
27
|
+
from envbool.exceptions import ConflictingValuesError, InvalidBoolValueError
|
|
28
28
|
|
|
29
29
|
# Module-level logger -- attributed to "envbool._core" so callers can filter it
|
|
30
30
|
# independently from "envbool.config" or the root "envbool" logger.
|
|
@@ -56,8 +56,10 @@ def to_bool(
|
|
|
56
56
|
process-level defaults (set_defaults()) (default False).
|
|
57
57
|
truthy: Replaces the effective truthy set.
|
|
58
58
|
falsy: Replaces the effective falsy set.
|
|
59
|
-
extend_truthy: Extends the effective truthy set
|
|
60
|
-
|
|
59
|
+
extend_truthy: Extends the effective truthy set, after any truthy
|
|
60
|
+
replacement.
|
|
61
|
+
extend_falsy: Extends the effective falsy set, after any falsy
|
|
62
|
+
replacement.
|
|
61
63
|
_var: Internal - env var name for error messages when called via envbool().
|
|
62
64
|
|
|
63
65
|
Returns:
|
|
@@ -65,6 +67,8 @@ def to_bool(
|
|
|
65
67
|
|
|
66
68
|
Raises:
|
|
67
69
|
InvalidBoolValueError: In strict mode when value is unrecognized.
|
|
70
|
+
ConflictingValuesError: In strict mode when the effective truthy and
|
|
71
|
+
falsy sets overlap.
|
|
68
72
|
"""
|
|
69
73
|
# Normalize first so all comparisons are case- and whitespace-insensitive.
|
|
70
74
|
# Empty after normalization means "unset" -- return the caller's default
|
|
@@ -90,10 +94,27 @@ def to_bool(
|
|
|
90
94
|
extend_falsy=extend_falsy,
|
|
91
95
|
)
|
|
92
96
|
|
|
93
|
-
#
|
|
94
|
-
#
|
|
97
|
+
# Three-state logic: True/False at the call site override the process-level
|
|
98
|
+
# default; None defers to whatever set_defaults() last set (which defaults
|
|
99
|
+
# to False if set_defaults() was never called). Resolved before the lookup
|
|
100
|
+
# because the overlap check below also depends on it.
|
|
101
|
+
effective_strict = strict if strict is not None else defaults.strict
|
|
102
|
+
|
|
103
|
+
# Overlapping sets are a configuration mistake. Strict mode promises every
|
|
104
|
+
# accepted value is unambiguous, so it rejects the configuration outright --
|
|
105
|
+
# on every call, not just when the value lands in the overlap, so the
|
|
106
|
+
# mistake surfaces at the first strict read. Lenient mode warns so the
|
|
107
|
+
# problem is visible, then lets truthy win to stay predictable.
|
|
95
108
|
overlap = effective_truthy & effective_falsy
|
|
96
109
|
if overlap:
|
|
110
|
+
if effective_strict:
|
|
111
|
+
err = ConflictingValuesError(
|
|
112
|
+
f"Truthy and falsy sets overlap: {', '.join(sorted(overlap))}"
|
|
113
|
+
)
|
|
114
|
+
err.overlap = overlap
|
|
115
|
+
err.truthy = effective_truthy
|
|
116
|
+
err.falsy = effective_falsy
|
|
117
|
+
raise err
|
|
97
118
|
_logger.warning(
|
|
98
119
|
"Overlapping truthy/falsy values (truthy wins): %s", sorted(overlap)
|
|
99
120
|
)
|
|
@@ -101,15 +122,11 @@ def to_bool(
|
|
|
101
122
|
if normalized in effective_truthy:
|
|
102
123
|
return True
|
|
103
124
|
|
|
104
|
-
# Falsy is checked after truthy so the overlap rule above
|
|
105
|
-
# without any extra branching.
|
|
125
|
+
# Falsy is checked after truthy so the lenient overlap rule above (truthy
|
|
126
|
+
# wins) is enforced without any extra branching.
|
|
106
127
|
if normalized in effective_falsy:
|
|
107
128
|
return False
|
|
108
129
|
|
|
109
|
-
# Three-state logic: True/False at the call site override the process-level
|
|
110
|
-
# default; None defers to whatever set_defaults() last set (which defaults
|
|
111
|
-
# to False if set_defaults() was never called).
|
|
112
|
-
effective_strict = strict if strict is not None else defaults.strict
|
|
113
130
|
if effective_strict:
|
|
114
131
|
truthy_list = ", ".join(sorted(effective_truthy))
|
|
115
132
|
falsy_list = ", ".join(sorted(effective_falsy))
|
|
@@ -155,9 +172,9 @@ def _resolve(
|
|
|
155
172
|
extend_truthy: Iterable[str] | None = None,
|
|
156
173
|
extend_falsy: Iterable[str] | None = None,
|
|
157
174
|
) -> tuple[frozenset[str], frozenset[str]]:
|
|
158
|
-
#
|
|
159
|
-
#
|
|
160
|
-
effective_truthy =
|
|
161
|
-
effective_falsy =
|
|
175
|
+
# Replace-then-extend, per set -- see the _apply_replace_then_extend()
|
|
176
|
+
# docstring for the full precedence rules.
|
|
177
|
+
effective_truthy = _apply_replace_then_extend(config_truthy, truthy, extend_truthy)
|
|
178
|
+
effective_falsy = _apply_replace_then_extend(config_falsy, falsy, extend_falsy)
|
|
162
179
|
|
|
163
180
|
return (effective_truthy, effective_falsy)
|
|
@@ -37,34 +37,34 @@ def _normalize_set(values: Iterable[str]) -> frozenset[str]:
|
|
|
37
37
|
return frozenset(v.strip().lower() for v in values)
|
|
38
38
|
|
|
39
39
|
|
|
40
|
-
def
|
|
40
|
+
def _apply_replace_then_extend(
|
|
41
41
|
base: frozenset[str],
|
|
42
42
|
replace: Iterable[str] | None,
|
|
43
43
|
extend: Iterable[str] | None,
|
|
44
44
|
) -> frozenset[str]:
|
|
45
|
-
"""Resolve a value set
|
|
45
|
+
"""Resolve a value set: replace the base (if given), then extend the result.
|
|
46
46
|
|
|
47
47
|
Shared by _resolve() (call-site truthy/falsy args, in _core.py) and
|
|
48
48
|
set_defaults() (below) since both layer their inputs on top of a base set
|
|
49
|
-
|
|
50
|
-
replace --
|
|
51
|
-
extend -- additive;
|
|
49
|
+
the same way:
|
|
50
|
+
replace -- swaps out base entirely; the caller owns the starting set
|
|
51
|
+
extend -- additive; merged on top of whatever replace left
|
|
52
52
|
neither -- use base as-is
|
|
53
|
-
replace
|
|
53
|
+
Passing both applies replace first, then extend, so neither argument is
|
|
54
|
+
silently dropped.
|
|
54
55
|
|
|
55
56
|
Args:
|
|
56
|
-
base: The starting set
|
|
57
|
+
base: The starting set, used when replace is None.
|
|
57
58
|
replace: If not None, fully replaces base (normalized).
|
|
58
|
-
extend: If not None
|
|
59
|
+
extend: If not None, merged on top of the (possibly replaced) set.
|
|
59
60
|
|
|
60
61
|
Returns:
|
|
61
62
|
The resolved, normalized frozenset.
|
|
62
63
|
"""
|
|
63
|
-
if replace is not None
|
|
64
|
-
return _normalize_set(replace)
|
|
64
|
+
result = _normalize_set(replace) if replace is not None else base
|
|
65
65
|
if extend is not None:
|
|
66
|
-
|
|
67
|
-
return
|
|
66
|
+
result |= _normalize_set(extend)
|
|
67
|
+
return result
|
|
68
68
|
|
|
69
69
|
|
|
70
70
|
@dataclass(frozen=True)
|
|
@@ -150,8 +150,8 @@ def set_defaults(
|
|
|
150
150
|
built-in (False).
|
|
151
151
|
truthy: Replaces the built-in truthy set.
|
|
152
152
|
falsy: Replaces the built-in falsy set.
|
|
153
|
-
extend_truthy: Extends the built-in truthy
|
|
154
|
-
extend_falsy: Extends the built-in falsy
|
|
153
|
+
extend_truthy: Extends the truthy set (built-in, or truthy if given).
|
|
154
|
+
extend_falsy: Extends the falsy set (built-in, or falsy if given).
|
|
155
155
|
|
|
156
156
|
Raises:
|
|
157
157
|
TypeError: If strict/warn are not bool, or truthy/falsy/extend_truthy/
|
|
@@ -170,10 +170,10 @@ def set_defaults(
|
|
|
170
170
|
new_defaults = Defaults(
|
|
171
171
|
strict=strict if strict is not None else False,
|
|
172
172
|
warn=warn if warn is not None else False,
|
|
173
|
-
effective_truthy=
|
|
173
|
+
effective_truthy=_apply_replace_then_extend(
|
|
174
174
|
DEFAULT_TRUTHY, truthy, extend_truthy
|
|
175
175
|
),
|
|
176
|
-
effective_falsy=
|
|
176
|
+
effective_falsy=_apply_replace_then_extend(DEFAULT_FALSY, falsy, extend_falsy),
|
|
177
177
|
)
|
|
178
178
|
with _cache.lock:
|
|
179
179
|
_cache.value = new_defaults
|
|
@@ -43,14 +43,18 @@ def envbool(
|
|
|
43
43
|
process-level defaults (set_defaults()) (default False).
|
|
44
44
|
truthy: Replaces the effective truthy set.
|
|
45
45
|
falsy: Replaces the effective falsy set.
|
|
46
|
-
extend_truthy: Extends the effective truthy set
|
|
47
|
-
|
|
46
|
+
extend_truthy: Extends the effective truthy set, after any truthy
|
|
47
|
+
replacement.
|
|
48
|
+
extend_falsy: Extends the effective falsy set, after any falsy
|
|
49
|
+
replacement.
|
|
48
50
|
|
|
49
51
|
Returns:
|
|
50
52
|
True if the env var value is in the truthy set, False otherwise.
|
|
51
53
|
|
|
52
54
|
Raises:
|
|
53
55
|
InvalidBoolValueError: In strict mode when the value is unrecognized.
|
|
56
|
+
ConflictingValuesError: In strict mode when the effective truthy and
|
|
57
|
+
falsy sets overlap.
|
|
54
58
|
MissingEnvVarError: When required=True and the variable is unset.
|
|
55
59
|
"""
|
|
56
60
|
# `required` distinguishes "absent from the environment" from "set but empty"
|
|
@@ -8,6 +8,7 @@ which predates envbool adoption keeps working without changes.
|
|
|
8
8
|
Hierarchy:
|
|
9
9
|
EnvBoolError(Exception)
|
|
10
10
|
InvalidBoolValueError(EnvBoolError, ValueError)
|
|
11
|
+
ConflictingValuesError(EnvBoolError, ValueError)
|
|
11
12
|
MissingEnvVarError(EnvBoolError, KeyError)
|
|
12
13
|
"""
|
|
13
14
|
|
|
@@ -42,6 +43,28 @@ class InvalidBoolValueError(EnvBoolError, ValueError):
|
|
|
42
43
|
falsy: frozenset[str]
|
|
43
44
|
|
|
44
45
|
|
|
46
|
+
class ConflictingValuesError(EnvBoolError, ValueError):
|
|
47
|
+
"""Raised in strict mode when the effective truthy and falsy sets overlap.
|
|
48
|
+
|
|
49
|
+
This is a configuration error, not a bad input value -- which is why it is
|
|
50
|
+
distinct from InvalidBoolValueError. It is raised on every strict call while
|
|
51
|
+
the conflict exists (not only when the value lands in the overlap), so a
|
|
52
|
+
misconfiguration fails immediately instead of waiting for a colliding token.
|
|
53
|
+
ValueError inheritance matches InvalidBoolValueError, so a single
|
|
54
|
+
``except ValueError`` still covers every strict-mode failure.
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
# Set by the raising code after construction (see InvalidBoolValueError for
|
|
58
|
+
# why attributes live here rather than in __init__).
|
|
59
|
+
|
|
60
|
+
# Values present in both sets.
|
|
61
|
+
overlap: frozenset[str]
|
|
62
|
+
|
|
63
|
+
# The effective truthy and falsy sets that conflicted.
|
|
64
|
+
truthy: frozenset[str]
|
|
65
|
+
falsy: frozenset[str]
|
|
66
|
+
|
|
67
|
+
|
|
45
68
|
class MissingEnvVarError(EnvBoolError, KeyError):
|
|
46
69
|
"""Raised by envbool(var, required=True) when var is not set in the environment.
|
|
47
70
|
|
|
File without changes
|
|
File without changes
|