duckenv 0.1.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.
- duckenv-0.1.0/.gitignore +3 -0
- duckenv-0.1.0/.woodpecker/lint.yml +13 -0
- duckenv-0.1.0/.woodpecker/release.yml +20 -0
- duckenv-0.1.0/.woodpecker/tests.yml +20 -0
- duckenv-0.1.0/PKG-INFO +66 -0
- duckenv-0.1.0/README.md +48 -0
- duckenv-0.1.0/duckenv.py +158 -0
- duckenv-0.1.0/pyproject.toml +36 -0
- duckenv-0.1.0/tests.py +152 -0
duckenv-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
steps:
|
|
2
|
+
release:
|
|
3
|
+
image: python:3.11-slim
|
|
4
|
+
commands:
|
|
5
|
+
- pip install build twine
|
|
6
|
+
- python -m build
|
|
7
|
+
- twine upload --disable-progress-bar dist/*
|
|
8
|
+
environment:
|
|
9
|
+
TWINE_REPOSITORY:
|
|
10
|
+
from_secret: twine_repository
|
|
11
|
+
TWINE_USERNAME:
|
|
12
|
+
from_secret: twine_username
|
|
13
|
+
TWINE_PASSWORD:
|
|
14
|
+
from_secret: twine_password
|
|
15
|
+
when:
|
|
16
|
+
event: tag
|
|
17
|
+
|
|
18
|
+
depends_on:
|
|
19
|
+
- lint
|
|
20
|
+
- tests
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
matrix:
|
|
2
|
+
PY_VERSION:
|
|
3
|
+
- 3.11
|
|
4
|
+
- 3.12
|
|
5
|
+
- 3.13
|
|
6
|
+
- 3.14
|
|
7
|
+
|
|
8
|
+
steps:
|
|
9
|
+
tests:
|
|
10
|
+
image: python:${PY_VERSION}-slim
|
|
11
|
+
environment:
|
|
12
|
+
PIP_ROOT_USER_ACTION: ignore
|
|
13
|
+
commands:
|
|
14
|
+
- pip install .[tests]
|
|
15
|
+
- coverage run -m unittest -v tests.py
|
|
16
|
+
- coverage report -m
|
|
17
|
+
when:
|
|
18
|
+
- event: pull_request
|
|
19
|
+
- event: push
|
|
20
|
+
branch: main
|
duckenv-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: duckenv
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A minimalist .env loader
|
|
5
|
+
Project-URL: Homepage, https://codeberg.org/canarduck/duckenv
|
|
6
|
+
Project-URL: Issues, https://codeberg.org/canarduck/duckenv/issues
|
|
7
|
+
Author-email: Renaud Canarduck <renaud@canarduck.com>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Requires-Python: >=3.11
|
|
12
|
+
Provides-Extra: dev
|
|
13
|
+
Requires-Dist: black; extra == 'dev'
|
|
14
|
+
Requires-Dist: mypy; extra == 'dev'
|
|
15
|
+
Provides-Extra: tests
|
|
16
|
+
Requires-Dist: coverage; extra == 'tests'
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
|
|
19
|
+
# duckenv
|
|
20
|
+
|
|
21
|
+
A minimalist dotenv loader for Python, inspired by [environs](https://github.com/sloria/environs) but with fewer features and dependencies.
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pip install duckenv
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Usage
|
|
30
|
+
|
|
31
|
+
Create a `.env` file:
|
|
32
|
+
```env
|
|
33
|
+
DEBUG=on
|
|
34
|
+
TTL=30
|
|
35
|
+
NAME=Jean-Michel
|
|
36
|
+
MY_LIST=apple,banana,orange
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Load and parse environment variables:
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
from duckenv import Env
|
|
43
|
+
|
|
44
|
+
env = Env() # reads `.env` by default
|
|
45
|
+
|
|
46
|
+
env.str("NAME") # "Jean-Michel"
|
|
47
|
+
env.int("AGE") # 30
|
|
48
|
+
env.bool("DEBUG") # True
|
|
49
|
+
env.list("MY_LIST") # ["apple", "banana", "orange"]
|
|
50
|
+
env.str("MISSING_VAR", "default") # "default"
|
|
51
|
+
env.str("MISSING_VAR") # Raises KeyError
|
|
52
|
+
env.int("NAME") # Raises ValueError
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Supported Methods
|
|
56
|
+
|
|
57
|
+
- `str(name, default=...)`: Returns a string or `None`.
|
|
58
|
+
- `int(name, default=...)`: Returns an integer or `None`.
|
|
59
|
+
- `bool(name, default=...)`: Accepts `true/false`, `on/off`, `1/0`, returns `True/False` or `None`.
|
|
60
|
+
- `list(name, default=...)`: Parses comma-separated strings into a list of stripped strings, or accepts a list directly.
|
|
61
|
+
|
|
62
|
+
If a variable is not set and no default is provided, a `KeyError` is raised.
|
|
63
|
+
|
|
64
|
+
## License
|
|
65
|
+
|
|
66
|
+
MIT
|
duckenv-0.1.0/README.md
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# duckenv
|
|
2
|
+
|
|
3
|
+
A minimalist dotenv loader for Python, inspired by [environs](https://github.com/sloria/environs) but with fewer features and dependencies.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install duckenv
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
Create a `.env` file:
|
|
14
|
+
```env
|
|
15
|
+
DEBUG=on
|
|
16
|
+
TTL=30
|
|
17
|
+
NAME=Jean-Michel
|
|
18
|
+
MY_LIST=apple,banana,orange
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Load and parse environment variables:
|
|
22
|
+
|
|
23
|
+
```python
|
|
24
|
+
from duckenv import Env
|
|
25
|
+
|
|
26
|
+
env = Env() # reads `.env` by default
|
|
27
|
+
|
|
28
|
+
env.str("NAME") # "Jean-Michel"
|
|
29
|
+
env.int("AGE") # 30
|
|
30
|
+
env.bool("DEBUG") # True
|
|
31
|
+
env.list("MY_LIST") # ["apple", "banana", "orange"]
|
|
32
|
+
env.str("MISSING_VAR", "default") # "default"
|
|
33
|
+
env.str("MISSING_VAR") # Raises KeyError
|
|
34
|
+
env.int("NAME") # Raises ValueError
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Supported Methods
|
|
38
|
+
|
|
39
|
+
- `str(name, default=...)`: Returns a string or `None`.
|
|
40
|
+
- `int(name, default=...)`: Returns an integer or `None`.
|
|
41
|
+
- `bool(name, default=...)`: Accepts `true/false`, `on/off`, `1/0`, returns `True/False` or `None`.
|
|
42
|
+
- `list(name, default=...)`: Parses comma-separated strings into a list of stripped strings, or accepts a list directly.
|
|
43
|
+
|
|
44
|
+
If a variable is not set and no default is provided, a `KeyError` is raised.
|
|
45
|
+
|
|
46
|
+
## License
|
|
47
|
+
|
|
48
|
+
MIT
|
duckenv-0.1.0/duckenv.py
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"""duckenv."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
DEFAULT = object()
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Env:
|
|
11
|
+
"""A Minimalist dotenv loader.
|
|
12
|
+
|
|
13
|
+
Inspired by [environs](https://github.com/sloria/environs) with less features and
|
|
14
|
+
dependencies.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
implemented_methods = ["str", "int", "bool", "list", "bool"]
|
|
18
|
+
|
|
19
|
+
def __init__(self, env_file: Path = Path(".env")) -> None:
|
|
20
|
+
"""Loads `env_file` in the environnement variables.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
env_file: the dotenv file
|
|
24
|
+
"""
|
|
25
|
+
if not env_file.is_file():
|
|
26
|
+
raise FileNotFoundError(f"{env_file} does not exist")
|
|
27
|
+
for idx, line in enumerate(env_file.read_text().splitlines(), 1):
|
|
28
|
+
try:
|
|
29
|
+
name, value = line.split("=", 1)
|
|
30
|
+
except ValueError:
|
|
31
|
+
raise ValueError(
|
|
32
|
+
f"Unable to parse line {idx} of {env_file}, must be NAME=value"
|
|
33
|
+
)
|
|
34
|
+
os.environ[name] = value
|
|
35
|
+
|
|
36
|
+
def __getattr__(self, method: str) -> None:
|
|
37
|
+
"""Aliases implemented methods to their `get_` equivalent.
|
|
38
|
+
|
|
39
|
+
To avoid using defining `str`, `int` methods (which makes mypy unhappy, we use
|
|
40
|
+
`get_str`, `get_int` and handle their `natural` aliases here.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
method: A method name (str, list, ...)
|
|
44
|
+
|
|
45
|
+
Raises:
|
|
46
|
+
NotImplementedError: the method (for example `dict`) is not implemented
|
|
47
|
+
"""
|
|
48
|
+
if method in self.implemented_methods:
|
|
49
|
+
return getattr(self, f"get_{method}")
|
|
50
|
+
else:
|
|
51
|
+
raise NotImplementedError(f"Method {method} is not supported")
|
|
52
|
+
|
|
53
|
+
def _get(self, name: str, default: Any) -> Any:
|
|
54
|
+
"""Get the value of `name` from the env variables.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
name: variable's name
|
|
58
|
+
default: default value
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
`name`'s value or `default`
|
|
62
|
+
|
|
63
|
+
Raises:
|
|
64
|
+
KeyError: `name` is not defined in the env and no `default` to use
|
|
65
|
+
"""
|
|
66
|
+
if default == DEFAULT and name not in os.environ:
|
|
67
|
+
raise KeyError(f"Env var {name} is required")
|
|
68
|
+
return os.environ.get(name, default)
|
|
69
|
+
|
|
70
|
+
def get_str(self, name: str, default: Any = DEFAULT) -> str | None:
|
|
71
|
+
"""Cast `name` in a string.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
name: variable's name
|
|
75
|
+
default: default value if `name` is not defined in the env
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
A string or None
|
|
79
|
+
|
|
80
|
+
Raises:
|
|
81
|
+
KeyError: `name` is not defined in the env and no `default` to use
|
|
82
|
+
"""
|
|
83
|
+
value = self._get(name, default)
|
|
84
|
+
if value is None:
|
|
85
|
+
return None
|
|
86
|
+
return str(value)
|
|
87
|
+
|
|
88
|
+
def get_list(self, name: str, default: Any = DEFAULT) -> list | None:
|
|
89
|
+
"""Cast `name` in a list.
|
|
90
|
+
|
|
91
|
+
This only handles a list of strings.
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
name: variable's name
|
|
95
|
+
default: default value if `name` is not defined in the env
|
|
96
|
+
|
|
97
|
+
Returns:
|
|
98
|
+
A list or None
|
|
99
|
+
|
|
100
|
+
Raises:
|
|
101
|
+
ValueError: `name`'s value is nor a list nor a comma-separated string
|
|
102
|
+
KeyError: `name` is not defined in the env and no `default` to use
|
|
103
|
+
"""
|
|
104
|
+
value = self._get(name, default)
|
|
105
|
+
if value is None or isinstance(value, list):
|
|
106
|
+
return value
|
|
107
|
+
if not isinstance(value, str):
|
|
108
|
+
raise ValueError(
|
|
109
|
+
f"{name} must be a list or a string of comma-separated values, not {value} ({type(value)})"
|
|
110
|
+
)
|
|
111
|
+
return [v.strip() for v in value.split(",")]
|
|
112
|
+
|
|
113
|
+
def get_int(self, name: str, default: Any = DEFAULT) -> int | None:
|
|
114
|
+
"""Cast `name` in an integer.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
name: variable's name
|
|
118
|
+
default: default value if `name` is not defined in the env
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
An integer or None
|
|
122
|
+
|
|
123
|
+
Raises:
|
|
124
|
+
KeyError: `name` is not defined in the env and no `default` to use
|
|
125
|
+
"""
|
|
126
|
+
value = self._get(name, default)
|
|
127
|
+
if value is None or isinstance(value, int):
|
|
128
|
+
return value
|
|
129
|
+
try:
|
|
130
|
+
return int(value)
|
|
131
|
+
except ValueError:
|
|
132
|
+
raise ValueError(f"{name} is not int-able, « {value} » ({type(value)})")
|
|
133
|
+
|
|
134
|
+
def get_bool(self, name: str, default: Any = DEFAULT) -> bool | None:
|
|
135
|
+
"""Cast `name` in a bool.
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
name: variable's name
|
|
139
|
+
default: default value if `name` is not defined in the env
|
|
140
|
+
|
|
141
|
+
Returns:
|
|
142
|
+
A boolean value or None
|
|
143
|
+
|
|
144
|
+
Raises:
|
|
145
|
+
ValueError: `name`'s value is not bool-able
|
|
146
|
+
KeyError: `name` is not defined in the env and no `default` to use
|
|
147
|
+
"""
|
|
148
|
+
value = self._get(name, default)
|
|
149
|
+
if value is None or isinstance(value, bool):
|
|
150
|
+
return value
|
|
151
|
+
if value in [0, 1]:
|
|
152
|
+
return bool(value)
|
|
153
|
+
possible_values = {"on": True, "off": False, "1": True, "0": False}
|
|
154
|
+
if isinstance(value, str) and value in possible_values:
|
|
155
|
+
return possible_values[value]
|
|
156
|
+
raise ValueError(
|
|
157
|
+
f"{name} must be a bool, 0/1 or one of {', '.join(possible_values.keys())}, not « {value} » ({type(value)})"
|
|
158
|
+
)
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "duckenv"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
authors = [
|
|
5
|
+
{ name="Renaud Canarduck", email="renaud@canarduck.com" },
|
|
6
|
+
]
|
|
7
|
+
description = "A minimalist .env loader"
|
|
8
|
+
readme = "README.md"
|
|
9
|
+
requires-python = ">=3.11"
|
|
10
|
+
classifiers = [
|
|
11
|
+
"Programming Language :: Python :: 3",
|
|
12
|
+
"Operating System :: OS Independent",
|
|
13
|
+
]
|
|
14
|
+
license = "MIT"
|
|
15
|
+
|
|
16
|
+
[project.urls]
|
|
17
|
+
Homepage = "https://codeberg.org/canarduck/duckenv"
|
|
18
|
+
Issues = "https://codeberg.org/canarduck/duckenv/issues"
|
|
19
|
+
|
|
20
|
+
[project.optional-dependencies]
|
|
21
|
+
tests = ["coverage"]
|
|
22
|
+
dev = [
|
|
23
|
+
"black",
|
|
24
|
+
"mypy",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
[build-system]
|
|
28
|
+
requires = ["hatchling >= 1.26"]
|
|
29
|
+
build-backend = "hatchling.build"
|
|
30
|
+
|
|
31
|
+
[tool.coverage.report]
|
|
32
|
+
fail_under = 90
|
|
33
|
+
|
|
34
|
+
[tool.coverage.run]
|
|
35
|
+
branch = true
|
|
36
|
+
source = ["."]
|
duckenv-0.1.0/tests.py
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import tempfile
|
|
3
|
+
import unittest
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from duckenv import Env, DEFAULT
|
|
7
|
+
|
|
8
|
+
test_env = """\
|
|
9
|
+
NAME=Jean-Michel
|
|
10
|
+
TTL=30
|
|
11
|
+
DJANGO_DEBUG=on
|
|
12
|
+
THIEVES=Margaret,Brittany, Prudence
|
|
13
|
+
EMPTY=
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class TestEnv(unittest.TestCase):
|
|
18
|
+
def setUp(self):
|
|
19
|
+
self.temp_env_file = Path(tempfile.mktemp())
|
|
20
|
+
self.temp_env_file.write_text(test_env)
|
|
21
|
+
os.environ.clear()
|
|
22
|
+
self.env = Env(self.temp_env_file)
|
|
23
|
+
|
|
24
|
+
def tearDown(self):
|
|
25
|
+
self.temp_env_file.unlink()
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class TestInit(TestEnv):
|
|
29
|
+
def test_env_file_loaded(self):
|
|
30
|
+
self.assertEqual(os.environ["NAME"], "Jean-Michel")
|
|
31
|
+
self.assertEqual(os.environ["TTL"], "30")
|
|
32
|
+
|
|
33
|
+
def test_file_does_not_exist(self):
|
|
34
|
+
with self.assertRaisesRegex(FileNotFoundError, ".env does not exist"):
|
|
35
|
+
Env() # .env does not exist by default
|
|
36
|
+
|
|
37
|
+
def test_invalid_file(self):
|
|
38
|
+
self.temp_env_file.write_text("\n".join(["OK=ok", "KO≃KO"]))
|
|
39
|
+
with self.assertRaisesRegex(ValueError, "Unable to parse line 2"):
|
|
40
|
+
Env(self.temp_env_file)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class TestGetAttr(TestEnv):
|
|
44
|
+
def test_not_implemented(self):
|
|
45
|
+
with self.assertRaises(NotImplementedError):
|
|
46
|
+
self.env.dict("YO")
|
|
47
|
+
|
|
48
|
+
def test_handle_dunders(self):
|
|
49
|
+
self.assertIn("duckenv.Env", self.env.__str__())
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class TestGet(TestEnv):
|
|
53
|
+
def test_required_var_exists(self):
|
|
54
|
+
self.assertEqual(self.env._get("NAME", DEFAULT), "Jean-Michel")
|
|
55
|
+
|
|
56
|
+
def test_required_var_missing(self):
|
|
57
|
+
with self.assertRaisesRegex(KeyError, "Env var UNKNOWN is required"):
|
|
58
|
+
self.env._get("UNKNOWN", DEFAULT)
|
|
59
|
+
|
|
60
|
+
def test_with_default(self):
|
|
61
|
+
self.assertEqual(self.env._get("UNKNOWN", "default"), "default")
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class TestStr(TestEnv):
|
|
65
|
+
def test_existing_var(self):
|
|
66
|
+
self.assertEqual(self.env.str("NAME"), "Jean-Michel")
|
|
67
|
+
|
|
68
|
+
def test_empty_var(self):
|
|
69
|
+
self.assertEqual(self.env.str("EMPTY"), "")
|
|
70
|
+
|
|
71
|
+
def test_missing_with_default(self):
|
|
72
|
+
self.assertEqual(self.env.str("UNKNOWN", "default"), "default")
|
|
73
|
+
self.assertEqual(self.env.str("UNKNOWN", None), None)
|
|
74
|
+
|
|
75
|
+
def test_missing(self):
|
|
76
|
+
with self.assertRaisesRegex(KeyError, "Env var UNKNOWN is required"):
|
|
77
|
+
self.env.str("UNKNOWN")
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class TestList(TestEnv):
|
|
81
|
+
def test_from_comma_separated_string(self):
|
|
82
|
+
self.assertEqual(self.env.list("THIEVES"), ["Margaret", "Brittany", "Prudence"])
|
|
83
|
+
|
|
84
|
+
def test_from_string_no_commas(self):
|
|
85
|
+
self.assertEqual(self.env.list("NAME"), ["Jean-Michel"])
|
|
86
|
+
|
|
87
|
+
def test_from_list(self):
|
|
88
|
+
self.assertEqual(self.env.list("TEST_LIST", ["x", "y"]), ["x", "y"])
|
|
89
|
+
|
|
90
|
+
def test_invalid_type(self):
|
|
91
|
+
with self.assertRaises(ValueError):
|
|
92
|
+
self.env.list("TEST_DICT", {"a": 1})
|
|
93
|
+
|
|
94
|
+
def test_missing_with_default(self):
|
|
95
|
+
self.assertEqual(self.env.list("UNKNOWN", ["default"]), ["default"])
|
|
96
|
+
|
|
97
|
+
def test_missing(self):
|
|
98
|
+
with self.assertRaisesRegex(KeyError, "Env var UNKNOWN is required"):
|
|
99
|
+
self.env.list("UNKNOWN")
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class TestInt(TestEnv):
|
|
103
|
+
def test_conversion(self):
|
|
104
|
+
self.assertEqual(self.env.int("TTL"), 30)
|
|
105
|
+
|
|
106
|
+
def test_missing_with_default(self):
|
|
107
|
+
self.assertEqual(self.env.int("UNKNOWN", 42), 42)
|
|
108
|
+
|
|
109
|
+
def test_missing(self):
|
|
110
|
+
with self.assertRaisesRegex(KeyError, "Env var UNKNOWN is required"):
|
|
111
|
+
self.env.int("UNKNOWN")
|
|
112
|
+
|
|
113
|
+
def test_invalid_string(self):
|
|
114
|
+
os.environ["INVALID_INT"] = "not_a_number"
|
|
115
|
+
with self.assertRaisesRegex(ValueError, "INVALID_INT is not int-able"):
|
|
116
|
+
self.env.int("INVALID_INT")
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
class TestBool(TestEnv):
|
|
120
|
+
def test_conversion(self):
|
|
121
|
+
self.assertTrue(self.env.bool("DJANGO_DEBUG"))
|
|
122
|
+
|
|
123
|
+
def test_possible_values(self):
|
|
124
|
+
os.environ["ON"] = "on"
|
|
125
|
+
os.environ["OFF"] = "off"
|
|
126
|
+
self.assertTrue(self.env.bool("ON"))
|
|
127
|
+
self.assertFalse(self.env.bool("OFF"))
|
|
128
|
+
|
|
129
|
+
def test_int(self):
|
|
130
|
+
os.environ["ZERO"] = "0"
|
|
131
|
+
os.environ["ONE"] = "1"
|
|
132
|
+
self.assertTrue(self.env.bool("ONE"))
|
|
133
|
+
self.assertFalse(self.env.bool("ZERO"))
|
|
134
|
+
|
|
135
|
+
def test_bool(self):
|
|
136
|
+
self.assertTrue(self.env.bool("BOOL_TRUE", True))
|
|
137
|
+
self.assertFalse(self.env.bool("BOOL_FALSE", False))
|
|
138
|
+
self.assertTrue(self.env.bool("BOOL_TRUE", 1))
|
|
139
|
+
self.assertFalse(self.env.bool("BOOL_FALSE", 0))
|
|
140
|
+
|
|
141
|
+
def test_invalid(self):
|
|
142
|
+
os.environ["INVALID_BOOL"] = "maybe"
|
|
143
|
+
with self.assertRaisesRegex(ValueError, "INVALID_BOOL must be a bool"):
|
|
144
|
+
self.env.bool("INVALID_BOOL")
|
|
145
|
+
|
|
146
|
+
def test_missing(self):
|
|
147
|
+
with self.assertRaisesRegex(KeyError, "Env var UNKNOWN is required"):
|
|
148
|
+
self.env.bool("UNKNOWN")
|
|
149
|
+
|
|
150
|
+
def test_missing_with_default(self):
|
|
151
|
+
self.assertIsNone(self.env.bool("UNKNOWN", None))
|
|
152
|
+
self.assertTrue(self.env.bool("UNKNOWN", True))
|