Skip to content

Commit 17f5568

Browse files
authored
✨ feat(plugin): add U flag to unset env variables (#188)
1 parent 79af961 commit 17f5568

File tree

4 files changed

+69
-10
lines changed

4 files changed

+69
-10
lines changed

README.md

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ HOME = "~/tmp"
3131
RUN_ENV = 1
3232
TRANSFORMED = { value = "{USER}/alpha", transform = true }
3333
SKIP_IF_SET = { value = "on", skip_if_set = true }
34+
DATABASE_URL = { unset = true }
3435
```
3536

3637
In `pytest.toml` (or `.pytest.toml`):
@@ -41,13 +42,14 @@ HOME = "~/tmp"
4142
RUN_ENV = 1
4243
TRANSFORMED = { value = "{USER}/alpha", transform = true }
4344
SKIP_IF_SET = { value = "on", skip_if_set = true }
45+
DATABASE_URL = { unset = true }
4446
```
4547

4648
The `tool.pytest_env` (`pytest_env` in `pytest.toml` and `.pytest.toml`) tables keys are the environment variables keys
4749
to set. The right hand side of the assignment:
4850

49-
- if an inline table you can set options via the `transform` or `skip_if_set` keys, while the `value` key holds the
50-
value to set (or transform before setting). For transformation the variables you can use is other environment
51+
- if an inline table you can set options via the `transform`, `skip_if_set` or `unset` keys, while the `value` key holds
52+
the value to set (or transform before setting). For transformation the variables you can use is other environment
5153
variable,
5254
- otherwise the value to set for the environment variable to set (casted to a string).
5355

@@ -122,3 +124,14 @@ env =
122124
R:RUN_PATH=/run/path/{USER}
123125
R:D:RUN_PATH_IF_NOT_SET=/run/path/{USER}
124126
```
127+
128+
### Unsetting variables
129+
130+
You can use `U:` (unset) as prefix to remove an environment variable. This differs from setting a variable to an empty
131+
string — the variable will be completely removed from `os.environ`:
132+
133+
```ini
134+
[pytest]
135+
env =
136+
U:DATABASE_URL
137+
```

src/pytest_env/plugin.py

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ class Entry:
3333
value: str
3434
transform: bool
3535
skip_if_set: bool
36+
unset: bool = False
3637

3738

3839
@pytest.hookimpl(tryfirst=True)
@@ -43,20 +44,24 @@ def pytest_load_initial_conftests(
4344
) -> None:
4445
"""Load environment variables from configuration files."""
4546
for entry in _load_values(early_config):
46-
if entry.skip_if_set and entry.key in os.environ:
47+
if entry.unset:
48+
os.environ.pop(entry.key, None)
49+
elif entry.skip_if_set and entry.key in os.environ:
4750
continue
48-
# transformation -> replace environment variables, e.g. TEST_DIR={USER}/repo_test_dir.
49-
os.environ[entry.key] = entry.value.format(**os.environ) if entry.transform else entry.value
51+
else:
52+
# transformation -> replace environment variables, e.g. TEST_DIR={USER}/repo_test_dir.
53+
os.environ[entry.key] = entry.value.format(**os.environ) if entry.transform else entry.value
5054

5155

5256
def _parse_toml_config(config: dict[str, Any]) -> Generator[Entry, None, None]:
5357
for key, entry in config.items():
5458
if isinstance(entry, dict):
55-
value = str(entry["value"])
59+
unset = bool(entry.get("unset"))
60+
value = str(entry.get("value", "")) if not unset else ""
5661
transform, skip_if_set = bool(entry.get("transform")), bool(entry.get("skip_if_set"))
5762
else:
58-
value, transform, skip_if_set = str(entry), False, False
59-
yield Entry(key, value, transform, skip_if_set)
63+
value, transform, skip_if_set, unset = str(entry), False, False, False
64+
yield Entry(key, value, transform, skip_if_set, unset=unset)
6065

6166

6267
def _load_values(early_config: pytest.Config) -> Iterator[Entry]:
@@ -91,6 +96,8 @@ def _load_values(early_config: pytest.Config) -> Iterator[Entry]:
9196
transform = "R" not in flags
9297
# D: is a way to mark the value to be set only if it does not exist yet
9398
skip_if_set = "D" in flags
99+
# U: is a way to unset (remove) an environment variable
100+
unset = "U" in flags
94101
key = ini_key_parts[-1].strip()
95102
value = parts[2].strip()
96-
yield Entry(key, value, transform, skip_if_set)
103+
yield Entry(key, value, transform, skip_if_set, unset=unset)

tests/template.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,7 @@
66

77
def test_env() -> None:
88
for key, value in ast.literal_eval(os.environ["_TEST_ENV"]).items():
9-
assert os.environ[key] == value, key
9+
if value is None:
10+
assert key not in os.environ, f"{key} should be unset"
11+
else:
12+
assert os.environ[key] == value, key

tests/test_env.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,24 @@
9090
{"MAGIC": "zero"},
9191
id="empty ini works",
9292
),
93+
pytest.param(
94+
{"MAGIC": "alpha"},
95+
"[pytest]\nenv = U:MAGIC",
96+
{"MAGIC": None},
97+
id="U flag - unset existing var",
98+
),
99+
pytest.param(
100+
{},
101+
"[pytest]\nenv = U:MAGIC",
102+
{"MAGIC": None},
103+
id="U flag - unset non-existing var",
104+
),
105+
pytest.param(
106+
{"MAGIC": "alpha"},
107+
"[pytest]\nenv = U:MAGIC\n MAGIC=beta",
108+
{"MAGIC": "beta"},
109+
id="U flag then set - var is set",
110+
),
93111
],
94112
)
95113
def test_env_via_pytest(
@@ -229,6 +247,24 @@ def test_env_via_pytest(
229247
"pytest.toml",
230248
id="pytest toml over pyproject toml",
231249
),
250+
pytest.param(
251+
{"MAGIC": "alpha"},
252+
"[tool.pytest_env]\nMAGIC = {unset = true}",
253+
"",
254+
"",
255+
{"MAGIC": None},
256+
None,
257+
id="pyproject toml unset",
258+
),
259+
pytest.param(
260+
{},
261+
"[tool.pytest_env]\nMAGIC = {unset = true}",
262+
"",
263+
"",
264+
{"MAGIC": None},
265+
None,
266+
id="pyproject toml unset non-existing",
267+
),
232268
],
233269
)
234270
def test_env_via_toml( # noqa: PLR0913, PLR0917

0 commit comments

Comments
 (0)