envbool 0.1.1__tar.gz → 0.2.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.2.0/PKG-INFO +358 -0
- envbool-0.2.0/README.md +330 -0
- {envbool-0.1.1 → envbool-0.2.0}/pyproject.toml +1 -1
- envbool-0.2.0/src/envbool/_cli.py +234 -0
- {envbool-0.1.1 → envbool-0.2.0}/src/envbool/_config.py +13 -18
- {envbool-0.1.1 → envbool-0.2.0}/src/envbool/_core.py +34 -19
- envbool-0.1.1/PKG-INFO +0 -155
- envbool-0.1.1/README.md +0 -127
- envbool-0.1.1/src/envbool/_cli.py +0 -121
- {envbool-0.1.1 → envbool-0.2.0}/src/envbool/__init__.py +0 -0
- {envbool-0.1.1 → envbool-0.2.0}/src/envbool/_defaults.py +0 -0
- {envbool-0.1.1 → envbool-0.2.0}/src/envbool/_env.py +0 -0
- {envbool-0.1.1 → envbool-0.2.0}/src/envbool/exceptions.py +0 -0
- {envbool-0.1.1 → envbool-0.2.0}/src/envbool/py.typed +0 -0
envbool-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: envbool
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: A small Python library and CLI tool for coercing environment variables (and arbitrary strings) into boolean values.
|
|
5
|
+
Keywords: environment variables,boolean,configuration,env,coerce
|
|
6
|
+
Author: Kyle O'Malley
|
|
7
|
+
Author-email: Kyle O'Malley <j.kyle.omalley@gmail.com>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
12
|
+
Classifier: Topic :: Utilities
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
20
|
+
Classifier: Typing :: Typed
|
|
21
|
+
Requires-Dist: platformdirs>=4.9.6
|
|
22
|
+
Requires-Python: >=3.11
|
|
23
|
+
Project-URL: Homepage, https://github.com/jkomalley/envbool
|
|
24
|
+
Project-URL: Repository, https://github.com/jkomalley/envbool
|
|
25
|
+
Project-URL: Issues, https://github.com/jkomalley/envbool/issues
|
|
26
|
+
Project-URL: Changelog, https://github.com/jkomalley/envbool/releases
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
|
|
29
|
+
<div align="center">
|
|
30
|
+
|
|
31
|
+
# envbool
|
|
32
|
+
|
|
33
|
+
**Coerce environment variables and strings into booleans — sensibly.**
|
|
34
|
+
|
|
35
|
+
[](https://pypi.org/project/envbool/)
|
|
36
|
+
[](https://pypi.org/project/envbool/)
|
|
37
|
+
[](LICENSE)
|
|
38
|
+
[](https://github.com/jkomalley/envbool/actions/workflows/ci.yml)
|
|
39
|
+
|
|
40
|
+
</div>
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
Reading a boolean out of the environment is the kind of thing every project
|
|
45
|
+
reinvents, slightly differently, in slightly buggy ways:
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
DEBUG = os.environ.get("DEBUG", "").lower() in ("1", "true", "yes")
|
|
49
|
+
VERBOSE = os.environ.get("VERBOSE", "").lower() in ("1", "true", "yes")
|
|
50
|
+
CACHE = os.environ.get("CACHE", "").lower() in ("1", "true", "yes")
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
`envbool` is that snippet, done once and done properly:
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
from envbool import envbool
|
|
57
|
+
|
|
58
|
+
DEBUG = envbool("DEBUG")
|
|
59
|
+
VERBOSE = envbool("VERBOSE")
|
|
60
|
+
CACHE = envbool("CACHE")
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Features
|
|
64
|
+
|
|
65
|
+
- **Lenient by default, strict when you want it.** Unrecognized values quietly
|
|
66
|
+
become `False`, or raise on demand to catch typos in production config.
|
|
67
|
+
- **Always returns `bool`.** No `None`, no surprises in your type signatures.
|
|
68
|
+
- **Customizable value sets.** Replace or extend the truthy/falsy words your
|
|
69
|
+
environment uses.
|
|
70
|
+
- **Config files.** Share defaults across a project via `envbool.toml` or
|
|
71
|
+
`[tool.envbool]` in `pyproject.toml`.
|
|
72
|
+
- **A CLI for shell scripts.** Exit codes map to truthiness, so it drops
|
|
73
|
+
straight into `&&` / `||` chains.
|
|
74
|
+
- **Zero ceremony.** One dependency, fully typed, Python 3.11+.
|
|
75
|
+
|
|
76
|
+
## Contents
|
|
77
|
+
|
|
78
|
+
- [Installation](#installation)
|
|
79
|
+
- [Usage](#usage)
|
|
80
|
+
- [Command-line interface](#command-line-interface)
|
|
81
|
+
- [Configuration](#configuration)
|
|
82
|
+
- [API reference](#api-reference)
|
|
83
|
+
- [Advanced topics](#advanced-topics)
|
|
84
|
+
- [Contributing](#contributing)
|
|
85
|
+
- [License](#license)
|
|
86
|
+
|
|
87
|
+
## Installation
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
pip install envbool
|
|
91
|
+
# or
|
|
92
|
+
uv add envbool
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Usage
|
|
96
|
+
|
|
97
|
+
### The basics
|
|
98
|
+
|
|
99
|
+
`envbool` is **lenient by default**: anything not recognized as truthy returns
|
|
100
|
+
`False`, and unset or empty variables return the default.
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
from envbool import envbool
|
|
104
|
+
|
|
105
|
+
DEBUG = envbool("DEBUG") # False if unset or empty
|
|
106
|
+
CACHE = envbool("CACHE", default=True) # True if unset or empty
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
The built-in truthy values are `true`, `1`, `yes`, `on`; the falsy values are
|
|
110
|
+
`false`, `0`, `no`, `off`. Comparison is case-insensitive and ignores
|
|
111
|
+
surrounding whitespace.
|
|
112
|
+
|
|
113
|
+
### Strict mode
|
|
114
|
+
|
|
115
|
+
Pass `strict=True` to raise `InvalidBoolValueError` on anything outside the
|
|
116
|
+
truthy/falsy sets — ideal for failing fast on a misconfigured deployment.
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
import sys
|
|
120
|
+
from envbool import envbool, InvalidBoolValueError
|
|
121
|
+
|
|
122
|
+
try:
|
|
123
|
+
USE_SSL = envbool("USE_SSL", strict=True)
|
|
124
|
+
except InvalidBoolValueError as e:
|
|
125
|
+
sys.exit(f"Bad value for USE_SSL: {e.value!r}")
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Custom value sets
|
|
129
|
+
|
|
130
|
+
When your environment speaks a different dialect, **extend** the defaults or
|
|
131
|
+
**replace** them outright:
|
|
132
|
+
|
|
133
|
+
```python
|
|
134
|
+
# Add to the built-in sets
|
|
135
|
+
FEATURE = envbool("FEATURE_FLAG", extend_truthy={"enabled", "y"})
|
|
136
|
+
|
|
137
|
+
# Replace them entirely
|
|
138
|
+
LOCALE = envbool("USE_METRIC", truthy={"metric"}, falsy={"imperial"})
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Coercing arbitrary strings
|
|
142
|
+
|
|
143
|
+
Use `to_bool` for values that don't come from the environment. It accepts the
|
|
144
|
+
same keyword arguments as `envbool`.
|
|
145
|
+
|
|
146
|
+
```python
|
|
147
|
+
from envbool import to_bool
|
|
148
|
+
|
|
149
|
+
to_bool("yes") # True
|
|
150
|
+
to_bool("0") # False
|
|
151
|
+
to_bool("maybe", strict=True) # raises InvalidBoolValueError
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Command-line interface
|
|
155
|
+
|
|
156
|
+
The `envbool` command exits `0` for truthy, `1` for falsy, and `2` on error, so
|
|
157
|
+
it composes naturally with shell control flow.
|
|
158
|
+
|
|
159
|
+
```console
|
|
160
|
+
$ export DEBUG=true
|
|
161
|
+
$ envbool DEBUG && echo "debug is on"
|
|
162
|
+
debug is on
|
|
163
|
+
|
|
164
|
+
$ echo "Verbose: $(envbool --print VERBOSE)"
|
|
165
|
+
Verbose: false
|
|
166
|
+
|
|
167
|
+
$ echo "yes" | envbool && echo "truthy"
|
|
168
|
+
truthy
|
|
169
|
+
|
|
170
|
+
$ envbool --strict ENABLE_CACHE || echo "cache is off or misconfigured"
|
|
171
|
+
cache is off or misconfigured
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Input is taken from a `VAR_NAME` argument, the `--value` flag, or a stdin pipe —
|
|
175
|
+
in that order of priority.
|
|
176
|
+
|
|
177
|
+
```console
|
|
178
|
+
$ envbool --help
|
|
179
|
+
usage: envbool [-h] [--value TEXT] [--strict] [--warn] [--default] [--print]
|
|
180
|
+
[--truthy VALUE] [--falsy VALUE] [--extend-truthy VALUE]
|
|
181
|
+
[--extend-falsy VALUE] [--show-config]
|
|
182
|
+
[VAR_NAME]
|
|
183
|
+
|
|
184
|
+
Coerce an environment variable or string to a boolean.
|
|
185
|
+
|
|
186
|
+
positional arguments:
|
|
187
|
+
VAR_NAME Environment variable name to check.
|
|
188
|
+
|
|
189
|
+
options:
|
|
190
|
+
-h, --help show this help message and exit
|
|
191
|
+
--value, -v TEXT Check a literal string instead of an env var.
|
|
192
|
+
--strict, -s Raise error on unrecognized values.
|
|
193
|
+
--warn Log a warning on unrecognized values.
|
|
194
|
+
--default, -d Default value if unset/empty (default: false).
|
|
195
|
+
--print, -p Print "true" or "false" instead of using exit codes.
|
|
196
|
+
--truthy VALUE Replace the truthy set with VALUE (repeatable).
|
|
197
|
+
--falsy VALUE Replace the falsy set with VALUE (repeatable).
|
|
198
|
+
--extend-truthy VALUE
|
|
199
|
+
Add VALUE to the truthy set (repeatable).
|
|
200
|
+
--extend-falsy VALUE Add VALUE to the falsy set (repeatable).
|
|
201
|
+
--show-config Print the effective configuration and exit.
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
A few rules worth knowing:
|
|
205
|
+
|
|
206
|
+
- Omitting `--strict` / `--warn` defers to the config file setting.
|
|
207
|
+
- `VAR_NAME` and `--value` are mutually exclusive.
|
|
208
|
+
- `--show-config` prints the effective configuration and exits. It cannot be
|
|
209
|
+
combined with `VAR_NAME`, `--value`, `--print`, or `--default`, but it *can*
|
|
210
|
+
take the value-set flags to preview overrides.
|
|
211
|
+
- With no `VAR_NAME`, `--value`, or piped stdin, the CLI prints usage and exits `2`.
|
|
212
|
+
|
|
213
|
+
## Configuration
|
|
214
|
+
|
|
215
|
+
Share defaults across a project by dropping an `envbool.toml` at its root (or a
|
|
216
|
+
`[tool.envbool]` table in `pyproject.toml`):
|
|
217
|
+
|
|
218
|
+
```toml
|
|
219
|
+
# envbool.toml
|
|
220
|
+
strict = true
|
|
221
|
+
extend_truthy = ["enabled"]
|
|
222
|
+
extend_falsy = ["disabled"]
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
`envbool` walks up from the current directory to find the nearest project config,
|
|
226
|
+
then falls back to a user-level `config.toml` in the platform's standard config
|
|
227
|
+
directory (`~/.config/envbool/` on Linux, `~/Library/Application Support/envbool/`
|
|
228
|
+
on macOS), resolved via [platformdirs](https://pypi.org/project/platformdirs/).
|
|
229
|
+
|
|
230
|
+
Values resolve in three layers, each overriding the last:
|
|
231
|
+
|
|
232
|
+
```
|
|
233
|
+
built-in defaults → config file → function arguments / CLI flags
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
Set `ENVBOOL_NO_CONFIG=1` to skip config discovery entirely.
|
|
237
|
+
|
|
238
|
+
## API reference
|
|
239
|
+
|
|
240
|
+
| Symbol | Description |
|
|
241
|
+
| --- | --- |
|
|
242
|
+
| `envbool(var, **opts)` | Read an environment variable and return `bool`. |
|
|
243
|
+
| `to_bool(value, **opts)` | Coerce a string to `bool`. |
|
|
244
|
+
| `load_config()` | Load and return the active `EnvBoolConfig` (cached). |
|
|
245
|
+
| `EnvBoolConfig` | Frozen dataclass: `strict`, `warn`, `effective_truthy`, `effective_falsy`, `source_path`. |
|
|
246
|
+
| `DEFAULT_TRUTHY` | `frozenset` of the built-in truthy strings. |
|
|
247
|
+
| `DEFAULT_FALSY` | `frozenset` of the built-in falsy strings. |
|
|
248
|
+
| `EnvBoolError` | Base class for every exception the library raises. |
|
|
249
|
+
| `InvalidBoolValueError` | Raised in strict mode for unrecognized values. Also a `ValueError`. |
|
|
250
|
+
| `ConfigError` | Raised when a config file is malformed. |
|
|
251
|
+
|
|
252
|
+
`envbool()` and `to_bool()` share the same keyword-only options:
|
|
253
|
+
|
|
254
|
+
| Option | Type | Default | Meaning |
|
|
255
|
+
| --- | --- | --- | --- |
|
|
256
|
+
| `default` | `bool` | `False` | Returned for unset/empty input. |
|
|
257
|
+
| `strict` | `bool \| None` | `None` | Raise on unrecognized values (`None` defers to config). |
|
|
258
|
+
| `warn` | `bool \| None` | `None` | Log a warning on unrecognized values (`None` defers to config). |
|
|
259
|
+
| `truthy` / `falsy` | `Iterable[str] \| None` | `None` | **Replace** the effective set. |
|
|
260
|
+
| `extend_truthy` / `extend_falsy` | `Iterable[str] \| None` | `None` | **Extend** the effective set. |
|
|
261
|
+
|
|
262
|
+
## Advanced topics
|
|
263
|
+
|
|
264
|
+
### Exception handling
|
|
265
|
+
|
|
266
|
+
Every exception inherits from `EnvBoolError`, so a single `except EnvBoolError`
|
|
267
|
+
catches the whole library. Catch a specific subclass when you need its detail:
|
|
268
|
+
|
|
269
|
+
```python
|
|
270
|
+
from envbool import envbool, InvalidBoolValueError
|
|
271
|
+
|
|
272
|
+
try:
|
|
273
|
+
result = envbool("MY_VAR", strict=True)
|
|
274
|
+
except InvalidBoolValueError as e:
|
|
275
|
+
print(e.var) # "MY_VAR" — env var name, or None when raised from to_bool()
|
|
276
|
+
print(e.value) # "maybe" — the normalized (stripped, lowercased) value
|
|
277
|
+
print(e.truthy) # frozenset({"true", "1", "yes", "on"}) — effective truthy set
|
|
278
|
+
print(e.falsy) # frozenset({"false", "0", "no", "off"}) — effective falsy set
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
`InvalidBoolValueError` also subclasses the built-in `ValueError`, so existing
|
|
282
|
+
`except ValueError` handlers keep working. Its message spells out exactly what
|
|
283
|
+
was expected:
|
|
284
|
+
|
|
285
|
+
```
|
|
286
|
+
InvalidBoolValueError: Invalid boolean value for MY_VAR: 'maybe'
|
|
287
|
+
Expected truthy: 1, on, true, yes
|
|
288
|
+
Expected falsy: 0, false, no, off
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
`ConfigError` is raised when a config file is found but malformed (for example,
|
|
292
|
+
`strict = "yes"` instead of `strict = true`). It carries the offending path on
|
|
293
|
+
`e.path`.
|
|
294
|
+
|
|
295
|
+
### Logging
|
|
296
|
+
|
|
297
|
+
`envbool` logs through the standard `logging` module under the `"envbool"`
|
|
298
|
+
namespace and attaches no handlers of its own — configure it like any other
|
|
299
|
+
library logger:
|
|
300
|
+
|
|
301
|
+
```python
|
|
302
|
+
import logging
|
|
303
|
+
|
|
304
|
+
logging.getLogger("envbool").setLevel(logging.DEBUG)
|
|
305
|
+
logging.getLogger("envbool").addHandler(logging.StreamHandler())
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
| Level | When |
|
|
309
|
+
| --- | --- |
|
|
310
|
+
| `DEBUG` | A config file was discovered and loaded, or none was found. |
|
|
311
|
+
| `WARNING` | An unrecognized value fell through in lenient mode (only when `warn=True`). |
|
|
312
|
+
| `WARNING` | The truthy and falsy sets overlap (truthy wins). |
|
|
313
|
+
|
|
314
|
+
### The unset-vs-empty distinction
|
|
315
|
+
|
|
316
|
+
`envbool()` always returns `bool` and deliberately cannot tell an unset variable
|
|
317
|
+
apart from one set to the empty string — both yield `default`. Most deployment
|
|
318
|
+
tooling can't distinguish the two either, and a plain `bool` keeps call sites
|
|
319
|
+
clean. When you genuinely need the distinction, check `os.environ` yourself:
|
|
320
|
+
|
|
321
|
+
```python
|
|
322
|
+
import os
|
|
323
|
+
from envbool import envbool
|
|
324
|
+
|
|
325
|
+
if "MY_VAR" not in os.environ:
|
|
326
|
+
... # truly unset — handle the "not configured" case
|
|
327
|
+
else:
|
|
328
|
+
result = envbool("MY_VAR")
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
### Testing code that uses envbool
|
|
332
|
+
|
|
333
|
+
`envbool` loads its config file once and caches it for the process lifetime. If
|
|
334
|
+
your tests create temporary config files, clear that cache between them with an
|
|
335
|
+
autouse fixture:
|
|
336
|
+
|
|
337
|
+
```python
|
|
338
|
+
# conftest.py
|
|
339
|
+
import pytest
|
|
340
|
+
from envbool._config import _reset_config
|
|
341
|
+
|
|
342
|
+
@pytest.fixture(autouse=True)
|
|
343
|
+
def _reset_envbool_config():
|
|
344
|
+
yield
|
|
345
|
+
_reset_config()
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
`_reset_config()` is private but stable and exists for exactly this purpose; it
|
|
349
|
+
clears the cache under a lock, so it is safe to call from any thread.
|
|
350
|
+
|
|
351
|
+
## Contributing
|
|
352
|
+
|
|
353
|
+
Contributions are welcome. See [CONTRIBUTING.md](CONTRIBUTING.md) for development
|
|
354
|
+
setup, project layout, and the conventions this repo follows.
|
|
355
|
+
|
|
356
|
+
## License
|
|
357
|
+
|
|
358
|
+
Released under the [MIT License](LICENSE).
|
envbool-0.2.0/README.md
ADDED
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# envbool
|
|
4
|
+
|
|
5
|
+
**Coerce environment variables and strings into booleans — sensibly.**
|
|
6
|
+
|
|
7
|
+
[](https://pypi.org/project/envbool/)
|
|
8
|
+
[](https://pypi.org/project/envbool/)
|
|
9
|
+
[](LICENSE)
|
|
10
|
+
[](https://github.com/jkomalley/envbool/actions/workflows/ci.yml)
|
|
11
|
+
|
|
12
|
+
</div>
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
Reading a boolean out of the environment is the kind of thing every project
|
|
17
|
+
reinvents, slightly differently, in slightly buggy ways:
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
DEBUG = os.environ.get("DEBUG", "").lower() in ("1", "true", "yes")
|
|
21
|
+
VERBOSE = os.environ.get("VERBOSE", "").lower() in ("1", "true", "yes")
|
|
22
|
+
CACHE = os.environ.get("CACHE", "").lower() in ("1", "true", "yes")
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
`envbool` is that snippet, done once and done properly:
|
|
26
|
+
|
|
27
|
+
```python
|
|
28
|
+
from envbool import envbool
|
|
29
|
+
|
|
30
|
+
DEBUG = envbool("DEBUG")
|
|
31
|
+
VERBOSE = envbool("VERBOSE")
|
|
32
|
+
CACHE = envbool("CACHE")
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Features
|
|
36
|
+
|
|
37
|
+
- **Lenient by default, strict when you want it.** Unrecognized values quietly
|
|
38
|
+
become `False`, or raise on demand to catch typos in production config.
|
|
39
|
+
- **Always returns `bool`.** No `None`, no surprises in your type signatures.
|
|
40
|
+
- **Customizable value sets.** Replace or extend the truthy/falsy words your
|
|
41
|
+
environment uses.
|
|
42
|
+
- **Config files.** Share defaults across a project via `envbool.toml` or
|
|
43
|
+
`[tool.envbool]` in `pyproject.toml`.
|
|
44
|
+
- **A CLI for shell scripts.** Exit codes map to truthiness, so it drops
|
|
45
|
+
straight into `&&` / `||` chains.
|
|
46
|
+
- **Zero ceremony.** One dependency, fully typed, Python 3.11+.
|
|
47
|
+
|
|
48
|
+
## Contents
|
|
49
|
+
|
|
50
|
+
- [Installation](#installation)
|
|
51
|
+
- [Usage](#usage)
|
|
52
|
+
- [Command-line interface](#command-line-interface)
|
|
53
|
+
- [Configuration](#configuration)
|
|
54
|
+
- [API reference](#api-reference)
|
|
55
|
+
- [Advanced topics](#advanced-topics)
|
|
56
|
+
- [Contributing](#contributing)
|
|
57
|
+
- [License](#license)
|
|
58
|
+
|
|
59
|
+
## Installation
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
pip install envbool
|
|
63
|
+
# or
|
|
64
|
+
uv add envbool
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Usage
|
|
68
|
+
|
|
69
|
+
### The basics
|
|
70
|
+
|
|
71
|
+
`envbool` is **lenient by default**: anything not recognized as truthy returns
|
|
72
|
+
`False`, and unset or empty variables return the default.
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
from envbool import envbool
|
|
76
|
+
|
|
77
|
+
DEBUG = envbool("DEBUG") # False if unset or empty
|
|
78
|
+
CACHE = envbool("CACHE", default=True) # True if unset or empty
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
The built-in truthy values are `true`, `1`, `yes`, `on`; the falsy values are
|
|
82
|
+
`false`, `0`, `no`, `off`. Comparison is case-insensitive and ignores
|
|
83
|
+
surrounding whitespace.
|
|
84
|
+
|
|
85
|
+
### Strict mode
|
|
86
|
+
|
|
87
|
+
Pass `strict=True` to raise `InvalidBoolValueError` on anything outside the
|
|
88
|
+
truthy/falsy sets — ideal for failing fast on a misconfigured deployment.
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
import sys
|
|
92
|
+
from envbool import envbool, InvalidBoolValueError
|
|
93
|
+
|
|
94
|
+
try:
|
|
95
|
+
USE_SSL = envbool("USE_SSL", strict=True)
|
|
96
|
+
except InvalidBoolValueError as e:
|
|
97
|
+
sys.exit(f"Bad value for USE_SSL: {e.value!r}")
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Custom value sets
|
|
101
|
+
|
|
102
|
+
When your environment speaks a different dialect, **extend** the defaults or
|
|
103
|
+
**replace** them outright:
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
# Add to the built-in sets
|
|
107
|
+
FEATURE = envbool("FEATURE_FLAG", extend_truthy={"enabled", "y"})
|
|
108
|
+
|
|
109
|
+
# Replace them entirely
|
|
110
|
+
LOCALE = envbool("USE_METRIC", truthy={"metric"}, falsy={"imperial"})
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Coercing arbitrary strings
|
|
114
|
+
|
|
115
|
+
Use `to_bool` for values that don't come from the environment. It accepts the
|
|
116
|
+
same keyword arguments as `envbool`.
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
from envbool import to_bool
|
|
120
|
+
|
|
121
|
+
to_bool("yes") # True
|
|
122
|
+
to_bool("0") # False
|
|
123
|
+
to_bool("maybe", strict=True) # raises InvalidBoolValueError
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Command-line interface
|
|
127
|
+
|
|
128
|
+
The `envbool` command exits `0` for truthy, `1` for falsy, and `2` on error, so
|
|
129
|
+
it composes naturally with shell control flow.
|
|
130
|
+
|
|
131
|
+
```console
|
|
132
|
+
$ export DEBUG=true
|
|
133
|
+
$ envbool DEBUG && echo "debug is on"
|
|
134
|
+
debug is on
|
|
135
|
+
|
|
136
|
+
$ echo "Verbose: $(envbool --print VERBOSE)"
|
|
137
|
+
Verbose: false
|
|
138
|
+
|
|
139
|
+
$ echo "yes" | envbool && echo "truthy"
|
|
140
|
+
truthy
|
|
141
|
+
|
|
142
|
+
$ envbool --strict ENABLE_CACHE || echo "cache is off or misconfigured"
|
|
143
|
+
cache is off or misconfigured
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Input is taken from a `VAR_NAME` argument, the `--value` flag, or a stdin pipe —
|
|
147
|
+
in that order of priority.
|
|
148
|
+
|
|
149
|
+
```console
|
|
150
|
+
$ envbool --help
|
|
151
|
+
usage: envbool [-h] [--value TEXT] [--strict] [--warn] [--default] [--print]
|
|
152
|
+
[--truthy VALUE] [--falsy VALUE] [--extend-truthy VALUE]
|
|
153
|
+
[--extend-falsy VALUE] [--show-config]
|
|
154
|
+
[VAR_NAME]
|
|
155
|
+
|
|
156
|
+
Coerce an environment variable or string to a boolean.
|
|
157
|
+
|
|
158
|
+
positional arguments:
|
|
159
|
+
VAR_NAME Environment variable name to check.
|
|
160
|
+
|
|
161
|
+
options:
|
|
162
|
+
-h, --help show this help message and exit
|
|
163
|
+
--value, -v TEXT Check a literal string instead of an env var.
|
|
164
|
+
--strict, -s Raise error on unrecognized values.
|
|
165
|
+
--warn Log a warning on unrecognized values.
|
|
166
|
+
--default, -d Default value if unset/empty (default: false).
|
|
167
|
+
--print, -p Print "true" or "false" instead of using exit codes.
|
|
168
|
+
--truthy VALUE Replace the truthy set with VALUE (repeatable).
|
|
169
|
+
--falsy VALUE Replace the falsy set with VALUE (repeatable).
|
|
170
|
+
--extend-truthy VALUE
|
|
171
|
+
Add VALUE to the truthy set (repeatable).
|
|
172
|
+
--extend-falsy VALUE Add VALUE to the falsy set (repeatable).
|
|
173
|
+
--show-config Print the effective configuration and exit.
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
A few rules worth knowing:
|
|
177
|
+
|
|
178
|
+
- Omitting `--strict` / `--warn` defers to the config file setting.
|
|
179
|
+
- `VAR_NAME` and `--value` are mutually exclusive.
|
|
180
|
+
- `--show-config` prints the effective configuration and exits. It cannot be
|
|
181
|
+
combined with `VAR_NAME`, `--value`, `--print`, or `--default`, but it *can*
|
|
182
|
+
take the value-set flags to preview overrides.
|
|
183
|
+
- With no `VAR_NAME`, `--value`, or piped stdin, the CLI prints usage and exits `2`.
|
|
184
|
+
|
|
185
|
+
## Configuration
|
|
186
|
+
|
|
187
|
+
Share defaults across a project by dropping an `envbool.toml` at its root (or a
|
|
188
|
+
`[tool.envbool]` table in `pyproject.toml`):
|
|
189
|
+
|
|
190
|
+
```toml
|
|
191
|
+
# envbool.toml
|
|
192
|
+
strict = true
|
|
193
|
+
extend_truthy = ["enabled"]
|
|
194
|
+
extend_falsy = ["disabled"]
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
`envbool` walks up from the current directory to find the nearest project config,
|
|
198
|
+
then falls back to a user-level `config.toml` in the platform's standard config
|
|
199
|
+
directory (`~/.config/envbool/` on Linux, `~/Library/Application Support/envbool/`
|
|
200
|
+
on macOS), resolved via [platformdirs](https://pypi.org/project/platformdirs/).
|
|
201
|
+
|
|
202
|
+
Values resolve in three layers, each overriding the last:
|
|
203
|
+
|
|
204
|
+
```
|
|
205
|
+
built-in defaults → config file → function arguments / CLI flags
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
Set `ENVBOOL_NO_CONFIG=1` to skip config discovery entirely.
|
|
209
|
+
|
|
210
|
+
## API reference
|
|
211
|
+
|
|
212
|
+
| Symbol | Description |
|
|
213
|
+
| --- | --- |
|
|
214
|
+
| `envbool(var, **opts)` | Read an environment variable and return `bool`. |
|
|
215
|
+
| `to_bool(value, **opts)` | Coerce a string to `bool`. |
|
|
216
|
+
| `load_config()` | Load and return the active `EnvBoolConfig` (cached). |
|
|
217
|
+
| `EnvBoolConfig` | Frozen dataclass: `strict`, `warn`, `effective_truthy`, `effective_falsy`, `source_path`. |
|
|
218
|
+
| `DEFAULT_TRUTHY` | `frozenset` of the built-in truthy strings. |
|
|
219
|
+
| `DEFAULT_FALSY` | `frozenset` of the built-in falsy strings. |
|
|
220
|
+
| `EnvBoolError` | Base class for every exception the library raises. |
|
|
221
|
+
| `InvalidBoolValueError` | Raised in strict mode for unrecognized values. Also a `ValueError`. |
|
|
222
|
+
| `ConfigError` | Raised when a config file is malformed. |
|
|
223
|
+
|
|
224
|
+
`envbool()` and `to_bool()` share the same keyword-only options:
|
|
225
|
+
|
|
226
|
+
| Option | Type | Default | Meaning |
|
|
227
|
+
| --- | --- | --- | --- |
|
|
228
|
+
| `default` | `bool` | `False` | Returned for unset/empty input. |
|
|
229
|
+
| `strict` | `bool \| None` | `None` | Raise on unrecognized values (`None` defers to config). |
|
|
230
|
+
| `warn` | `bool \| None` | `None` | Log a warning on unrecognized values (`None` defers to config). |
|
|
231
|
+
| `truthy` / `falsy` | `Iterable[str] \| None` | `None` | **Replace** the effective set. |
|
|
232
|
+
| `extend_truthy` / `extend_falsy` | `Iterable[str] \| None` | `None` | **Extend** the effective set. |
|
|
233
|
+
|
|
234
|
+
## Advanced topics
|
|
235
|
+
|
|
236
|
+
### Exception handling
|
|
237
|
+
|
|
238
|
+
Every exception inherits from `EnvBoolError`, so a single `except EnvBoolError`
|
|
239
|
+
catches the whole library. Catch a specific subclass when you need its detail:
|
|
240
|
+
|
|
241
|
+
```python
|
|
242
|
+
from envbool import envbool, InvalidBoolValueError
|
|
243
|
+
|
|
244
|
+
try:
|
|
245
|
+
result = envbool("MY_VAR", strict=True)
|
|
246
|
+
except InvalidBoolValueError as e:
|
|
247
|
+
print(e.var) # "MY_VAR" — env var name, or None when raised from to_bool()
|
|
248
|
+
print(e.value) # "maybe" — the normalized (stripped, lowercased) value
|
|
249
|
+
print(e.truthy) # frozenset({"true", "1", "yes", "on"}) — effective truthy set
|
|
250
|
+
print(e.falsy) # frozenset({"false", "0", "no", "off"}) — effective falsy set
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
`InvalidBoolValueError` also subclasses the built-in `ValueError`, so existing
|
|
254
|
+
`except ValueError` handlers keep working. Its message spells out exactly what
|
|
255
|
+
was expected:
|
|
256
|
+
|
|
257
|
+
```
|
|
258
|
+
InvalidBoolValueError: Invalid boolean value for MY_VAR: 'maybe'
|
|
259
|
+
Expected truthy: 1, on, true, yes
|
|
260
|
+
Expected falsy: 0, false, no, off
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
`ConfigError` is raised when a config file is found but malformed (for example,
|
|
264
|
+
`strict = "yes"` instead of `strict = true`). It carries the offending path on
|
|
265
|
+
`e.path`.
|
|
266
|
+
|
|
267
|
+
### Logging
|
|
268
|
+
|
|
269
|
+
`envbool` logs through the standard `logging` module under the `"envbool"`
|
|
270
|
+
namespace and attaches no handlers of its own — configure it like any other
|
|
271
|
+
library logger:
|
|
272
|
+
|
|
273
|
+
```python
|
|
274
|
+
import logging
|
|
275
|
+
|
|
276
|
+
logging.getLogger("envbool").setLevel(logging.DEBUG)
|
|
277
|
+
logging.getLogger("envbool").addHandler(logging.StreamHandler())
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
| Level | When |
|
|
281
|
+
| --- | --- |
|
|
282
|
+
| `DEBUG` | A config file was discovered and loaded, or none was found. |
|
|
283
|
+
| `WARNING` | An unrecognized value fell through in lenient mode (only when `warn=True`). |
|
|
284
|
+
| `WARNING` | The truthy and falsy sets overlap (truthy wins). |
|
|
285
|
+
|
|
286
|
+
### The unset-vs-empty distinction
|
|
287
|
+
|
|
288
|
+
`envbool()` always returns `bool` and deliberately cannot tell an unset variable
|
|
289
|
+
apart from one set to the empty string — both yield `default`. Most deployment
|
|
290
|
+
tooling can't distinguish the two either, and a plain `bool` keeps call sites
|
|
291
|
+
clean. When you genuinely need the distinction, check `os.environ` yourself:
|
|
292
|
+
|
|
293
|
+
```python
|
|
294
|
+
import os
|
|
295
|
+
from envbool import envbool
|
|
296
|
+
|
|
297
|
+
if "MY_VAR" not in os.environ:
|
|
298
|
+
... # truly unset — handle the "not configured" case
|
|
299
|
+
else:
|
|
300
|
+
result = envbool("MY_VAR")
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
### Testing code that uses envbool
|
|
304
|
+
|
|
305
|
+
`envbool` loads its config file once and caches it for the process lifetime. If
|
|
306
|
+
your tests create temporary config files, clear that cache between them with an
|
|
307
|
+
autouse fixture:
|
|
308
|
+
|
|
309
|
+
```python
|
|
310
|
+
# conftest.py
|
|
311
|
+
import pytest
|
|
312
|
+
from envbool._config import _reset_config
|
|
313
|
+
|
|
314
|
+
@pytest.fixture(autouse=True)
|
|
315
|
+
def _reset_envbool_config():
|
|
316
|
+
yield
|
|
317
|
+
_reset_config()
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
`_reset_config()` is private but stable and exists for exactly this purpose; it
|
|
321
|
+
clears the cache under a lock, so it is safe to call from any thread.
|
|
322
|
+
|
|
323
|
+
## Contributing
|
|
324
|
+
|
|
325
|
+
Contributions are welcome. See [CONTRIBUTING.md](CONTRIBUTING.md) for development
|
|
326
|
+
setup, project layout, and the conventions this repo follows.
|
|
327
|
+
|
|
328
|
+
## License
|
|
329
|
+
|
|
330
|
+
Released under the [MIT License](LICENSE).
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "envbool"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.2.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" }]
|