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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: envbool
3
- Version: 0.5.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
  [![PyPI version](https://img.shields.io/pypi/v/envbool)](https://pypi.org/project/envbool/)
36
+ [![Snap Store](https://snapcraft.io/envbool/badge.svg)](https://snapcraft.io/envbool)
36
37
  [![Python versions](https://img.shields.io/pypi/pyversions/envbool)](https://pypi.org/project/envbool/)
37
38
  [![License: MIT](https://img.shields.io/github/license/jkomalley/envbool)](LICENSE)
38
39
  [![CI](https://github.com/jkomalley/envbool/actions/workflows/ci.yml/badge.svg)](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 `envbool` command is also available as a strictly confined
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 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.
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 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:
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, with the policy set up front. With strict mode on, a typo like
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.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
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
- - Omitting `--strict` / `--warn` uses the built-in defaults (lenient, no
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 \| None` | `None` | Raise on unrecognized values (`None` defers to `set_defaults()`). |
368
- | `warn` | `bool \| None` | `None` | Log a warning on unrecognized values (`None` defers to `set_defaults()`). |
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
  [![PyPI version](https://img.shields.io/pypi/v/envbool)](https://pypi.org/project/envbool/)
8
+ [![Snap Store](https://snapcraft.io/envbool/badge.svg)](https://snapcraft.io/envbool)
8
9
  [![Python versions](https://img.shields.io/pypi/pyversions/envbool)](https://pypi.org/project/envbool/)
9
10
  [![License: MIT](https://img.shields.io/github/license/jkomalley/envbool)](LICENSE)
10
11
  [![CI](https://github.com/jkomalley/envbool/actions/workflows/ci.yml/badge.svg)](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 `envbool` command is also available as a strictly confined
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 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.
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 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:
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, with the policy set up front. With strict mode on, a typo like
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.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
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
- - Omitting `--strict` / `--warn` uses the built-in defaults (lenient, no
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 \| None` | `None` | Raise on unrecognized values (`None` defers to `set_defaults()`). |
340
- | `warn` | `bool \| None` | `None` | Log a warning on unrecognized values (`None` defers to `set_defaults()`). |
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.5.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
  requires-python = ">=3.11"
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "envbool"
3
- version = "0.5.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, _config, _cli, or _defaults directly.
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
- Omitting --strict or --warn defers to the process-level defaults
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 (from _defaults)
5
- DEFAULT_FALSY -- the built-in falsy set (from _defaults)
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 layered inputs
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 does
12
- # consult get_defaults() from _defaults.py so that strict=None/warn=None defer
13
- # to the process-level defaults (set_defaults()) rather than always defaulting
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 "envbool.config" or the root "envbool" logger.
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 | None = None,
41
- warn: bool | None = None,
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. None defers to process-level
54
- defaults (set_defaults()) (default False).
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
- # Read the process-level defaults (set once via set_defaults(), or the
81
- # built-ins if never called). _resolve then applies the full three-level
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 effective_strict:
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 effective_strict:
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
- effective_warn = warn if warn is not None else defaults.warn
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(config_truthy, truthy, extend_truthy)
178
- effective_falsy = _apply_replace_then_extend(config_falsy, falsy, extend_falsy)
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() (+ get_defaults())
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 | None = None,
26
- warn: bool | None = None,
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. None defers to process-level
41
- defaults (set_defaults()) (default False).
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