Skip to content

Commit 52dccdf

Browse files
authored
Allow multiple env_file to be specified, split on os pathsep (#4993)
1 parent f359de1 commit 52dccdf

File tree

4 files changed

+97
-3
lines changed

4 files changed

+97
-3
lines changed

pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ dev = [
139139
"asynctest >=0.13.0,<1.0",
140140
"darglint >=1.8.1,<2.0",
141141
"dill >=0.3.8",
142+
"libsass >=0.23.0,<1.0",
142143
"numpy >=2.2.3,<3.0",
143144
"pandas >=2.1.1,<3.0",
144145
"pillow >=10.0.0,<12.0",
@@ -156,7 +157,7 @@ dev = [
156157
"pytest-playwright >=0.5.1",
157158
"pytest-retry >=1.7.0,<2.0",
158159
"pytest-split >=0.10.0,<1.0",
159-
"libsass >=0.23.0,<1.0",
160+
"python-dotenv >=1,<2",
160161
"ruff ==0.11.0",
161162
"selenium >=4.11.0,<5.0",
162163
"toml >=0.10.2,<1.0",

reflex/config.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -932,8 +932,14 @@ def update_from_env(self) -> dict[str, Any]:
932932
"""The `python-dotenv` package is required to load environment variables from a file. Run `pip install "python-dotenv>=1.0.1"`."""
933933
)
934934
else:
935-
# load env file if exists
936-
load_dotenv(env_file, override=True)
935+
# load env files in reverse order if they exist
936+
for env_file_path in [
937+
Path(p)
938+
for s in reversed(env_file.split(os.pathsep))
939+
if (p := s.strip())
940+
]:
941+
if env_file_path.exists():
942+
load_dotenv(env_file_path, override=True)
937943

938944
updated_values = {}
939945
# Iterate over the fields.

tests/units/test_config.py

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,3 +289,79 @@ class TestEnv:
289289
assert TestEnv.LIST.get() == [4, 5, 6]
290290
TestEnv.LIST.set(None)
291291
assert "LIST" not in os.environ
292+
293+
294+
@pytest.fixture
295+
def restore_env():
296+
"""Fixture to restore the environment variables after the test.
297+
298+
Yields:
299+
None: Placeholder for the test to run.
300+
"""
301+
original_env = os.environ.copy()
302+
yield
303+
os.environ.clear()
304+
os.environ.update(original_env)
305+
306+
307+
@pytest.mark.usefixtures("restore_env")
308+
@pytest.mark.parametrize(
309+
("file_map", "env_file", "exp_env_vars"),
310+
[
311+
(
312+
{
313+
".env": "APP_NAME=my_test_app\nFRONTEND_PORT=3001\nBACKEND_PORT=8001\n",
314+
},
315+
"{path}/.env",
316+
{
317+
"APP_NAME": "my_test_app",
318+
"FRONTEND_PORT": "3001",
319+
"BACKEND_PORT": "8001",
320+
},
321+
),
322+
(
323+
{
324+
".env": "FRONTEND_PORT=4001",
325+
},
326+
"{path}/.env{sep}{path}/.env.local",
327+
{
328+
"FRONTEND_PORT": "4001",
329+
},
330+
),
331+
(
332+
{
333+
".env": "APP_NAME=my_test_app\nFRONTEND_PORT=3001\nBACKEND_PORT=8001\n",
334+
".env.local": "FRONTEND_PORT=3002\n",
335+
},
336+
"{path}/.env.local{sep}{path}/.env",
337+
{
338+
"APP_NAME": "my_test_app",
339+
"FRONTEND_PORT": "3002", # Overrides .env
340+
"BACKEND_PORT": "8001",
341+
},
342+
),
343+
],
344+
)
345+
def test_env_file(
346+
tmp_path: Path,
347+
file_map: dict[str, str],
348+
env_file: str,
349+
exp_env_vars: dict[str, str],
350+
) -> None:
351+
"""Test that the env_file method loads environment variables from a file.
352+
353+
Args:
354+
tmp_path: The pytest tmp_path object.
355+
file_map: A mapping of file names to their contents.
356+
env_file: The path to the environment file to load.
357+
exp_env_vars: The expected environment variables after loading the file.
358+
"""
359+
for filename, content in file_map.items():
360+
(tmp_path / filename).write_text(content)
361+
362+
_ = rx.Config(
363+
app_name="test_env_file",
364+
env_file=env_file.format(path=tmp_path, sep=os.pathsep),
365+
)
366+
for key, value in exp_env_vars.items():
367+
assert os.environ.get(key) == value

uv.lock

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)