envbool 0.4.2__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.4.2 → envbool-0.5.0}/PKG-INFO +106 -5
- {envbool-0.4.2 → envbool-0.5.0}/README.md +105 -4
- {envbool-0.4.2 → envbool-0.5.0}/pyproject.toml +1 -1
- {envbool-0.4.2 → envbool-0.5.0}/pyproject.toml.orig +1 -1
- {envbool-0.4.2 → envbool-0.5.0}/src/envbool/__init__.py +14 -11
- {envbool-0.4.2 → envbool-0.5.0}/src/envbool/_cli.py +6 -6
- {envbool-0.4.2 → envbool-0.5.0}/src/envbool/_core.py +33 -16
- {envbool-0.4.2 → envbool-0.5.0}/src/envbool/_defaults.py +16 -16
- {envbool-0.4.2 → envbool-0.5.0}/src/envbool/_env.py +6 -2
- {envbool-0.4.2 → envbool-0.5.0}/src/envbool/exceptions.py +23 -0
- {envbool-0.4.2 → envbool-0.5.0}/LICENSE +0 -0
- {envbool-0.4.2 → envbool-0.5.0}/src/envbool/__main__.py +0 -0
- {envbool-0.4.2 → envbool-0.5.0}/src/envbool/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
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
|
|
@@ -91,6 +91,22 @@ pip install envbool
|
|
|
91
91
|
uv add envbool
|
|
92
92
|
```
|
|
93
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
|
+
|
|
94
110
|
## Usage
|
|
95
111
|
|
|
96
112
|
### The basics
|
|
@@ -113,6 +129,9 @@ surrounding whitespace.
|
|
|
113
129
|
|
|
114
130
|
Pass `strict=True` to raise `InvalidBoolValueError` on anything outside the
|
|
115
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.
|
|
116
135
|
|
|
117
136
|
```python
|
|
118
137
|
import sys
|
|
@@ -137,6 +156,16 @@ FEATURE = envbool("FEATURE_FLAG", extend_truthy={"enabled", "y"})
|
|
|
137
156
|
LOCALE = envbool("USE_METRIC", truthy={"metric"}, falsy={"imperial"})
|
|
138
157
|
```
|
|
139
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
|
+
|
|
140
169
|
### Coercing arbitrary strings
|
|
141
170
|
|
|
142
171
|
Use `to_bool` for values that don't come from the environment. It accepts the
|
|
@@ -177,6 +206,52 @@ built-in defaults → set_defaults() → function arguments / CLI flags
|
|
|
177
206
|
`reset_defaults()` restores the built-ins — call it in a test fixture (see
|
|
178
207
|
[Testing code that uses envbool](#testing-code-that-uses-envbool)).
|
|
179
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
|
+
|
|
180
255
|
> Through 0.3.x, envbool read TOML config files (`envbool.toml`,
|
|
181
256
|
> `[tool.envbool]`). 0.4.0 removed them in favor of `set_defaults()` — see
|
|
182
257
|
> `CHANGELOG.md` for the rationale and migration note.
|
|
@@ -227,8 +302,10 @@ options:
|
|
|
227
302
|
--truthy VALUE Replace the truthy set with VALUE (repeatable).
|
|
228
303
|
--falsy VALUE Replace the falsy set with VALUE (repeatable).
|
|
229
304
|
--extend-truthy VALUE
|
|
230
|
-
Add VALUE to the truthy set
|
|
231
|
-
|
|
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).
|
|
232
309
|
```
|
|
233
310
|
|
|
234
311
|
A few rules worth knowing:
|
|
@@ -242,6 +319,29 @@ A few rules worth knowing:
|
|
|
242
319
|
- With no `VAR_NAME`, `--value`, or non-empty piped stdin, the CLI prints
|
|
243
320
|
usage and exits `2`.
|
|
244
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
|
+
|
|
245
345
|
## API reference
|
|
246
346
|
|
|
247
347
|
| Symbol | Description |
|
|
@@ -256,6 +356,7 @@ A few rules worth knowing:
|
|
|
256
356
|
| `DEFAULT_FALSY` | `frozenset` of the built-in falsy strings. |
|
|
257
357
|
| `EnvBoolError` | Base class for every exception the library raises. |
|
|
258
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`. |
|
|
259
360
|
| `MissingEnvVarError` | Raised by `envbool(required=True)` when the variable is unset. Also a `KeyError`. |
|
|
260
361
|
|
|
261
362
|
`envbool()` and `to_bool()` share the same keyword-only options:
|
|
@@ -266,7 +367,7 @@ A few rules worth knowing:
|
|
|
266
367
|
| `strict` | `bool \| None` | `None` | Raise on unrecognized values (`None` defers to `set_defaults()`). |
|
|
267
368
|
| `warn` | `bool \| None` | `None` | Log a warning on unrecognized values (`None` defers to `set_defaults()`). |
|
|
268
369
|
| `truthy` / `falsy` | `Iterable[str] \| None` | `None` | **Replace** the effective set. |
|
|
269
|
-
| `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. |
|
|
270
371
|
|
|
271
372
|
`envbool()` also accepts `required` (`bool`, default `False`): when `True`, a
|
|
272
373
|
variable that is unset raises `MissingEnvVarError` before `default` is applied. A
|
|
@@ -317,7 +418,7 @@ logging.getLogger("envbool").addHandler(logging.StreamHandler())
|
|
|
317
418
|
| Level | When |
|
|
318
419
|
| --- | --- |
|
|
319
420
|
| `WARNING` | An unrecognized value fell through in lenient mode (only when `warn=True`). |
|
|
320
|
-
| `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). |
|
|
321
422
|
|
|
322
423
|
### The unset-vs-empty distinction
|
|
323
424
|
|
|
@@ -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
|
|
@@ -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
|
|
@@ -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
|
|
@@ -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
|
|
|
@@ -1,6 +1,6 @@
|
|
|
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" }]
|
|
@@ -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
|
|
File without changes
|