Sfoglia il codice sorgente

Allow multiple env_file to be specified, split on os pathsep (#4993)

Masen Furer 2 mesi fa
parent
commit
52dccdfecc
4 ha cambiato i file con 97 aggiunte e 3 eliminazioni
  1. 2 1
      pyproject.toml
  2. 8 2
      reflex/config.py
  3. 76 0
      tests/units/test_config.py
  4. 11 0
      uv.lock

+ 2 - 1
pyproject.toml

@@ -139,6 +139,7 @@ dev = [
   "asynctest >=0.13.0,<1.0",
   "asynctest >=0.13.0,<1.0",
   "darglint >=1.8.1,<2.0",
   "darglint >=1.8.1,<2.0",
   "dill >=0.3.8",
   "dill >=0.3.8",
+  "libsass >=0.23.0,<1.0",
   "numpy >=2.2.3,<3.0",
   "numpy >=2.2.3,<3.0",
   "pandas >=2.1.1,<3.0",
   "pandas >=2.1.1,<3.0",
   "pillow >=10.0.0,<12.0",
   "pillow >=10.0.0,<12.0",
@@ -156,7 +157,7 @@ dev = [
   "pytest-playwright >=0.5.1",
   "pytest-playwright >=0.5.1",
   "pytest-retry >=1.7.0,<2.0",
   "pytest-retry >=1.7.0,<2.0",
   "pytest-split >=0.10.0,<1.0",
   "pytest-split >=0.10.0,<1.0",
-  "libsass >=0.23.0,<1.0",
+  "python-dotenv >=1,<2",
   "ruff ==0.11.0",
   "ruff ==0.11.0",
   "selenium >=4.11.0,<5.0",
   "selenium >=4.11.0,<5.0",
   "toml >=0.10.2,<1.0",
   "toml >=0.10.2,<1.0",

+ 8 - 2
reflex/config.py

@@ -932,8 +932,14 @@ class Config(Base):
                     """The `python-dotenv` package is required to load environment variables from a file. Run `pip install "python-dotenv>=1.0.1"`."""
                     """The `python-dotenv` package is required to load environment variables from a file. Run `pip install "python-dotenv>=1.0.1"`."""
                 )
                 )
             else:
             else:
-                # load env file if exists
-                load_dotenv(env_file, override=True)
+                # load env files in reverse order if they exist
+                for env_file_path in [
+                    Path(p)
+                    for s in reversed(env_file.split(os.pathsep))
+                    if (p := s.strip())
+                ]:
+                    if env_file_path.exists():
+                        load_dotenv(env_file_path, override=True)
 
 
         updated_values = {}
         updated_values = {}
         # Iterate over the fields.
         # Iterate over the fields.

+ 76 - 0
tests/units/test_config.py

@@ -289,3 +289,79 @@ def test_env_var():
     assert TestEnv.LIST.get() == [4, 5, 6]
     assert TestEnv.LIST.get() == [4, 5, 6]
     TestEnv.LIST.set(None)
     TestEnv.LIST.set(None)
     assert "LIST" not in os.environ
     assert "LIST" not in os.environ
+
+
+@pytest.fixture
+def restore_env():
+    """Fixture to restore the environment variables after the test.
+
+    Yields:
+        None: Placeholder for the test to run.
+    """
+    original_env = os.environ.copy()
+    yield
+    os.environ.clear()
+    os.environ.update(original_env)
+
+
+@pytest.mark.usefixtures("restore_env")
+@pytest.mark.parametrize(
+    ("file_map", "env_file", "exp_env_vars"),
+    [
+        (
+            {
+                ".env": "APP_NAME=my_test_app\nFRONTEND_PORT=3001\nBACKEND_PORT=8001\n",
+            },
+            "{path}/.env",
+            {
+                "APP_NAME": "my_test_app",
+                "FRONTEND_PORT": "3001",
+                "BACKEND_PORT": "8001",
+            },
+        ),
+        (
+            {
+                ".env": "FRONTEND_PORT=4001",
+            },
+            "{path}/.env{sep}{path}/.env.local",
+            {
+                "FRONTEND_PORT": "4001",
+            },
+        ),
+        (
+            {
+                ".env": "APP_NAME=my_test_app\nFRONTEND_PORT=3001\nBACKEND_PORT=8001\n",
+                ".env.local": "FRONTEND_PORT=3002\n",
+            },
+            "{path}/.env.local{sep}{path}/.env",
+            {
+                "APP_NAME": "my_test_app",
+                "FRONTEND_PORT": "3002",  # Overrides .env
+                "BACKEND_PORT": "8001",
+            },
+        ),
+    ],
+)
+def test_env_file(
+    tmp_path: Path,
+    file_map: dict[str, str],
+    env_file: str,
+    exp_env_vars: dict[str, str],
+) -> None:
+    """Test that the env_file method loads environment variables from a file.
+
+    Args:
+        tmp_path: The pytest tmp_path object.
+        file_map: A mapping of file names to their contents.
+        env_file: The path to the environment file to load.
+        exp_env_vars: The expected environment variables after loading the file.
+    """
+    for filename, content in file_map.items():
+        (tmp_path / filename).write_text(content)
+
+    _ = rx.Config(
+        app_name="test_env_file",
+        env_file=env_file.format(path=tmp_path, sep=os.pathsep),
+    )
+    for key, value in exp_env_vars.items():
+        assert os.environ.get(key) == value

+ 11 - 0
uv.lock

@@ -1552,6 +1552,15 @@ wheels = [
     { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892 },
     { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892 },
 ]
 ]
 
 
+[[package]]
+name = "python-dotenv"
+version = "1.0.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/bc/57/e84d88dfe0aec03b7a2d4327012c1627ab5f03652216c63d49846d7a6c58/python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca", size = 39115 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/6a/3e/b68c118422ec867fa7ab88444e1274aa40681c606d59ac27de5a5588f082/python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a", size = 19863 },
+]
+
 [[package]]
 [[package]]
 name = "python-engineio"
 name = "python-engineio"
 version = "4.11.2"
 version = "4.11.2"
@@ -1744,6 +1753,7 @@ dev = [
     { name = "pytest-playwright" },
     { name = "pytest-playwright" },
     { name = "pytest-retry" },
     { name = "pytest-retry" },
     { name = "pytest-split" },
     { name = "pytest-split" },
+    { name = "python-dotenv" },
     { name = "ruff" },
     { name = "ruff" },
     { name = "selenium" },
     { name = "selenium" },
     { name = "toml" },
     { name = "toml" },
@@ -1805,6 +1815,7 @@ dev = [
     { name = "pytest-playwright", specifier = ">=0.5.1" },
     { name = "pytest-playwright", specifier = ">=0.5.1" },
     { name = "pytest-retry", specifier = ">=1.7.0,<2.0" },
     { name = "pytest-retry", specifier = ">=1.7.0,<2.0" },
     { name = "pytest-split", specifier = ">=0.10.0,<1.0" },
     { name = "pytest-split", specifier = ">=0.10.0,<1.0" },
+    { name = "python-dotenv", specifier = ">=1,<2" },
     { name = "ruff", specifier = "==0.11.0" },
     { name = "ruff", specifier = "==0.11.0" },
     { name = "selenium", specifier = ">=4.11.0,<5.0" },
     { name = "selenium", specifier = ">=4.11.0,<5.0" },
     { name = "toml", specifier = ">=0.10.2,<1.0" },
     { name = "toml", specifier = ">=0.10.2,<1.0" },