Skip to content

Commit

Permalink
feat: Add an override option to the env_file script option (#1299)
Browse files Browse the repository at this point in the history
This allows the user to specify that the env_file should override any
existing environment variables. This is not the default as the
environment the code runs it should take precedence.
  • Loading branch information
pgjones authored Aug 4, 2022
1 parent d5c88a1 commit 950885f
Show file tree
Hide file tree
Showing 4 changed files with 43 additions and 12 deletions.
9 changes: 9 additions & 0 deletions docs/docs/usage/scripts.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,15 @@ start.cmd = "flask run -p 54321"
start.env_file = ".env"
```

The variables within the dotenv file will *not* override any existing environment variables.
If you want the dotenv file to override existing environment variables use the following:

```toml
[tool.pdm.scripts]
start.cmd = "flask run -p 54321"
start.env_file.override = ".env"
```

!!! note
A dotenv file specified on a composite task level will override those defined by called tasks.

Expand Down
4 changes: 4 additions & 0 deletions news/1299.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Add a env_file.override option that allows the user to specify that
the env_file should override any existing environment variables. This
is not the default as the environment the code runs it should take
precedence.
28 changes: 20 additions & 8 deletions pdm/cli/commands/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,13 @@
from pdm.utils import is_path_relative_to


class EnvFileOptions(TypedDict, total=True):
override: str


class TaskOptions(TypedDict, total=False):
env: Mapping[str, str]
env_file: str | None
env_file: EnvFileOptions | str | None
help: str
site_packages: bool

Expand Down Expand Up @@ -119,23 +123,31 @@ def _run_process(
shell: bool = False,
site_packages: bool = False,
env: Mapping[str, str] | None = None,
env_file: str | None = None,
env_file: EnvFileOptions | str | None = None,
) -> int:
"""Run command in a subprocess and return the exit code."""
project = self.project
process_env = {}
if env_file:
process_env = os.environ.copy()
if env_file is not None:
if isinstance(env_file, str):
path = env_file
override = False
else:
path = env_file["override"]
override = True

import dotenv

project.core.ui.echo(
f"Loading .env file: [green]{env_file}[/]",
err=True,
verbosity=termui.Verbosity.DETAIL,
)
process_env = dotenv.dotenv_values(
project.root / env_file, encoding="utf-8"
)
process_env.update(os.environ)
dotenv_env = dotenv.dotenv_values(project.root / path, encoding="utf-8")
if override:
process_env = {**process_env, **dotenv_env}
else:
process_env = {**dotenv_env, **process_env}
pythonpath = process_env.get("PYTHONPATH", "").split(os.pathsep)
pythonpath = [PEP582_PATH] + [
p for p in pythonpath if "pdm/pep582" not in p.replace("\\", "/")
Expand Down
14 changes: 10 additions & 4 deletions tests/cli/test_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -230,20 +230,26 @@ def test_run_script_with_env_defined(project, invoke, capfd):
assert capfd.readouterr()[0].strip() == "bar"


def test_run_script_with_dotenv_file(project, invoke, capfd):
def test_run_script_with_dotenv_file(project, invoke, capfd, monkeypatch):
(project.root / "test_script.py").write_text(
"import os; print(os.getenv('FOO'), os.getenv('BAR'))"
)
project.tool_settings["scripts"] = {
"_": {"env": {"BAR": "foo"}},
"test_script": {"cmd": "python test_script.py", "env_file": ".env"},
"test_override": {
"cmd": "python test_script.py",
"env_file": {"override": ".env"},
},
"test_default": {"cmd": "python test_script.py", "env_file": ".env"},
}
project.write_pyproject()
monkeypatch.setenv("BAR", "foo")
(project.root / ".env").write_text("FOO=bar\nBAR=override")
capfd.readouterr()
with cd(project.root):
invoke(["run", "test_script"], obj=project)
invoke(["run", "test_default"], obj=project)
assert capfd.readouterr()[0].strip() == "bar foo"
invoke(["run", "test_override"], obj=project)
assert capfd.readouterr()[0].strip() == "bar override"


def test_run_script_override_global_env(project, invoke, capfd):
Expand Down

0 comments on commit 950885f

Please sign in to comment.