Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support None to remove envvars #812

Merged
merged 1 commit into from
Apr 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 4 additions & 6 deletions nox/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,17 +55,15 @@ def which(program: str | os.PathLike[str], paths: Sequence[str] | None) -> str:
raise CommandFailed(f"Program {program} not found")


def _clean_env(env: Mapping[str, str] | None = None) -> dict[str, str] | None:
def _clean_env(env: Mapping[str, str | None] | None = None) -> dict[str, str] | None:
if env is None:
return None

clean_env: dict[str, str] = {}
clean_env = {k: v for k, v in env.items() if v is not None}

# Ensure systemroot is passed down, otherwise Windows will explode.
if sys.platform == "win32":
clean_env["SYSTEMROOT"] = os.environ.get("SYSTEMROOT", "")

clean_env.update(env)
clean_env.setdefault("SYSTEMROOT", os.environ.get("SYSTEMROOT", ""))

return clean_env

Expand All @@ -77,7 +75,7 @@ def _shlex_join(args: Sequence[str | os.PathLike[str]]) -> str:
def run(
args: Sequence[str | os.PathLike[str]],
*,
env: Mapping[str, str] | None = None,
env: Mapping[str, str | None] | None = None,
silent: bool = False,
paths: Sequence[str] | None = None,
success_codes: Iterable[int] | None = None,
Expand Down
18 changes: 9 additions & 9 deletions nox/sessions.py
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,7 @@ def _run_func(
def run(
self,
*args: str | os.PathLike[str],
env: Mapping[str, str] | None = None,
env: Mapping[str, str | None] | None = None,
include_outer_env: bool = True,
**kwargs: Any,
) -> Any | None:
Expand Down Expand Up @@ -350,7 +350,9 @@ def run(
print("Current Git commit is", out.strip())

:param env: A dictionary of environment variables to expose to the
command. By default, all environment variables are passed.
command. By default, all environment variables are passed. You
can block an environment variable from the outer environment by
setting it to None.
Comment on lines +353 to +355
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Docs update here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:type env: dict or None
:param include_outer_env: Boolean parameter that determines if the
environment variables from the nox invocation environment should
Expand Down Expand Up @@ -406,7 +408,7 @@ def run(
def run_install(
self,
*args: str | os.PathLike[str],
env: Mapping[str, str] | None = None,
env: Mapping[str, str | None] | None = None,
include_outer_env: bool = True,
**kwargs: Any,
) -> Any | None:
Expand Down Expand Up @@ -474,7 +476,7 @@ def run_install(
def run_always(
self,
*args: str | os.PathLike[str],
env: Mapping[str, str] | None = None,
env: Mapping[str, str | None] | None = None,
include_outer_env: bool = True,
**kwargs: Any,
) -> Any | None:
Expand All @@ -490,7 +492,7 @@ def run_always(
def _run(
self,
*args: str | os.PathLike[str],
env: Mapping[str, str] | None = None,
env: Mapping[str, str | None] | None = None,
include_outer_env: bool = True,
**kwargs: Any,
) -> Any:
Expand All @@ -501,10 +503,8 @@ def _run(

# Combine the env argument with our virtualenv's env vars.
if include_outer_env:
overlay_env = env
env = self.env.copy()
if overlay_env is not None:
env.update(overlay_env)
overlay_env = env or {}
env = {**self.env, **overlay_env}

# If --error-on-external-run is specified, error on external programs.
if self._runner.global_config.error_on_external_run:
Expand Down
12 changes: 5 additions & 7 deletions nox/virtualenv.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,15 +78,13 @@ def __init__(
self, bin_paths: None = None, env: Mapping[str, str | None] | None = None
) -> None:
self._bin_paths = bin_paths
self.env = os.environ.copy()
self._reused = False
env = env or {}

for k, v in env.items():
if v is None:
self.env.pop(k, None)
else:
self.env[k] = v
# Filter envs now so `.env` is dict[str, str] (easier to use)
# even though .command's env supports None.
env = env or {}
env = {**os.environ, **env}
self.env = {k: v for k, v in env.items() if v is not None}

for key in _BLACKLISTED_ENV_VARS:
self.env.pop(key, None)
Expand Down
13 changes: 13 additions & 0 deletions tests/test_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,19 @@ def test_run_env_unicode():
assert "123" in result


def test_run_env_remove(monkeypatch):
monkeypatch.setenv("EMPTY", "notempty")
nox.command.run(
[PYTHON, "-c", 'import os; assert "EMPTY" in os.environ'],
silent=True,
)
nox.command.run(
[PYTHON, "-c", 'import os; assert "EMPTY" not in os.environ'],
silent=True,
env={"EMPTY": None},
)


@mock.patch("sys.platform", "win32")
def test_run_env_systemroot():
systemroot = os.environ.setdefault("SYSTEMROOT", "sigil")
Expand Down
7 changes: 4 additions & 3 deletions tests/test_sessions.py
Original file line number Diff line number Diff line change
Expand Up @@ -271,14 +271,15 @@ def test_run_overly_env(self):
session, runner = self.make_session_and_runner()
runner.venv.env["A"] = "1"
runner.venv.env["B"] = "2"
runner.venv.env["C"] = "4"
result = session.run(
sys.executable,
"-c",
'import os; print(os.environ["A"], os.environ["B"])',
env={"B": "3"},
'import os; print(os.environ["A"], os.environ["B"], os.environ.get("C", "5"))',
env={"B": "3", "C": None},
silent=True,
)
assert result.strip() == "1 3"
assert result.strip() == "1 3 5"

def test_by_default_all_invocation_env_vars_are_passed(self):
session, runner = self.make_session_and_runner()
Expand Down
Loading