Skip to content

Commit 1362fbd

Browse files
committed
✨ Set FASTAPI_ENV when running fastapi dev
Shortcake-Parent: main
1 parent c1402c0 commit 1362fbd

4 files changed

Lines changed: 67 additions & 1 deletion

File tree

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,10 @@ By default, it will have **auto-reload** enabled, so it will automatically reloa
7676

7777
By default it will listen on the IP address `127.0.0.1`, which is the IP for your machine to communicate with itself alone (`localhost`).
7878

79+
Before importing your app, `fastapi dev` sets the `FASTAPI_ENV` environment variable to `development`. If `FASTAPI_ENV` is already set, its existing value is preserved. This lets app startup code choose development-friendly behavior while allowing you to provide an app-specific environment such as `staging`.
80+
81+
The conventional `FASTAPI_ENV` values are `development` and `production`. `fastapi run` currently leaves `FASTAPI_ENV` unchanged, so set it explicitly if your app needs to detect production mode.
82+
7983
## `fastapi run`
8084

8185
When you run `fastapi run`, it will run on production mode by default.

src/fastapi_cli/cli.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,9 @@ def _run(
144144
public_url: str | None = None,
145145
verbose: bool = False,
146146
) -> None:
147+
if command == "dev":
148+
os.environ.setdefault("FASTAPI_ENV", "development")
149+
147150
with get_rich_toolkit() as toolkit:
148151
server_type = "development" if command == "dev" else "production"
149152

tests/assets/environment_app.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import os
2+
3+
from fastapi import FastAPI
4+
5+
app = FastAPI()
6+
7+
fastapi_env_at_import = os.environ.get("FASTAPI_ENV")
8+
9+
10+
@app.get("/fastapi-env")
11+
def get_fastapi_env() -> dict[str, str | None]:
12+
return {"fastapi_env": fastapi_env_at_import}

tests/test_cli.py

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@
66

77
import pytest
88
import uvicorn
9+
from fastapi.testclient import TestClient
10+
from httpx import Response
911
from typer.testing import CliRunner
12+
from uvicorn.importer import import_from_string
1013

1114
from fastapi_cli.cli import app
1215
from fastapi_cli.utils.cli import get_uvicorn_log_config
@@ -18,7 +21,8 @@
1821

1922

2023
@pytest.fixture(autouse=True)
21-
def force_rich_logs(monkeypatch: pytest.MonkeyPatch) -> None:
24+
def isolate_cli_environment(monkeypatch: pytest.MonkeyPatch) -> None:
25+
monkeypatch.delenv("FASTAPI_ENV", raising=False)
2226
monkeypatch.setattr("fastapi_cli.cli.should_use_rich_logs", lambda: True)
2327

2428

@@ -53,6 +57,49 @@ def test_dev() -> None:
5357
assert "Configuration sources:" not in result.output
5458

5559

60+
def _get_fastapi_env_response(*, command: str, fastapi_env: str | None) -> Response:
61+
module_name = "environment_app"
62+
sys.modules.pop(module_name, None)
63+
64+
try:
65+
with changing_dir(assets_path):
66+
with patch.object(uvicorn, "run") as mock_run:
67+
result = runner.invoke(
68+
app,
69+
[command, f"{module_name}.py"],
70+
env={"FASTAPI_ENV": fastapi_env},
71+
)
72+
73+
assert result.exit_code == 0, result.output
74+
assert mock_run.call_args
75+
imported_app = import_from_string(mock_run.call_args.kwargs["app"])
76+
with TestClient(imported_app) as test_client:
77+
return test_client.get("/fastapi-env")
78+
finally:
79+
sys.modules.pop(module_name, None)
80+
81+
82+
@pytest.mark.parametrize(
83+
("command", "existing_fastapi_env", "expected_fastapi_env"),
84+
[
85+
pytest.param("dev", None, "development", id="dev-sets-development"),
86+
pytest.param("dev", "staging", "staging", id="dev-preserves-existing-value"),
87+
pytest.param("run", None, None, id="run-leaves-value-unset"),
88+
],
89+
)
90+
def test_fastapi_env_available_to_app(
91+
command: str,
92+
existing_fastapi_env: str | None,
93+
expected_fastapi_env: str | None,
94+
) -> None:
95+
response = _get_fastapi_env_response(
96+
command=command, fastapi_env=existing_fastapi_env
97+
)
98+
99+
assert response.status_code == 200
100+
assert response.json() == {"fastapi_env": expected_fastapi_env}
101+
102+
56103
def test_run_uses_uvicorn_default_log_config_without_rich_logs(
57104
monkeypatch: pytest.MonkeyPatch,
58105
) -> None:

0 commit comments

Comments
 (0)