Verified Commit ac42d90a authored by David Runge's avatar David Runge
Browse files

Add commands module to run external commands

Add `run_command()` which allows for running an external command with
the help of subprocess-tee.
Add the private method `_print_env()` which is used to print the
optional environment variables given to `run_command()`.

Add tests for `_print_env()` and `run_command()`.
parent f588beec
from pathlib import Path
from subprocess import PIPE, STDOUT, CalledProcessError
from typing import Dict, List, Optional, Union
from subprocess_tee import CompletedProcess, run
def _print_env(env: Optional[Dict[str, str]]) -> None:
"""Print the environment variables from a dict
env: Optional[Dict[str, str]]
An optional dict with environment variables and their values
if env:
for (key, value) in sorted(env.items()):
print(f"{key}: {value}")
def run_command(
cmd: Union[str, List[str]],
env: Optional[Dict[str, str]] = None,
debug: bool = False,
echo: bool = False,
quiet: bool = False,
check: bool = False,
cwd: Union[Optional[str], Optional[Path]] = None,
) -> CompletedProcess:
"""Run a command
cmd: Union[str, List[str]]
A string or list of strings that will be passed to
env: Optional[Dict[str, str]]
A dict of environment variables and their respective values (defaults to None)
debug: bool
Whether to run in debug mode, which prints environment variables and command output (defaults to False)
echo: bool
Whether to print the command before running it (defaults to False)
quiet: bool
Whether to print the output of command while running it (defaults to False)
check: bool
Whether to check the return code of the command, which implies raising CallecProcessError (defaults to False)
cwd: Union[Optional[str], Optional[Path]]
In which directory to run the command (defaults to None, which means current working directory)
If check is True and the commands return code is not 0
The result of the command
if debug:
result = run(
echo=echo or debug,
if result.returncode != 0 and check:
raise CalledProcessError(
return result
from contextlib import nullcontext as does_not_raise
from pathlib import Path
from subprocess import CalledProcessError
from typing import ContextManager, Dict, List, Optional, Union
from pytest import mark, raises
from repo_management import commands
@mark.parametrize("env", [(None), ({"FOO": "BAR"})])
def test__print_env(env: Optional[Dict[str, str]]) -> None:
"cmd, env, debug, echo, quiet, check, cwd, expectation",
(["ls", "-lah"], {"FOO": "BAR"}, False, False, False, False, None, does_not_raise()),
(["ls", "-lah"], {"FOO": "BAR"}, True, False, False, False, None, does_not_raise()),
(["cd", "-f"], {"FOO": "BAR"}, True, False, False, True, None, raises(CalledProcessError)),
def test_run_command(
cmd: Union[str, List[str]],
env: Optional[Dict[str, str]],
debug: bool,
echo: bool,
quiet: bool,
check: bool,
cwd: Union[Optional[str], Optional[Path]],
expectation: ContextManager[str],
) -> None:
with expectation:
commands.run_command(cmd=cmd, env=env, debug=debug, echo=echo, quiet=quiet, check=check, cwd=cwd)
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment