envbool 0.5.0__tar.gz → 0.6.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 → envbool-0.6.0}/PKG-INFO +19 -84
- {envbool-0.5.0 → envbool-0.6.0}/README.md +18 -83
- {envbool-0.5.0 → envbool-0.6.0}/pyproject.toml +1 -1
- {envbool-0.5.0 → envbool-0.6.0}/pyproject.toml.orig +1 -1
- {envbool-0.5.0 → envbool-0.6.0}/src/envbool/__init__.py +2 -18
- {envbool-0.5.0 → envbool-0.6.0}/src/envbool/_cli.py +1 -13
- {envbool-0.5.0 → envbool-0.6.0}/src/envbool/_core.py +56 -45
- {envbool-0.5.0 → envbool-0.6.0}/src/envbool/_env.py +5 -7
- envbool-0.5.0/src/envbool/_defaults.py +0 -189
- {envbool-0.5.0 → envbool-0.6.0}/LICENSE +0 -0
- {envbool-0.5.0 → envbool-0.6.0}/src/envbool/__main__.py +0 -0
- {envbool-0.5.0 → envbool-0.6.0}/src/envbool/exceptions.py +0 -0
- {envbool-0.5.0 → envbool-0.6.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.6.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
|
|
@@ -33,6 +33,7 @@ Description-Content-Type: text/markdown
|
|
|
33
33
|
**Coerce environment variables and strings into booleans — sensibly.**
|
|
34
34
|
|
|
35
35
|
[](https://pypi.org/project/envbool/)
|
|
36
|
+
[](https://snapcraft.io/envbool)
|
|
36
37
|
[](https://pypi.org/project/envbool/)
|
|
37
38
|
[](LICENSE)
|
|
38
39
|
[](https://github.com/jkomalley/envbool/actions/workflows/ci.yml)
|
|
@@ -67,8 +68,6 @@ CACHE = envbool("CACHE")
|
|
|
67
68
|
- **Always returns `bool`.** No `None`, no surprises in your type signatures.
|
|
68
69
|
- **Customizable value sets.** Replace or extend the truthy/falsy words your
|
|
69
70
|
environment uses.
|
|
70
|
-
- **Process-level defaults.** Call `set_defaults()` once at startup instead
|
|
71
|
-
of threading options through every call site.
|
|
72
71
|
- **A CLI for shell scripts.** Exit codes map to truthiness, so it drops
|
|
73
72
|
straight into `&&` / `||` chains.
|
|
74
73
|
- **Zero ceremony.** Zero dependencies, fully typed, Python 3.11+.
|
|
@@ -93,19 +92,15 @@ uv add envbool
|
|
|
93
92
|
|
|
94
93
|
### Snap
|
|
95
94
|
|
|
96
|
-
On Linux, the
|
|
97
|
-
[snap](https://snapcraft.io/):
|
|
95
|
+
On Linux, the CLI is also available as a snap:
|
|
98
96
|
|
|
99
97
|
```bash
|
|
100
98
|
sudo snap install envbool
|
|
101
99
|
```
|
|
102
100
|
|
|
103
|
-
The snap
|
|
104
|
-
|
|
105
|
-
snapd
|
|
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.
|
|
101
|
+
The snap is CLI-only; use `pip` or `uv` to `import envbool`. snapd overrides
|
|
102
|
+
`HOME`, `PATH`, `TMPDIR`, `XDG_RUNTIME_DIR`, and `SNAP_*`, so the CLI sees
|
|
103
|
+
snapd's values for those.
|
|
109
104
|
|
|
110
105
|
## Usage
|
|
111
106
|
|
|
@@ -156,10 +151,10 @@ FEATURE = envbool("FEATURE_FLAG", extend_truthy={"enabled", "y"})
|
|
|
156
151
|
LOCALE = envbool("USE_METRIC", truthy={"metric"}, falsy={"imperial"})
|
|
157
152
|
```
|
|
158
153
|
|
|
159
|
-
Each set is built in order: start from the
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
154
|
+
Each set is built in order: start from the built-in set, swap it out if
|
|
155
|
+
`truthy`/`falsy` is given, then add anything in `extend_truthy`/
|
|
156
|
+
`extend_falsy`. Passing both `truthy` and `extend_truthy` therefore gives you
|
|
157
|
+
exactly their union:
|
|
163
158
|
|
|
164
159
|
```python
|
|
165
160
|
to_bool("y", truthy={"yes"}, extend_truthy={"y"}) # True
|
|
@@ -179,45 +174,17 @@ to_bool("0") # False
|
|
|
179
174
|
to_bool("maybe", strict=True) # raises InvalidBoolValueError
|
|
180
175
|
```
|
|
181
176
|
|
|
182
|
-
### Process-level defaults
|
|
183
|
-
|
|
184
|
-
Set policy once at startup instead of threading `strict=`/`extend_truthy=`
|
|
185
|
-
through every call site:
|
|
186
|
-
|
|
187
|
-
```python
|
|
188
|
-
import envbool
|
|
189
|
-
|
|
190
|
-
envbool.set_defaults(strict=True, extend_truthy=["enabled"])
|
|
191
|
-
|
|
192
|
-
envbool.envbool("DEBUG") # now raises on unrecognized values by default
|
|
193
|
-
```
|
|
194
|
-
|
|
195
|
-
`set_defaults()` replaces the process-level defaults **from the built-ins**,
|
|
196
|
-
not from whatever a previous `set_defaults()` call left in place — call it
|
|
197
|
-
once. Call-site arguments (`envbool("X", strict=False)`) still override
|
|
198
|
-
whatever `set_defaults()` configured:
|
|
199
|
-
|
|
200
|
-
```
|
|
201
|
-
built-in defaults → set_defaults() → function arguments / CLI flags
|
|
202
|
-
```
|
|
203
|
-
|
|
204
|
-
`get_defaults()` returns the active `Defaults` (a frozen dataclass: `strict`,
|
|
205
|
-
`warn`, `effective_truthy`, `effective_falsy`) for inspection.
|
|
206
|
-
`reset_defaults()` restores the built-ins — call it in a test fixture (see
|
|
207
|
-
[Testing code that uses envbool](#testing-code-that-uses-envbool)).
|
|
208
|
-
|
|
209
177
|
### Loading application settings
|
|
210
178
|
|
|
211
179
|
In a real application, read every flag once at startup into a single settings
|
|
212
|
-
object
|
|
180
|
+
object. With strict mode on, a typo like
|
|
213
181
|
`DEBUG=ture` stops startup instead of quietly reading as `False`:
|
|
214
182
|
|
|
215
183
|
```python
|
|
216
184
|
import sys
|
|
217
185
|
from dataclasses import dataclass
|
|
218
186
|
|
|
219
|
-
import envbool
|
|
220
|
-
from envbool import EnvBoolError
|
|
187
|
+
from envbool import EnvBoolError, envbool
|
|
221
188
|
|
|
222
189
|
|
|
223
190
|
@dataclass(frozen=True)
|
|
@@ -229,16 +196,11 @@ class Settings:
|
|
|
229
196
|
|
|
230
197
|
|
|
231
198
|
def load_settings() -> Settings:
|
|
232
|
-
envbool.set_defaults(
|
|
233
|
-
strict=True,
|
|
234
|
-
extend_truthy=["enabled"],
|
|
235
|
-
extend_falsy=["disabled"],
|
|
236
|
-
)
|
|
237
199
|
return Settings(
|
|
238
|
-
debug=envbool
|
|
239
|
-
use_cache=envbool
|
|
240
|
-
new_checkout=envbool
|
|
241
|
-
send_emails=envbool
|
|
200
|
+
debug=envbool("DEBUG", strict=True), # off unless set
|
|
201
|
+
use_cache=envbool("USE_CACHE", default=True, strict=True), # on unless set
|
|
202
|
+
new_checkout=envbool("FEATURE_NEW_CHECKOUT", strict=True),
|
|
203
|
+
send_emails=envbool("SEND_EMAILS", required=True, strict=True), # must be set
|
|
242
204
|
)
|
|
243
205
|
|
|
244
206
|
|
|
@@ -252,10 +214,6 @@ Catching `EnvBoolError` covers every failure: a bad value, a missing
|
|
|
252
214
|
`required` variable, or overlapping value sets. The rest of the application
|
|
253
215
|
reads `SETTINGS.debug` and never touches `os.environ` again.
|
|
254
216
|
|
|
255
|
-
> Through 0.3.x, envbool read TOML config files (`envbool.toml`,
|
|
256
|
-
> `[tool.envbool]`). 0.4.0 removed them in favor of `set_defaults()` — see
|
|
257
|
-
> `CHANGELOG.md` for the rationale and migration note.
|
|
258
|
-
|
|
259
217
|
## Command-line interface
|
|
260
218
|
|
|
261
219
|
The `envbool` command exits `0` for truthy, `1` for falsy, and `2` on error, so
|
|
@@ -310,9 +268,7 @@ options:
|
|
|
310
268
|
|
|
311
269
|
A few rules worth knowing:
|
|
312
270
|
|
|
313
|
-
-
|
|
314
|
-
warnings). `set_defaults()` is a library-level concern — the one-shot CLI
|
|
315
|
-
process doesn't read it.
|
|
271
|
+
- Without `--strict` / `--warn`, coercion is lenient and logs no warnings.
|
|
316
272
|
- `VAR_NAME` and `--value` are mutually exclusive.
|
|
317
273
|
- `--required` only applies to `VAR_NAME`; combining it with `--value` or
|
|
318
274
|
giving it no `VAR_NAME` at all is a usage error.
|
|
@@ -348,10 +304,6 @@ doesn't stop the script.
|
|
|
348
304
|
| --- | --- |
|
|
349
305
|
| `envbool(var, **opts)` | Read an environment variable and return `bool`. |
|
|
350
306
|
| `to_bool(value, **opts)` | Coerce a string to `bool`. |
|
|
351
|
-
| `set_defaults(**opts)` | Set process-level strict/warn/truthy/falsy defaults, replacing the built-ins. |
|
|
352
|
-
| `get_defaults()` | Return the active `Defaults`. |
|
|
353
|
-
| `reset_defaults()` | Restore built-in defaults. |
|
|
354
|
-
| `Defaults` | Frozen dataclass: `strict`, `warn`, `effective_truthy`, `effective_falsy`. |
|
|
355
307
|
| `DEFAULT_TRUTHY` | `frozenset` of the built-in truthy strings. |
|
|
356
308
|
| `DEFAULT_FALSY` | `frozenset` of the built-in falsy strings. |
|
|
357
309
|
| `EnvBoolError` | Base class for every exception the library raises. |
|
|
@@ -364,8 +316,8 @@ doesn't stop the script.
|
|
|
364
316
|
| Option | Type | Default | Meaning |
|
|
365
317
|
| --- | --- | --- | --- |
|
|
366
318
|
| `default` | `bool` | `False` | Returned for unset/empty input. |
|
|
367
|
-
| `strict` | `bool
|
|
368
|
-
| `warn` | `bool
|
|
319
|
+
| `strict` | `bool` | `False` | Raise on unrecognized values. |
|
|
320
|
+
| `warn` | `bool` | `False` | Log a warning on unrecognized values. |
|
|
369
321
|
| `truthy` / `falsy` | `Iterable[str] \| None` | `None` | **Replace** the effective set. |
|
|
370
322
|
| `extend_truthy` / `extend_falsy` | `Iterable[str] \| None` | `None` | **Extend** the effective set, after any replacement. |
|
|
371
323
|
|
|
@@ -437,23 +389,6 @@ else:
|
|
|
437
389
|
result = envbool("MY_VAR")
|
|
438
390
|
```
|
|
439
391
|
|
|
440
|
-
### Testing code that uses envbool
|
|
441
|
-
|
|
442
|
-
If your tests call `set_defaults()`, reset it between tests with an autouse
|
|
443
|
-
fixture so overrides don't leak across the suite:
|
|
444
|
-
|
|
445
|
-
```python
|
|
446
|
-
# conftest.py
|
|
447
|
-
import pytest
|
|
448
|
-
from envbool import reset_defaults
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
@pytest.fixture(autouse=True)
|
|
452
|
-
def _reset_envbool_defaults():
|
|
453
|
-
yield
|
|
454
|
-
reset_defaults()
|
|
455
|
-
```
|
|
456
|
-
|
|
457
392
|
## Contributing
|
|
458
393
|
|
|
459
394
|
Contributions are welcome. See [CONTRIBUTING.md](CONTRIBUTING.md) for development
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
**Coerce environment variables and strings into booleans — sensibly.**
|
|
6
6
|
|
|
7
7
|
[](https://pypi.org/project/envbool/)
|
|
8
|
+
[](https://snapcraft.io/envbool)
|
|
8
9
|
[](https://pypi.org/project/envbool/)
|
|
9
10
|
[](LICENSE)
|
|
10
11
|
[](https://github.com/jkomalley/envbool/actions/workflows/ci.yml)
|
|
@@ -39,8 +40,6 @@ CACHE = envbool("CACHE")
|
|
|
39
40
|
- **Always returns `bool`.** No `None`, no surprises in your type signatures.
|
|
40
41
|
- **Customizable value sets.** Replace or extend the truthy/falsy words your
|
|
41
42
|
environment uses.
|
|
42
|
-
- **Process-level defaults.** Call `set_defaults()` once at startup instead
|
|
43
|
-
of threading options through every call site.
|
|
44
43
|
- **A CLI for shell scripts.** Exit codes map to truthiness, so it drops
|
|
45
44
|
straight into `&&` / `||` chains.
|
|
46
45
|
- **Zero ceremony.** Zero dependencies, fully typed, Python 3.11+.
|
|
@@ -65,19 +64,15 @@ uv add envbool
|
|
|
65
64
|
|
|
66
65
|
### Snap
|
|
67
66
|
|
|
68
|
-
On Linux, the
|
|
69
|
-
[snap](https://snapcraft.io/):
|
|
67
|
+
On Linux, the CLI is also available as a snap:
|
|
70
68
|
|
|
71
69
|
```bash
|
|
72
70
|
sudo snap install envbool
|
|
73
71
|
```
|
|
74
72
|
|
|
75
|
-
The snap
|
|
76
|
-
|
|
77
|
-
snapd
|
|
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.
|
|
73
|
+
The snap is CLI-only; use `pip` or `uv` to `import envbool`. snapd overrides
|
|
74
|
+
`HOME`, `PATH`, `TMPDIR`, `XDG_RUNTIME_DIR`, and `SNAP_*`, so the CLI sees
|
|
75
|
+
snapd's values for those.
|
|
81
76
|
|
|
82
77
|
## Usage
|
|
83
78
|
|
|
@@ -128,10 +123,10 @@ FEATURE = envbool("FEATURE_FLAG", extend_truthy={"enabled", "y"})
|
|
|
128
123
|
LOCALE = envbool("USE_METRIC", truthy={"metric"}, falsy={"imperial"})
|
|
129
124
|
```
|
|
130
125
|
|
|
131
|
-
Each set is built in order: start from the
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
126
|
+
Each set is built in order: start from the built-in set, swap it out if
|
|
127
|
+
`truthy`/`falsy` is given, then add anything in `extend_truthy`/
|
|
128
|
+
`extend_falsy`. Passing both `truthy` and `extend_truthy` therefore gives you
|
|
129
|
+
exactly their union:
|
|
135
130
|
|
|
136
131
|
```python
|
|
137
132
|
to_bool("y", truthy={"yes"}, extend_truthy={"y"}) # True
|
|
@@ -151,45 +146,17 @@ to_bool("0") # False
|
|
|
151
146
|
to_bool("maybe", strict=True) # raises InvalidBoolValueError
|
|
152
147
|
```
|
|
153
148
|
|
|
154
|
-
### Process-level defaults
|
|
155
|
-
|
|
156
|
-
Set policy once at startup instead of threading `strict=`/`extend_truthy=`
|
|
157
|
-
through every call site:
|
|
158
|
-
|
|
159
|
-
```python
|
|
160
|
-
import envbool
|
|
161
|
-
|
|
162
|
-
envbool.set_defaults(strict=True, extend_truthy=["enabled"])
|
|
163
|
-
|
|
164
|
-
envbool.envbool("DEBUG") # now raises on unrecognized values by default
|
|
165
|
-
```
|
|
166
|
-
|
|
167
|
-
`set_defaults()` replaces the process-level defaults **from the built-ins**,
|
|
168
|
-
not from whatever a previous `set_defaults()` call left in place — call it
|
|
169
|
-
once. Call-site arguments (`envbool("X", strict=False)`) still override
|
|
170
|
-
whatever `set_defaults()` configured:
|
|
171
|
-
|
|
172
|
-
```
|
|
173
|
-
built-in defaults → set_defaults() → function arguments / CLI flags
|
|
174
|
-
```
|
|
175
|
-
|
|
176
|
-
`get_defaults()` returns the active `Defaults` (a frozen dataclass: `strict`,
|
|
177
|
-
`warn`, `effective_truthy`, `effective_falsy`) for inspection.
|
|
178
|
-
`reset_defaults()` restores the built-ins — call it in a test fixture (see
|
|
179
|
-
[Testing code that uses envbool](#testing-code-that-uses-envbool)).
|
|
180
|
-
|
|
181
149
|
### Loading application settings
|
|
182
150
|
|
|
183
151
|
In a real application, read every flag once at startup into a single settings
|
|
184
|
-
object
|
|
152
|
+
object. With strict mode on, a typo like
|
|
185
153
|
`DEBUG=ture` stops startup instead of quietly reading as `False`:
|
|
186
154
|
|
|
187
155
|
```python
|
|
188
156
|
import sys
|
|
189
157
|
from dataclasses import dataclass
|
|
190
158
|
|
|
191
|
-
import envbool
|
|
192
|
-
from envbool import EnvBoolError
|
|
159
|
+
from envbool import EnvBoolError, envbool
|
|
193
160
|
|
|
194
161
|
|
|
195
162
|
@dataclass(frozen=True)
|
|
@@ -201,16 +168,11 @@ class Settings:
|
|
|
201
168
|
|
|
202
169
|
|
|
203
170
|
def load_settings() -> Settings:
|
|
204
|
-
envbool.set_defaults(
|
|
205
|
-
strict=True,
|
|
206
|
-
extend_truthy=["enabled"],
|
|
207
|
-
extend_falsy=["disabled"],
|
|
208
|
-
)
|
|
209
171
|
return Settings(
|
|
210
|
-
debug=envbool
|
|
211
|
-
use_cache=envbool
|
|
212
|
-
new_checkout=envbool
|
|
213
|
-
send_emails=envbool
|
|
172
|
+
debug=envbool("DEBUG", strict=True), # off unless set
|
|
173
|
+
use_cache=envbool("USE_CACHE", default=True, strict=True), # on unless set
|
|
174
|
+
new_checkout=envbool("FEATURE_NEW_CHECKOUT", strict=True),
|
|
175
|
+
send_emails=envbool("SEND_EMAILS", required=True, strict=True), # must be set
|
|
214
176
|
)
|
|
215
177
|
|
|
216
178
|
|
|
@@ -224,10 +186,6 @@ Catching `EnvBoolError` covers every failure: a bad value, a missing
|
|
|
224
186
|
`required` variable, or overlapping value sets. The rest of the application
|
|
225
187
|
reads `SETTINGS.debug` and never touches `os.environ` again.
|
|
226
188
|
|
|
227
|
-
> Through 0.3.x, envbool read TOML config files (`envbool.toml`,
|
|
228
|
-
> `[tool.envbool]`). 0.4.0 removed them in favor of `set_defaults()` — see
|
|
229
|
-
> `CHANGELOG.md` for the rationale and migration note.
|
|
230
|
-
|
|
231
189
|
## Command-line interface
|
|
232
190
|
|
|
233
191
|
The `envbool` command exits `0` for truthy, `1` for falsy, and `2` on error, so
|
|
@@ -282,9 +240,7 @@ options:
|
|
|
282
240
|
|
|
283
241
|
A few rules worth knowing:
|
|
284
242
|
|
|
285
|
-
-
|
|
286
|
-
warnings). `set_defaults()` is a library-level concern — the one-shot CLI
|
|
287
|
-
process doesn't read it.
|
|
243
|
+
- Without `--strict` / `--warn`, coercion is lenient and logs no warnings.
|
|
288
244
|
- `VAR_NAME` and `--value` are mutually exclusive.
|
|
289
245
|
- `--required` only applies to `VAR_NAME`; combining it with `--value` or
|
|
290
246
|
giving it no `VAR_NAME` at all is a usage error.
|
|
@@ -320,10 +276,6 @@ doesn't stop the script.
|
|
|
320
276
|
| --- | --- |
|
|
321
277
|
| `envbool(var, **opts)` | Read an environment variable and return `bool`. |
|
|
322
278
|
| `to_bool(value, **opts)` | Coerce a string to `bool`. |
|
|
323
|
-
| `set_defaults(**opts)` | Set process-level strict/warn/truthy/falsy defaults, replacing the built-ins. |
|
|
324
|
-
| `get_defaults()` | Return the active `Defaults`. |
|
|
325
|
-
| `reset_defaults()` | Restore built-in defaults. |
|
|
326
|
-
| `Defaults` | Frozen dataclass: `strict`, `warn`, `effective_truthy`, `effective_falsy`. |
|
|
327
279
|
| `DEFAULT_TRUTHY` | `frozenset` of the built-in truthy strings. |
|
|
328
280
|
| `DEFAULT_FALSY` | `frozenset` of the built-in falsy strings. |
|
|
329
281
|
| `EnvBoolError` | Base class for every exception the library raises. |
|
|
@@ -336,8 +288,8 @@ doesn't stop the script.
|
|
|
336
288
|
| Option | Type | Default | Meaning |
|
|
337
289
|
| --- | --- | --- | --- |
|
|
338
290
|
| `default` | `bool` | `False` | Returned for unset/empty input. |
|
|
339
|
-
| `strict` | `bool
|
|
340
|
-
| `warn` | `bool
|
|
291
|
+
| `strict` | `bool` | `False` | Raise on unrecognized values. |
|
|
292
|
+
| `warn` | `bool` | `False` | Log a warning on unrecognized values. |
|
|
341
293
|
| `truthy` / `falsy` | `Iterable[str] \| None` | `None` | **Replace** the effective set. |
|
|
342
294
|
| `extend_truthy` / `extend_falsy` | `Iterable[str] \| None` | `None` | **Extend** the effective set, after any replacement. |
|
|
343
295
|
|
|
@@ -409,23 +361,6 @@ else:
|
|
|
409
361
|
result = envbool("MY_VAR")
|
|
410
362
|
```
|
|
411
363
|
|
|
412
|
-
### Testing code that uses envbool
|
|
413
|
-
|
|
414
|
-
If your tests call `set_defaults()`, reset it between tests with an autouse
|
|
415
|
-
fixture so overrides don't leak across the suite:
|
|
416
|
-
|
|
417
|
-
```python
|
|
418
|
-
# conftest.py
|
|
419
|
-
import pytest
|
|
420
|
-
from envbool import reset_defaults
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
@pytest.fixture(autouse=True)
|
|
424
|
-
def _reset_envbool_defaults():
|
|
425
|
-
yield
|
|
426
|
-
reset_defaults()
|
|
427
|
-
```
|
|
428
|
-
|
|
429
364
|
## Contributing
|
|
430
365
|
|
|
431
366
|
Contributions are welcome. See [CONTRIBUTING.md](CONTRIBUTING.md) for development
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "envbool"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.6.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" }]
|
|
@@ -11,10 +11,6 @@ For except clauses, envbool.exceptions is also importable by name:
|
|
|
11
11
|
Available names:
|
|
12
12
|
envbool() -- read an env var and coerce to bool (primary API)
|
|
13
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
14
|
DEFAULT_TRUTHY -- built-in truthy set (frozenset)
|
|
19
15
|
DEFAULT_FALSY -- built-in falsy set (frozenset)
|
|
20
16
|
EnvBoolError -- base exception for all envbool errors
|
|
@@ -24,17 +20,9 @@ Available names:
|
|
|
24
20
|
"""
|
|
25
21
|
# All implementation lives in private underscore-prefixed modules so the public
|
|
26
22
|
# surface can be reshaped without breaking imports. Do not import from _core,
|
|
27
|
-
# _env,
|
|
23
|
+
# _env, or _cli directly.
|
|
28
24
|
|
|
29
|
-
from envbool._core import to_bool
|
|
30
|
-
from envbool._defaults import (
|
|
31
|
-
DEFAULT_FALSY,
|
|
32
|
-
DEFAULT_TRUTHY,
|
|
33
|
-
Defaults,
|
|
34
|
-
get_defaults,
|
|
35
|
-
reset_defaults,
|
|
36
|
-
set_defaults,
|
|
37
|
-
)
|
|
25
|
+
from envbool._core import DEFAULT_FALSY, DEFAULT_TRUTHY, to_bool
|
|
38
26
|
from envbool._env import envbool
|
|
39
27
|
from envbool.exceptions import (
|
|
40
28
|
ConflictingValuesError,
|
|
@@ -47,13 +35,9 @@ __all__ = [
|
|
|
47
35
|
"DEFAULT_FALSY",
|
|
48
36
|
"DEFAULT_TRUTHY",
|
|
49
37
|
"ConflictingValuesError",
|
|
50
|
-
"Defaults",
|
|
51
38
|
"EnvBoolError",
|
|
52
39
|
"InvalidBoolValueError",
|
|
53
40
|
"MissingEnvVarError",
|
|
54
41
|
"envbool",
|
|
55
|
-
"get_defaults",
|
|
56
|
-
"reset_defaults",
|
|
57
|
-
"set_defaults",
|
|
58
42
|
"to_bool",
|
|
59
43
|
]
|
|
@@ -14,8 +14,7 @@ Exit codes:
|
|
|
14
14
|
2 -- error (unrecognized value in strict mode, unset VAR_NAME with --required,
|
|
15
15
|
bad arguments, multi-line stdin)
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
(envbool.set_defaults(); default: lenient, no warnings).
|
|
17
|
+
Without --strict or --warn, coercion is lenient and emits no warnings.
|
|
19
18
|
|
|
20
19
|
Value sets: --truthy/--falsy (repeatable) replace the truthy/falsy set;
|
|
21
20
|
--extend-truthy/--extend-falsy (repeatable) add to it, after any
|
|
@@ -24,10 +23,6 @@ replacement.
|
|
|
24
23
|
Public surface:
|
|
25
24
|
main() -- entry point registered as the "envbool" command
|
|
26
25
|
"""
|
|
27
|
-
# --strict and --warn use default=None rather than False so an absent flag
|
|
28
|
-
# passes None through to envbool()/to_bool(), which then defers to the
|
|
29
|
-
# process-level defaults instead of overriding a set_defaults(strict=True)/
|
|
30
|
-
# warn=True with False.
|
|
31
26
|
|
|
32
27
|
__all__ = ["main"]
|
|
33
28
|
|
|
@@ -63,18 +58,11 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
63
58
|
"--strict",
|
|
64
59
|
"-s",
|
|
65
60
|
action="store_true",
|
|
66
|
-
# None so an absent flag defers to the process-level defaults rather
|
|
67
|
-
# than overriding them with False.
|
|
68
|
-
# store_true with default=None gives: flag present -> True, absent -> None.
|
|
69
|
-
default=None,
|
|
70
61
|
help="Raise error on unrecognized values.",
|
|
71
62
|
)
|
|
72
63
|
parser.add_argument(
|
|
73
64
|
"--warn",
|
|
74
65
|
action="store_true",
|
|
75
|
-
# None so an absent flag defers to the process-level defaults rather
|
|
76
|
-
# than overriding them with False.
|
|
77
|
-
default=None,
|
|
78
66
|
help="Log a warning on unrecognized values.",
|
|
79
67
|
)
|
|
80
68
|
parser.add_argument(
|
|
@@ -1,35 +1,65 @@
|
|
|
1
1
|
"""Pure string-to-bool coercion with configurable truthy/falsy sets.
|
|
2
2
|
|
|
3
3
|
Public surface:
|
|
4
|
-
DEFAULT_TRUTHY -- the built-in truthy set
|
|
5
|
-
DEFAULT_FALSY -- the built-in falsy set
|
|
4
|
+
DEFAULT_TRUTHY -- the built-in truthy set
|
|
5
|
+
DEFAULT_FALSY -- the built-in falsy set
|
|
6
6
|
to_bool() -- coerce a single string to bool
|
|
7
7
|
|
|
8
8
|
Private surface (used by _env.py and tests):
|
|
9
|
-
_resolve() -- compute effective truthy/falsy sets from
|
|
9
|
+
_resolve() -- compute effective truthy/falsy sets from call-site args
|
|
10
10
|
"""
|
|
11
|
-
# This module has no knowledge of os.environ -- that lives in _env.py. It
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
# to False.
|
|
11
|
+
# This module has no knowledge of os.environ -- that lives in _env.py. It also
|
|
12
|
+
# holds no process-wide state: every setting comes from the call-site
|
|
13
|
+
# arguments.
|
|
15
14
|
|
|
16
|
-
__all__ = ["to_bool"]
|
|
15
|
+
__all__ = ["DEFAULT_FALSY", "DEFAULT_TRUTHY", "to_bool"]
|
|
17
16
|
|
|
18
17
|
import logging
|
|
19
18
|
from collections.abc import Iterable
|
|
20
19
|
|
|
21
|
-
from envbool._defaults import (
|
|
22
|
-
DEFAULT_FALSY,
|
|
23
|
-
DEFAULT_TRUTHY,
|
|
24
|
-
_apply_replace_then_extend,
|
|
25
|
-
get_defaults,
|
|
26
|
-
)
|
|
27
20
|
from envbool.exceptions import ConflictingValuesError, InvalidBoolValueError
|
|
28
21
|
|
|
29
22
|
# Module-level logger -- attributed to "envbool._core" so callers can filter it
|
|
30
|
-
# independently from
|
|
23
|
+
# independently from the root "envbool" logger.
|
|
31
24
|
_logger = logging.getLogger(__name__)
|
|
32
25
|
|
|
26
|
+
DEFAULT_TRUTHY: frozenset[str] = frozenset({"true", "1", "yes", "on"})
|
|
27
|
+
DEFAULT_FALSY: frozenset[str] = frozenset({"false", "0", "no", "off"})
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _normalize_set(values: Iterable[str]) -> frozenset[str]:
|
|
31
|
+
"""Strip and lowercase values so they match to_bool()'s normalized input."""
|
|
32
|
+
return frozenset(v.strip().lower() for v in values)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _apply_replace_then_extend(
|
|
36
|
+
base: frozenset[str],
|
|
37
|
+
replace: Iterable[str] | None,
|
|
38
|
+
extend: Iterable[str] | None,
|
|
39
|
+
) -> frozenset[str]:
|
|
40
|
+
"""Resolve a value set: replace the base (if given), then extend the result.
|
|
41
|
+
|
|
42
|
+
Used by _resolve() for each of the truthy and falsy sets:
|
|
43
|
+
replace -- swaps out base entirely; the caller owns the starting set
|
|
44
|
+
extend -- additive; merged on top of whatever replace left
|
|
45
|
+
neither -- use base as-is
|
|
46
|
+
Passing both applies replace first, then extend, so neither argument is
|
|
47
|
+
silently dropped.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
base: The starting set, used when replace is None.
|
|
51
|
+
replace: If not None, fully replaces base (normalized).
|
|
52
|
+
extend: If not None, merged on top of the (possibly replaced) set.
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
The resolved, normalized frozenset.
|
|
56
|
+
"""
|
|
57
|
+
result = _normalize_set(replace) if replace is not None else base
|
|
58
|
+
if extend is not None:
|
|
59
|
+
result |= _normalize_set(extend)
|
|
60
|
+
return result
|
|
61
|
+
|
|
62
|
+
|
|
33
63
|
# Public API
|
|
34
64
|
|
|
35
65
|
|
|
@@ -37,8 +67,8 @@ def to_bool(
|
|
|
37
67
|
value: str,
|
|
38
68
|
*,
|
|
39
69
|
default: bool = False,
|
|
40
|
-
strict: bool
|
|
41
|
-
warn: bool
|
|
70
|
+
strict: bool = False,
|
|
71
|
+
warn: bool = False,
|
|
42
72
|
truthy: Iterable[str] | None = None,
|
|
43
73
|
falsy: Iterable[str] | None = None,
|
|
44
74
|
extend_truthy: Iterable[str] | None = None,
|
|
@@ -50,10 +80,8 @@ def to_bool(
|
|
|
50
80
|
Args:
|
|
51
81
|
value: The string to coerce.
|
|
52
82
|
default: Returned when value is empty or unset.
|
|
53
|
-
strict: Raise on unrecognized values.
|
|
54
|
-
|
|
55
|
-
warn: Log a warning on unrecognized values. None defers to
|
|
56
|
-
process-level defaults (set_defaults()) (default False).
|
|
83
|
+
strict: Raise on unrecognized values.
|
|
84
|
+
warn: Log a warning on unrecognized values.
|
|
57
85
|
truthy: Replaces the effective truthy set.
|
|
58
86
|
falsy: Replaces the effective falsy set.
|
|
59
87
|
extend_truthy: Extends the effective truthy set, after any truthy
|
|
@@ -77,29 +105,15 @@ def to_bool(
|
|
|
77
105
|
if not normalized:
|
|
78
106
|
return default
|
|
79
107
|
|
|
80
|
-
#
|
|
81
|
-
#
|
|
82
|
-
# precedence chain:
|
|
83
|
-
# hardcoded defaults (_defaults.py)
|
|
84
|
-
# -> process-level defaults (effective_truthy/effective_falsy already
|
|
85
|
-
# resolved there by set_defaults())
|
|
86
|
-
# -> call-site args (truthy/extend_truthy/falsy/extend_falsy)
|
|
87
|
-
defaults = get_defaults()
|
|
108
|
+
# Precedence is two-level: the built-in sets, then the call-site args
|
|
109
|
+
# (truthy/extend_truthy/falsy/extend_falsy) layered on top by _resolve.
|
|
88
110
|
effective_truthy, effective_falsy = _resolve(
|
|
89
|
-
config_truthy=defaults.effective_truthy,
|
|
90
|
-
config_falsy=defaults.effective_falsy,
|
|
91
111
|
truthy=truthy,
|
|
92
112
|
falsy=falsy,
|
|
93
113
|
extend_truthy=extend_truthy,
|
|
94
114
|
extend_falsy=extend_falsy,
|
|
95
115
|
)
|
|
96
116
|
|
|
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
117
|
# Overlapping sets are a configuration mistake. Strict mode promises every
|
|
104
118
|
# accepted value is unambiguous, so it rejects the configuration outright --
|
|
105
119
|
# on every call, not just when the value lands in the overlap, so the
|
|
@@ -107,7 +121,7 @@ def to_bool(
|
|
|
107
121
|
# problem is visible, then lets truthy win to stay predictable.
|
|
108
122
|
overlap = effective_truthy & effective_falsy
|
|
109
123
|
if overlap:
|
|
110
|
-
if
|
|
124
|
+
if strict:
|
|
111
125
|
err = ConflictingValuesError(
|
|
112
126
|
f"Truthy and falsy sets overlap: {', '.join(sorted(overlap))}"
|
|
113
127
|
)
|
|
@@ -127,7 +141,7 @@ def to_bool(
|
|
|
127
141
|
if normalized in effective_falsy:
|
|
128
142
|
return False
|
|
129
143
|
|
|
130
|
-
if
|
|
144
|
+
if strict:
|
|
131
145
|
truthy_list = ", ".join(sorted(effective_truthy))
|
|
132
146
|
falsy_list = ", ".join(sorted(effective_falsy))
|
|
133
147
|
# _var is threaded in by envbool() so the error message names the env
|
|
@@ -151,8 +165,7 @@ def to_bool(
|
|
|
151
165
|
err.falsy = effective_falsy
|
|
152
166
|
raise err
|
|
153
167
|
|
|
154
|
-
|
|
155
|
-
if effective_warn:
|
|
168
|
+
if warn:
|
|
156
169
|
_logger.warning("Unrecognized boolean value: %r", normalized)
|
|
157
170
|
|
|
158
171
|
# Lenient fallback: anything unrecognized is treated as falsy. This matches
|
|
@@ -165,8 +178,6 @@ def to_bool(
|
|
|
165
178
|
|
|
166
179
|
def _resolve(
|
|
167
180
|
*,
|
|
168
|
-
config_truthy: frozenset[str] = DEFAULT_TRUTHY,
|
|
169
|
-
config_falsy: frozenset[str] = DEFAULT_FALSY,
|
|
170
181
|
truthy: Iterable[str] | None = None,
|
|
171
182
|
falsy: Iterable[str] | None = None,
|
|
172
183
|
extend_truthy: Iterable[str] | None = None,
|
|
@@ -174,7 +185,7 @@ def _resolve(
|
|
|
174
185
|
) -> tuple[frozenset[str], frozenset[str]]:
|
|
175
186
|
# Replace-then-extend, per set -- see the _apply_replace_then_extend()
|
|
176
187
|
# docstring for the full precedence rules.
|
|
177
|
-
effective_truthy = _apply_replace_then_extend(
|
|
178
|
-
effective_falsy = _apply_replace_then_extend(
|
|
188
|
+
effective_truthy = _apply_replace_then_extend(DEFAULT_TRUTHY, truthy, extend_truthy)
|
|
189
|
+
effective_falsy = _apply_replace_then_extend(DEFAULT_FALSY, falsy, extend_falsy)
|
|
179
190
|
|
|
180
191
|
return (effective_truthy, effective_falsy)
|
|
@@ -6,7 +6,7 @@ Public surface:
|
|
|
6
6
|
# This is the only layer in the package that touches os.environ. The split
|
|
7
7
|
# between _env.py and _core.py keeps os.environ access isolated here so that
|
|
8
8
|
# to_bool() can be tested without monkeypatching the environment.
|
|
9
|
-
# Delegation chain: envbool() -> to_bool() -> _resolve()
|
|
9
|
+
# Delegation chain: envbool() -> to_bool() -> _resolve()
|
|
10
10
|
|
|
11
11
|
__all__ = ["envbool"]
|
|
12
12
|
|
|
@@ -22,8 +22,8 @@ def envbool(
|
|
|
22
22
|
*,
|
|
23
23
|
default: bool = False,
|
|
24
24
|
required: bool = False,
|
|
25
|
-
strict: bool
|
|
26
|
-
warn: bool
|
|
25
|
+
strict: bool = False,
|
|
26
|
+
warn: bool = False,
|
|
27
27
|
truthy: Iterable[str] | None = None,
|
|
28
28
|
falsy: Iterable[str] | None = None,
|
|
29
29
|
extend_truthy: Iterable[str] | None = None,
|
|
@@ -37,10 +37,8 @@ def envbool(
|
|
|
37
37
|
required: When True, raise if the variable is not set at all. A truly
|
|
38
38
|
unset var raises before `default` is considered; a var set to an
|
|
39
39
|
empty string is "present" and still coerces via `default`.
|
|
40
|
-
strict: Raise on unrecognized values.
|
|
41
|
-
|
|
42
|
-
warn: Log a warning on unrecognized values. None defers to
|
|
43
|
-
process-level defaults (set_defaults()) (default False).
|
|
40
|
+
strict: Raise on unrecognized values.
|
|
41
|
+
warn: Log a warning on unrecognized values.
|
|
44
42
|
truthy: Replaces the effective truthy set.
|
|
45
43
|
falsy: Replaces the effective falsy set.
|
|
46
44
|
extend_truthy: Extends the effective truthy set, after any truthy
|
|
@@ -1,189 +0,0 @@
|
|
|
1
|
-
"""Built-in truthy/falsy sets, set-resolution helpers, and process-level defaults.
|
|
2
|
-
|
|
3
|
-
The Defaults instance held here is consulted by to_bool()/envbool() whenever a
|
|
4
|
-
call-site strict/warn argument is None or no truthy/falsy override is given.
|
|
5
|
-
|
|
6
|
-
Public surface:
|
|
7
|
-
DEFAULT_TRUTHY -- the built-in truthy set
|
|
8
|
-
DEFAULT_FALSY -- the built-in falsy set
|
|
9
|
-
Defaults -- frozen dataclass with resolved process-level settings
|
|
10
|
-
get_defaults() -- returns the active Defaults
|
|
11
|
-
set_defaults() -- replaces the active Defaults, built from the built-ins
|
|
12
|
-
reset_defaults() -- restores the built-in Defaults; for test fixtures
|
|
13
|
-
|
|
14
|
-
This is a leaf module (no imports from elsewhere in envbool) so _core.py can
|
|
15
|
-
import from it without a circular dependency.
|
|
16
|
-
"""
|
|
17
|
-
|
|
18
|
-
__all__ = [
|
|
19
|
-
"DEFAULT_FALSY",
|
|
20
|
-
"DEFAULT_TRUTHY",
|
|
21
|
-
"Defaults",
|
|
22
|
-
"get_defaults",
|
|
23
|
-
"reset_defaults",
|
|
24
|
-
"set_defaults",
|
|
25
|
-
]
|
|
26
|
-
|
|
27
|
-
import threading
|
|
28
|
-
from collections.abc import Iterable
|
|
29
|
-
from dataclasses import dataclass
|
|
30
|
-
|
|
31
|
-
DEFAULT_TRUTHY: frozenset[str] = frozenset({"true", "1", "yes", "on"})
|
|
32
|
-
DEFAULT_FALSY: frozenset[str] = frozenset({"false", "0", "no", "off"})
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
def _normalize_set(values: Iterable[str]) -> frozenset[str]:
|
|
36
|
-
"""Strip and lowercase values so they match to_bool()'s normalized input."""
|
|
37
|
-
return frozenset(v.strip().lower() for v in values)
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
def _apply_replace_then_extend(
|
|
41
|
-
base: frozenset[str],
|
|
42
|
-
replace: Iterable[str] | None,
|
|
43
|
-
extend: Iterable[str] | None,
|
|
44
|
-
) -> frozenset[str]:
|
|
45
|
-
"""Resolve a value set: replace the base (if given), then extend the result.
|
|
46
|
-
|
|
47
|
-
Shared by _resolve() (call-site truthy/falsy args, in _core.py) and
|
|
48
|
-
set_defaults() (below) since both layer their inputs on top of a base set
|
|
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
|
-
neither -- use base as-is
|
|
53
|
-
Passing both applies replace first, then extend, so neither argument is
|
|
54
|
-
silently dropped.
|
|
55
|
-
|
|
56
|
-
Args:
|
|
57
|
-
base: The starting set, used when replace is None.
|
|
58
|
-
replace: If not None, fully replaces base (normalized).
|
|
59
|
-
extend: If not None, merged on top of the (possibly replaced) set.
|
|
60
|
-
|
|
61
|
-
Returns:
|
|
62
|
-
The resolved, normalized frozenset.
|
|
63
|
-
"""
|
|
64
|
-
result = _normalize_set(replace) if replace is not None else base
|
|
65
|
-
if extend is not None:
|
|
66
|
-
result |= _normalize_set(extend)
|
|
67
|
-
return result
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
@dataclass(frozen=True)
|
|
71
|
-
class Defaults:
|
|
72
|
-
"""Process-level defaults consulted when a call-site argument is None.
|
|
73
|
-
|
|
74
|
-
Attributes:
|
|
75
|
-
strict: When True, unrecognized values raise InvalidBoolValueError.
|
|
76
|
-
warn: When True, unrecognized values in lenient mode emit a WARNING log.
|
|
77
|
-
effective_truthy: Fully resolved truthy set (after extend/replace logic).
|
|
78
|
-
effective_falsy: Fully resolved falsy set (after extend/replace logic).
|
|
79
|
-
"""
|
|
80
|
-
|
|
81
|
-
strict: bool = False
|
|
82
|
-
warn: bool = False
|
|
83
|
-
effective_truthy: frozenset[str] = DEFAULT_TRUTHY
|
|
84
|
-
effective_falsy: frozenset[str] = DEFAULT_FALSY
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
class _DefaultsCache:
|
|
88
|
-
# Building a Defaults() is pure in-memory work (no disk I/O), so the cache
|
|
89
|
-
# starts pre-populated -- readers never need a "not built yet" branch. Only
|
|
90
|
-
# writers (set_defaults/reset_defaults) take the lock, so a concurrent
|
|
91
|
-
# writer can't produce a torn read.
|
|
92
|
-
|
|
93
|
-
value: Defaults = Defaults()
|
|
94
|
-
lock: threading.Lock = threading.Lock()
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
_cache = _DefaultsCache()
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
def get_defaults() -> Defaults:
|
|
101
|
-
"""Return the active process-level defaults.
|
|
102
|
-
|
|
103
|
-
Returns:
|
|
104
|
-
The current Defaults -- built-ins, or whatever set_defaults() last set.
|
|
105
|
-
"""
|
|
106
|
-
return _cache.value
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
def _validated_tuple(name: str, values: Iterable[str] | None) -> tuple[str, ...] | None:
|
|
110
|
-
"""Materialize an Iterable[str] argument once, validating every member.
|
|
111
|
-
|
|
112
|
-
Iterables (e.g. generators) can only be consumed once; materializing here
|
|
113
|
-
(rather than validating then re-passing the original iterable) avoids
|
|
114
|
-
silently resolving to an empty set on the second pass.
|
|
115
|
-
|
|
116
|
-
Raises:
|
|
117
|
-
TypeError: If any member is not a str.
|
|
118
|
-
"""
|
|
119
|
-
if values is None:
|
|
120
|
-
return None
|
|
121
|
-
materialized = tuple(values)
|
|
122
|
-
for item in materialized:
|
|
123
|
-
if not isinstance(item, str):
|
|
124
|
-
raise TypeError(
|
|
125
|
-
f"{name} must be an iterable of strings, got {type(item).__name__!r}"
|
|
126
|
-
)
|
|
127
|
-
return materialized
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
def set_defaults(
|
|
131
|
-
*,
|
|
132
|
-
strict: bool | None = None,
|
|
133
|
-
warn: bool | None = None,
|
|
134
|
-
truthy: Iterable[str] | None = None,
|
|
135
|
-
falsy: Iterable[str] | None = None,
|
|
136
|
-
extend_truthy: Iterable[str] | None = None,
|
|
137
|
-
extend_falsy: Iterable[str] | None = None,
|
|
138
|
-
) -> None:
|
|
139
|
-
"""Set process-level defaults consulted when a call-site argument is None.
|
|
140
|
-
|
|
141
|
-
Each call replaces the defaults starting from the hardcoded built-ins --
|
|
142
|
-
it does not merge with a previous set_defaults() call. Call this once at
|
|
143
|
-
application startup rather than threading strict=/truthy=/etc. through
|
|
144
|
-
every envbool()/to_bool() call site.
|
|
145
|
-
|
|
146
|
-
Args:
|
|
147
|
-
strict: Raise on unrecognized values by default. None keeps the
|
|
148
|
-
built-in (False).
|
|
149
|
-
warn: Log a warning on unrecognized values by default. None keeps the
|
|
150
|
-
built-in (False).
|
|
151
|
-
truthy: Replaces the built-in truthy set.
|
|
152
|
-
falsy: Replaces the built-in falsy set.
|
|
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
|
-
|
|
156
|
-
Raises:
|
|
157
|
-
TypeError: If strict/warn are not bool, or truthy/falsy/extend_truthy/
|
|
158
|
-
extend_falsy contain a non-string member.
|
|
159
|
-
"""
|
|
160
|
-
if strict is not None and not isinstance(strict, bool):
|
|
161
|
-
raise TypeError(f"strict must be a bool, got {type(strict).__name__!r}")
|
|
162
|
-
if warn is not None and not isinstance(warn, bool):
|
|
163
|
-
raise TypeError(f"warn must be a bool, got {type(warn).__name__!r}")
|
|
164
|
-
|
|
165
|
-
truthy = _validated_tuple("truthy", truthy)
|
|
166
|
-
falsy = _validated_tuple("falsy", falsy)
|
|
167
|
-
extend_truthy = _validated_tuple("extend_truthy", extend_truthy)
|
|
168
|
-
extend_falsy = _validated_tuple("extend_falsy", extend_falsy)
|
|
169
|
-
|
|
170
|
-
new_defaults = Defaults(
|
|
171
|
-
strict=strict if strict is not None else False,
|
|
172
|
-
warn=warn if warn is not None else False,
|
|
173
|
-
effective_truthy=_apply_replace_then_extend(
|
|
174
|
-
DEFAULT_TRUTHY, truthy, extend_truthy
|
|
175
|
-
),
|
|
176
|
-
effective_falsy=_apply_replace_then_extend(DEFAULT_FALSY, falsy, extend_falsy),
|
|
177
|
-
)
|
|
178
|
-
with _cache.lock:
|
|
179
|
-
_cache.value = new_defaults
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
def reset_defaults() -> None:
|
|
183
|
-
"""Restore built-in defaults, discarding any set_defaults() override.
|
|
184
|
-
|
|
185
|
-
Intended for test fixtures: call in an autouse fixture teardown so a
|
|
186
|
-
set_defaults() call in one test doesn't leak into the next.
|
|
187
|
-
"""
|
|
188
|
-
with _cache.lock:
|
|
189
|
-
_cache.value = Defaults()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|