Skip to content

Commit

Permalink
Add the include_outer_env parameter to the session.run functions that…
Browse files Browse the repository at this point in the history
… enables a more granular control of the environment variables passing
  • Loading branch information
franekmagiera committed Oct 13, 2022
1 parent 867ca4f commit 6b3ea0e
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 16 deletions.
12 changes: 6 additions & 6 deletions nox/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,16 +52,16 @@ def which(program: str, paths: list[str] | None) -> str:
raise CommandFailed(f"Program {program} not found")


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

def _clean_env(env: dict[str, str] | None) -> dict[str, str]:
clean_env = {}

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

if env is not None:
clean_env.update(env)

clean_env.update(env)
return clean_env


Expand Down
47 changes: 38 additions & 9 deletions nox/sessions.py
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,11 @@ def _run_func(
raise nox.command.CommandFailed() from e

def run(
self, *args: str, env: Mapping[str, str] | None = None, **kwargs: Any
self,
*args: str,
env: dict[str, str] | None = None,
include_outer_env: bool = True,
**kwargs: Any,
) -> Any | None:
"""Run a command.
Expand Down Expand Up @@ -339,6 +343,10 @@ def run(
:param env: A dictionary of environment variables to expose to the
command. By default, all environment variables are passed.
:type env: dict or None
:param include_outer_env: Boolean parameter that determines if the
environment variables from the nox invocation environment should
be passed to the command. ``True`` by default.
:type include_outer_env: bool
:param bool silent: Silence command output, unless the command fails.
If ``True``, returns the command output (unless the command fails).
``False`` by default.
Expand Down Expand Up @@ -375,10 +383,19 @@ def run(
logger.info(f"Skipping {args[0]} run, as --install-only is set.")
return None

return self._run(*args, env=env, **kwargs)
return self._run(
*args,
env=env,
include_outer_env=include_outer_env,
**kwargs,
)

def run_always(
self, *args: str, env: Mapping[str, str] | None = None, **kwargs: Any
self,
*args: str,
env: dict[str, str] | None = None,
include_outer_env: bool = True,
**kwargs: Any,
) -> Any | None:
"""Run a command **always**.
Expand All @@ -396,6 +413,10 @@ def run_always(
:param env: A dictionary of environment variables to expose to the
command. By default, all environment variables are passed.
:type env: dict or None
:param include_outer_env: Boolean parameter that determines if the
environment variables from the nox invocation environment should
be passed to the command. ``True`` by default.
:type include_outer_env: bool
:param bool silent: Silence command output, unless the command fails.
``False`` by default.
:param success_codes: A list of return codes that are considered
Expand Down Expand Up @@ -428,23 +449,31 @@ def run_always(
if not args:
raise ValueError("At least one argument required to run_always().")

return self._run(*args, env=env, **kwargs)
return self._run(
*args,
env=env,
include_outer_env=include_outer_env,
**kwargs,
)

def _run(
self, *args: str, env: Mapping[str, str] | None = None, **kwargs: Any
self,
*args: str,
env: dict[str, str] | None = None,
include_outer_env: bool = True,
**kwargs: Any,
) -> Any:
"""Like run(), except that it runs even if --install-only is provided."""
# Legacy support - run a function given.
if callable(args[0]):
return self._run_func(args[0], args[1:], kwargs)

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

# If --error-on-external-run is specified, error on external programs.
if self._runner.global_config.error_on_external_run:
Expand Down
3 changes: 2 additions & 1 deletion tests/test_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ def test_run_env_unicode():
assert "123" in result


@only_on_windows
def test_run_env_systemroot():
systemroot = os.environ.setdefault("SYSTEMROOT", "sigil")

Expand Down Expand Up @@ -181,7 +182,7 @@ def test_run_path_existent(tmp_path: Path):
with mock.patch("nox.command.popen") as mock_command:
mock_command.return_value = (0, "")
nox.command.run([executable_name], silent=True, paths=[str(tmp_path)])
mock_command.assert_called_with([str(executable)], env=None, silent=True)
mock_command.assert_called_with([str(executable)], env={}, silent=True)


def test_run_external_warns(tmpdir, caplog):
Expand Down
31 changes: 31 additions & 0 deletions tests/test_sessions.py
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,37 @@ def test_run_overly_env(self):
)
assert result.strip() == "1 3"

def test_by_default_all_invocation_env_vars_are_passed(self):
session, runner = self.make_session_and_runner()
runner.venv.env["I_SHOULD_BE_INCLUDED"] = "happy"
runner.venv.env["I_SHOULD_BE_INCLUDED_TOO"] = "happier"
runner.venv.env["EVERYONE_SHOULD_BE_INCLUDED_TOO"] = "happiest"
result = session.run(
sys.executable,
"-c",
"import os; print(os.environ)",
silent=True,
)
assert "happy" in result
assert "happier" in result
assert "happiest" in result

def test_no_included_invocation_env_vars_are_passed(self):
session, runner = self.make_session_and_runner()
runner.venv.env["I_SHOULD_NOT_BE_INCLUDED"] = "sad"
runner.venv.env["AND_NEITHER_SHOULD_I"] = "unhappy"
result = session.run(
sys.executable,
"-c",
"import os; print(os.environ)",
env={"I_SHOULD_BE_INCLUDED": "happy"},
include_outer_env=False,
silent=True,
)
assert "sad" not in result
assert "unhappy" not in result
assert "happy" in result

def test_run_external_not_a_virtualenv(self):
# Non-virtualenv sessions should always allow external programs.
session, runner = self.make_session_and_runner()
Expand Down

0 comments on commit 6b3ea0e

Please sign in to comment.