Skip to content

Commit 45bf75b

Browse files
authored
✨ Add support for the PORT environment variable (#209)
1 parent abafc89 commit 45bf75b

File tree

2 files changed

+130
-2
lines changed

2 files changed

+130
-2
lines changed

src/fastapi_cli/cli.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,8 @@ def dev(
206206
port: Annotated[
207207
int,
208208
typer.Option(
209-
help="The port to serve on. You would normally have a termination proxy on top (another program) handling HTTPS on port [blue]443[/blue] and HTTP on port [blue]80[/blue], transferring the communication to your app."
209+
help="The port to serve on. You would normally have a termination proxy on top (another program) handling HTTPS on port [blue]443[/blue] and HTTP on port [blue]80[/blue], transferring the communication to your app.",
210+
envvar="PORT",
210211
),
211212
] = 8000,
212213
reload: Annotated[
@@ -305,7 +306,8 @@ def run(
305306
port: Annotated[
306307
int,
307308
typer.Option(
308-
help="The port to serve on. You would normally have a termination proxy on top (another program) handling HTTPS on port [blue]443[/blue] and HTTP on port [blue]80[/blue], transferring the communication to your app."
309+
help="The port to serve on. You would normally have a termination proxy on top (another program) handling HTTPS on port [blue]443[/blue] and HTTP on port [blue]80[/blue], transferring the communication to your app.",
310+
envvar="PORT",
309311
),
310312
] = 8000,
311313
reload: Annotated[

tests/test_cli.py

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,73 @@ def test_dev_args() -> None:
122122
)
123123

124124

125+
def test_dev_env_vars() -> None:
126+
with changing_dir(assets_path):
127+
with patch.object(uvicorn, "run") as mock_run:
128+
result = runner.invoke(
129+
app, ["dev", "single_file_app.py"], env={"PORT": "8111"}
130+
)
131+
assert result.exit_code == 0, result.output
132+
assert mock_run.called
133+
assert mock_run.call_args
134+
assert mock_run.call_args.kwargs == {
135+
"app": "single_file_app:app",
136+
"host": "127.0.0.1",
137+
"port": 8111,
138+
"reload": True,
139+
"workers": None,
140+
"root_path": "",
141+
"proxy_headers": True,
142+
"forwarded_allow_ips": None,
143+
"log_config": get_uvicorn_log_config(),
144+
}
145+
assert "Using import string: single_file_app:app" in result.output
146+
assert "Starting development server 🚀" in result.output
147+
assert "Server started at http://127.0.0.1:8111" in result.output
148+
assert "Documentation at http://127.0.0.1:8111/docs" in result.output
149+
assert (
150+
"Running in development mode, for production use: fastapi run"
151+
in result.output
152+
)
153+
154+
155+
def test_dev_env_vars_and_args() -> None:
156+
with changing_dir(assets_path):
157+
with patch.object(uvicorn, "run") as mock_run:
158+
result = runner.invoke(
159+
app,
160+
[
161+
"dev",
162+
"single_file_app.py",
163+
"--port",
164+
"8080",
165+
],
166+
env={"PORT": "8111"},
167+
)
168+
assert result.exit_code == 0, result.output
169+
assert mock_run.called
170+
assert mock_run.call_args
171+
assert mock_run.call_args.kwargs == {
172+
"app": "single_file_app:app",
173+
"host": "127.0.0.1",
174+
"port": 8080,
175+
"reload": True,
176+
"workers": None,
177+
"root_path": "",
178+
"proxy_headers": True,
179+
"forwarded_allow_ips": None,
180+
"log_config": get_uvicorn_log_config(),
181+
}
182+
assert "Using import string: single_file_app:app" in result.output
183+
assert "Starting development server 🚀" in result.output
184+
assert "Server started at http://127.0.0.1:8080" in result.output
185+
assert "Documentation at http://127.0.0.1:8080/docs" in result.output
186+
assert (
187+
"Running in development mode, for production use: fastapi run"
188+
in result.output
189+
)
190+
191+
125192
def test_run() -> None:
126193
with changing_dir(assets_path):
127194
with patch.object(uvicorn, "run") as mock_run:
@@ -223,6 +290,65 @@ def test_run_args() -> None:
223290
)
224291

225292

293+
def test_run_env_vars() -> None:
294+
with changing_dir(assets_path):
295+
with patch.object(uvicorn, "run") as mock_run:
296+
result = runner.invoke(
297+
app, ["run", "single_file_app.py"], env={"PORT": "8111"}
298+
)
299+
assert result.exit_code == 0, result.output
300+
assert mock_run.called
301+
assert mock_run.call_args
302+
assert mock_run.call_args.kwargs == {
303+
"app": "single_file_app:app",
304+
"host": "0.0.0.0",
305+
"port": 8111,
306+
"reload": False,
307+
"workers": None,
308+
"root_path": "",
309+
"proxy_headers": True,
310+
"forwarded_allow_ips": None,
311+
"log_config": get_uvicorn_log_config(),
312+
}
313+
assert "Using import string: single_file_app:app" in result.output
314+
assert "Starting production server 🚀" in result.output
315+
assert "Server started at http://0.0.0.0:8111" in result.output
316+
assert "Documentation at http://0.0.0.0:8111/docs" in result.output
317+
318+
319+
def test_run_env_vars_and_args() -> None:
320+
with changing_dir(assets_path):
321+
with patch.object(uvicorn, "run") as mock_run:
322+
result = runner.invoke(
323+
app,
324+
[
325+
"run",
326+
"single_file_app.py",
327+
"--port",
328+
"8080",
329+
],
330+
env={"PORT": "8111"},
331+
)
332+
assert result.exit_code == 0, result.output
333+
assert mock_run.called
334+
assert mock_run.call_args
335+
assert mock_run.call_args.kwargs == {
336+
"app": "single_file_app:app",
337+
"host": "0.0.0.0",
338+
"port": 8080,
339+
"reload": False,
340+
"workers": None,
341+
"root_path": "",
342+
"proxy_headers": True,
343+
"forwarded_allow_ips": None,
344+
"log_config": get_uvicorn_log_config(),
345+
}
346+
assert "Using import string: single_file_app:app" in result.output
347+
assert "Starting production server 🚀" in result.output
348+
assert "Server started at http://0.0.0.0:8080" in result.output
349+
assert "Documentation at http://0.0.0.0:8080/docs" in result.output
350+
351+
226352
def test_run_error() -> None:
227353
with changing_dir(assets_path):
228354
result = runner.invoke(app, ["run", "non_existing_file.py"])

0 commit comments

Comments
 (0)