-
Notifications
You must be signed in to change notification settings - Fork 430
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 reading/loading multiple .env files #424
Changes from all commits
71f1730
5f7d1f8
6718b16
b4f335b
0660ded
d46b1e6
bc474ca
2b3a28b
f730c6d
d6c1385
630c0bd
c585a7b
9b1b4af
a2d9365
f5ed3e9
49c34a5
437a921
a10e462
186dc81
dde1654
ec23819
febc153
c52dd9e
9e3df30
6090d64
d61c1ed
46f1b75
363aab5
1d33b6a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,7 @@ | ||
import os | ||
import sh | ||
from pathlib import Path | ||
from typing import Optional | ||
from typing import Dict, List, Optional | ||
|
||
import pytest | ||
|
||
|
@@ -25,7 +25,7 @@ | |
("export", "x='a b c'", '''export x='a b c'\n'''), | ||
) | ||
) | ||
def test_list(cli, dotenv_path, format: Optional[str], content: str, expected: str): | ||
def test_list_single_file(cli, dotenv_path, format: Optional[str], content: str, expected: str): | ||
dotenv_path.write_text(content + '\n') | ||
|
||
args = ['--file', dotenv_path, 'list'] | ||
|
@@ -37,6 +37,25 @@ def test_list(cli, dotenv_path, format: Optional[str], content: str, expected: s | |
assert (result.exit_code, result.output) == (0, expected) | ||
|
||
|
||
@pytest.mark.parametrize( | ||
"contents,expected", | ||
( | ||
(["x='1'", "y='2'"], '''x=1\ny=2\n'''), | ||
(["x='1'", "x='2'"], '''x=2\n'''), | ||
(["x='1'\ny='2'", "y='20'\nz='30'"], '''x=1\ny=20\nz=30\n'''), | ||
) | ||
) | ||
def test_list_multi_file(cli, dotenv_path, extra_dotenv_path, contents: List[str], expected: str): | ||
dotenv_path.write_text(contents[0] + '\n') | ||
extra_dotenv_path.write_text(contents[1] + '\n') | ||
|
||
args = ['--file', dotenv_path, '--file', extra_dotenv_path, 'list'] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I omitted tests with multiple formatting because that seems to me like a separate concern - if the variables are loaded correctly, the formatting behavior shouldn't change. Do you agree? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You are right. This is fine. |
||
|
||
result = cli.invoke(dotenv_cli, args) | ||
|
||
assert (result.exit_code, result.output) == (0, expected) | ||
|
||
|
||
def test_list_non_existent_file(cli): | ||
result = cli.invoke(dotenv_cli, ['--file', 'nx_file', 'list']) | ||
|
||
|
@@ -57,17 +76,40 @@ def test_list_no_file(cli): | |
assert (result.exit_code, result.output) == (1, "") | ||
|
||
|
||
def test_get_existing_value(cli, dotenv_path): | ||
def test_get_existing_value_single_file(cli, dotenv_path): | ||
dotenv_path.write_text("a=b") | ||
|
||
result = cli.invoke(dotenv_cli, ['--file', dotenv_path, 'get', 'a']) | ||
|
||
assert (result.exit_code, result.output) == (0, "b\n") | ||
|
||
|
||
@pytest.mark.parametrize( | ||
"contents,expected_values", | ||
( | ||
(["a=1", "b=2"], {"a": "1", "b": "2"}), | ||
(["b=2", "a=1"], {"a": "1", "b": "2"}), | ||
(["a=1", "a=2"], {"a": "2"}), | ||
) | ||
) | ||
def test_get_existing_value_multi_file( | ||
cli, | ||
dotenv_path, | ||
extra_dotenv_path, | ||
contents: List[str], | ||
expected_values: Dict[str, str] | ||
): | ||
dotenv_path.write_text(contents[0]) | ||
extra_dotenv_path.write_text(contents[1]) | ||
|
||
for key, value in expected_values.items(): | ||
result = cli.invoke(dotenv_cli, ['--file', dotenv_path, '--file', extra_dotenv_path, 'get', key]) | ||
|
||
assert (result.exit_code, result.output) == (0, f"{value}\n") | ||
|
||
|
||
def test_get_non_existent_value(cli, dotenv_path): | ||
result = cli.invoke(dotenv_cli, ['--file', dotenv_path, 'get', 'a']) | ||
|
||
assert (result.exit_code, result.output) == (1, "") | ||
|
||
|
||
|
@@ -101,6 +143,12 @@ def test_unset_non_existent_value(cli, dotenv_path): | |
assert dotenv_path.read_text() == "" | ||
|
||
|
||
def test_unset_multi_file_not_allowed(cli, dotenv_path, extra_dotenv_path): | ||
result = cli.invoke(dotenv_cli, ['--file', dotenv_path, '--file', extra_dotenv_path, 'unset', 'a']) | ||
assert result.exit_code == 1 | ||
assert result.output == f"Unset is not supported for multiple files: ['{dotenv_path}', '{extra_dotenv_path}'].\n" | ||
|
||
|
||
@pytest.mark.parametrize( | ||
"quote_mode,variable,value,expected", | ||
( | ||
|
@@ -151,6 +199,12 @@ def test_set_no_file(cli): | |
assert "Missing argument" in result.output | ||
|
||
|
||
def test_set_multi_file_not_allowed(cli, dotenv_path, extra_dotenv_path): | ||
result = cli.invoke(dotenv_cli, ['--file', dotenv_path, '--file', extra_dotenv_path, 'set', 'a', 'b']) | ||
assert result.exit_code == 1 | ||
assert result.output == f"Set is not supported for multiple files: ['{dotenv_path}', '{extra_dotenv_path}'].\n" | ||
|
||
|
||
def test_get_default_path(tmp_path): | ||
with sh.pushd(tmp_path): | ||
(tmp_path / ".env").write_text("a=b") | ||
|
@@ -208,6 +262,24 @@ def test_run_with_other_env(dotenv_path): | |
assert result == "b\n" | ||
|
||
|
||
@pytest.mark.parametrize( | ||
"contents,expected_values", | ||
( | ||
(["a=1", "b=2"], {"a": "1", "b": "2"}), | ||
(["b=2", "a=1"], {"a": "1", "b": "2"}), | ||
(["a=1", "a=2"], {"a": "2"}), | ||
) | ||
) | ||
def test_run_with_multi_envs(dotenv_path, extra_dotenv_path, contents: List[str], expected_values: Dict[str, str]): | ||
dotenv_path.write_text(contents[0]) | ||
extra_dotenv_path.write_text(contents[1]) | ||
|
||
for key, value in expected_values.items(): | ||
result = sh.dotenv("--file", dotenv_path, '--file', extra_dotenv_path, "run", "printenv", key) | ||
|
||
assert result == f"{value}\n" | ||
|
||
|
||
def test_run_without_cmd(cli): | ||
result = cli.invoke(dotenv_cli, ['run']) | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@duarte-pompeu Consider document the precedence/loading order values from the of the files. I didn't see a test but based on the this example it seems the
.env2
overrides the previous value from.env1
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree: it's important to document this. I chose an overriding behavior, as my use case would be:
I tried to improve it on 363aab5