Skip to content

Commit 233c493

Browse files
authored
Support all Unix shell-style wildcards for pass_env (#2145)
Signed-off-by: Bernát Gábor <[email protected]>
1 parent aaa749f commit 233c493

File tree

5 files changed

+44
-18
lines changed

5 files changed

+44
-18
lines changed

docs/changelog/2121.feature.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Allow any Unix shell-style wildcards expression for :ref:`pass_env` - by :user:`gaborbernat`.

docs/config.rst

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -242,11 +242,10 @@ Base
242242
:keys: pass_env, passenv
243243
:default: <empty list>
244244

245-
Environment variables to pass on to the tox environment. You can use the ``*`` to express wildcard expressions, e.g.
246-
``PIP_*`` translates to all environment variables that start with the ``PIP_`` characters. If a specified
247-
environment variable doesn't exist in the tox invocation environment it is ignored. The list of environment variable
248-
names is not case sensitive, and all variables that match when upper cased will be passed. For example, passing ``A``
249-
will pass both ``A`` and ``a``.
245+
Environment variables to pass on to the tox environment. The values are evaluated as UNIX shell-style wildcards, see
246+
`fnmatch <https://docs.python.org/3/library/fnmatch.html>`_ If a specified environment variable doesn't exist in the
247+
tox invocation environment it is ignored. The list of environment variable names is not case sensitive, for example:
248+
passing ``A`` or ``a`` will pass through both ``A`` and ``a``.
250249

251250
.. conf::
252251
:keys: set_env, setenv

src/tox/tox_env/api.py

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""
22
Defines the abstract base traits of a tox environment.
33
"""
4+
import fnmatch
45
import logging
56
import os
67
import re
@@ -278,17 +279,8 @@ def _clean(self, force: bool = False) -> None: # noqa: U100
278279
def _environment_variables(self) -> Dict[str, str]:
279280
if self._env_vars is not None:
280281
return self._env_vars
281-
result: Dict[str, str] = {}
282282
pass_env: List[str] = self.conf["pass_env"]
283-
glob_pass_env = [re.compile(e.replace("*", ".*")) for e in pass_env if "*" in e]
284-
literal_pass_env = [e for e in pass_env if "*" not in e]
285-
for env in literal_pass_env:
286-
if env in os.environ: # pragma: no branch
287-
result[env] = os.environ[env]
288-
if glob_pass_env: # pragma: no branch
289-
for env, value in os.environ.items():
290-
if any(g.match(env) is not None for g in glob_pass_env):
291-
result[env] = value
283+
result = self._load_pass_env(pass_env)
292284
set_env: SetEnv = self.conf["set_env"]
293285
# load/paths_env might trigger a load of the environment variables, set result here, returns current state
294286
self._env_vars = result
@@ -298,6 +290,15 @@ def _environment_variables(self) -> Dict[str, str]:
298290
result[key] = set_env.load(key)
299291
return result
300292

293+
@staticmethod
294+
def _load_pass_env(pass_env: List[str]) -> Dict[str, str]:
295+
result: Dict[str, str] = {}
296+
patterns = [re.compile(fnmatch.translate(e), re.IGNORECASE) for e in pass_env]
297+
for env, value in os.environ.items():
298+
if any(p.match(env) for p in patterns):
299+
result[env] = value
300+
return result
301+
301302
@property
302303
def _paths(self) -> List[Path]:
303304
return self._paths_private

tests/tox_env/test_tox_env_api.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
from pathlib import Path
22
from textwrap import dedent
3+
from unittest.mock import patch
4+
5+
import pytest
36

47
from tox.pytest import ToxProjectCreator
8+
from tox.tox_env.api import ToxEnv
59

610

711
def test_recreate(tox_project: ToxProjectCreator) -> None:
@@ -60,3 +64,23 @@ def test_env_log(tox_project: ToxProjectCreator) -> None:
6064
result_second.assert_success()
6165
filename = {i.name for i in log_dir.iterdir()}
6266
assert filename == {"1-commands[0].log"}
67+
68+
69+
def test_tox_env_pass_env_literal_exist() -> None:
70+
with patch("os.environ", {"A": "1"}):
71+
env = ToxEnv._load_pass_env(["A"])
72+
assert env == {"A": "1"}
73+
74+
75+
def test_tox_env_pass_env_literal_miss() -> None:
76+
with patch("os.environ", {}):
77+
env = ToxEnv._load_pass_env(["A"])
78+
assert not env
79+
80+
81+
@pytest.mark.parametrize("glob", ["*", "?"])
82+
@pytest.mark.parametrize("char", ["a", "A"])
83+
def test_tox_env_pass_env_match_ignore_case(char: str, glob: str) -> None:
84+
with patch("os.environ", {"A1": "1", "a2": "2", "A2": "3", "B": "4"}):
85+
env = ToxEnv._load_pass_env([f"{char}{glob}"])
86+
assert env == {"A1": "1", "a2": "2", "A2": "3"}

whitelist.txt

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ dedent
4444
deinit
4545
delenv
4646
dep
47-
deps
47+
Deps
4848
desc
4949
dest
5050
devenv
@@ -100,6 +100,7 @@ hookspecs
100100
htmlhelp
101101
ident
102102
IGN
103+
IGNORECASE
103104
impl
104105
INET
105106
insort
@@ -170,7 +171,7 @@ refspec
170171
reftitle
171172
releaselevel
172173
replacer
173-
repo
174+
Repo
174175
req
175176
reqs
176177
retann
@@ -205,7 +206,7 @@ testenv
205206
textwrap
206207
tmp
207208
tmpdir
208-
toml
209+
Toml
209210
tomli
210211
towncrier
211212
tox

0 commit comments

Comments
 (0)