Skip to content

Commit 8d18bad

Browse files
committed
Make dotenv run forward flags to given command
Changes for users: - (BREAKING) Forward flags passed after `dotenv run` to the given command instead of interpreting them. - This means that an invocation such as `dotenv run ls --help` will show the help page of `ls` instead of that of `dotenv run`. - To pass flags to `dotenv run` itself, pass them right after `run`: `dotenv run --help` or `dotenv run --override ls`. - As usual, generic options should be passed right after `dotenv`: `dotenv --file path/to/env run ls`
1 parent f54d29f commit 8d18bad

File tree

2 files changed

+53
-4
lines changed

2 files changed

+53
-4
lines changed

src/dotenv/cli.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -156,15 +156,21 @@ def unset(ctx: click.Context, key: Any) -> None:
156156
sys.exit(1)
157157

158158

159-
@cli.command(context_settings={"ignore_unknown_options": True})
159+
@cli.command(
160+
context_settings={
161+
"allow_extra_args": True,
162+
"allow_interspersed_args": False,
163+
"ignore_unknown_options": True,
164+
}
165+
)
160166
@click.pass_context
161167
@click.option(
162168
"--override/--no-override",
163169
default=True,
164170
help="Override variables from the environment file with those from the .env file.",
165171
)
166172
@click.argument("commandline", nargs=-1, type=click.UNPROCESSED)
167-
def run(ctx: click.Context, override: bool, commandline: List[str]) -> None:
173+
def run(ctx: click.Context, override: bool, commandline: tuple[str, ...]) -> None:
168174
"""Run command with environment variables present."""
169175
file = ctx.obj["FILE"]
170176
if not os.path.isfile(file):
@@ -180,7 +186,8 @@ def run(ctx: click.Context, override: bool, commandline: List[str]) -> None:
180186
if not commandline:
181187
click.echo("No command given.")
182188
sys.exit(1)
183-
run_command(commandline, dotenv_as_dict)
189+
190+
run_command([*commandline, *ctx.args], dotenv_as_dict)
184191

185192

186193
def run_command(command: List[str], env: Dict[str, str]) -> None:

tests/test_cli.py

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import os
2+
import subprocess
23
from pathlib import Path
3-
from typing import Optional
4+
from typing import Optional, Sequence
45

56
import pytest
67
import sh
@@ -10,6 +11,21 @@
1011
from dotenv.version import __version__
1112

1213

14+
def invoke_sub(args: Sequence[str]) -> subprocess.CompletedProcess:
15+
"""
16+
Invoke the `dotenv` CLI in a subprocess.
17+
18+
This is necessary to test subcommands like `dotenv run` that replace the
19+
current process.
20+
"""
21+
22+
return subprocess.run(
23+
["dotenv", *args],
24+
capture_output=True,
25+
text=True,
26+
)
27+
28+
1329
@pytest.mark.parametrize(
1430
"output_format,content,expected",
1531
(
@@ -249,3 +265,29 @@ def test_run_with_version(cli):
249265

250266
assert result.exit_code == 0
251267
assert result.output.strip().endswith(__version__)
268+
269+
270+
def test_run_with_command_flags(dotenv_path):
271+
"""
272+
Check that command flags passed after `dotenv run` are not interpreted.
273+
274+
Here, we want to run `printenv --version`, not `dotenv --version`.
275+
"""
276+
277+
result = invoke_sub(["--file", dotenv_path, "run", "printenv", "--version"])
278+
279+
assert result.returncode == 0
280+
assert result.stdout.strip().startswith("printenv ")
281+
282+
283+
def test_run_with_dotenv_and_command_flags(cli, dotenv_path):
284+
"""
285+
Check that dotenv flags supersede command flags.
286+
"""
287+
288+
result = invoke_sub(
289+
["--version", "--file", dotenv_path, "run", "printenv", "--version"]
290+
)
291+
292+
assert result.returncode == 0
293+
assert result.stdout.strip().startswith("dotenv, version")

0 commit comments

Comments
 (0)