Skip to content

Commit bd5d31a

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

4 files changed

Lines changed: 66 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: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,8 @@ def dev(
399399
400400
Otherwise, it uses the first [bold]FastAPI[/bold] app found in the imported module or package.
401401
"""
402+
os.environ.setdefault("FASTAPI_ENV", "development")
403+
402404
_run(
403405
path=path,
404406
host=host,

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+
def test_dev_sets_fastapi_env_before_app_import() -> None:
83+
response = _get_fastapi_env_response(command="dev", fastapi_env=None)
84+
85+
assert response.status_code == 200
86+
assert response.json() == {"fastapi_env": "development"}
87+
88+
89+
def test_dev_does_not_override_existing_fastapi_env() -> None:
90+
response = _get_fastapi_env_response(command="dev", fastapi_env="chicken")
91+
92+
assert response.status_code == 200
93+
assert response.json() == {"fastapi_env": "chicken"}
94+
95+
96+
def test_run_does_not_set_fastapi_env() -> None:
97+
response = _get_fastapi_env_response(command="run", fastapi_env=None)
98+
99+
assert response.status_code == 200
100+
assert response.json() == {"fastapi_env": None}
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)