Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 34 additions & 17 deletions src/fastapi_cli/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,39 +195,44 @@ def dev(
path: Annotated[
Union[Path, None],
typer.Argument(
help="A path to a Python file or package directory (with [blue]__init__.py[/blue] files) containing a [bold]FastAPI[/bold] app. If not provided, a default set of paths will be tried."
help="A path to a Python file or package directory (with [blue]__init__.py[/blue] files) containing a [bold]FastAPI[/bold] app. If not provided, a default set of paths will be tried.",
envvar="FASTAPI_PATH",
),
] = None,
*,
host: Annotated[
str,
typer.Option(
help="The host to serve on. For local development in localhost use [blue]127.0.0.1[/blue]. To enable public access, e.g. in a container, use all the IP addresses available with [blue]0.0.0.0[/blue]."
help="The host to serve on. For local development in localhost use [blue]127.0.0.1[/blue]. To enable public access, e.g. in a container, use all the IP addresses available with [blue]0.0.0.0[/blue].",
envvar="FASTAPI_HOST",
),
] = "127.0.0.1",
port: Annotated[
int,
typer.Option(
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.",
envvar="PORT",
envvar="FASTAPI_PORT",
),
] = 8000,
reload: Annotated[
bool,
typer.Option(
help="Enable auto-reload of the server when (code) files change. This is [bold]resource intensive[/bold], use it only during development."
help="Enable auto-reload of the server when (code) files change. This is [bold]resource intensive[/bold], use it only during development.",
envvar="FASTAPI_RELOAD",
),
] = True,
root_path: Annotated[
str,
typer.Option(
help="The root path is used to tell your app that it is being served to the outside world with some [bold]path prefix[/bold] set up in some termination proxy or similar."
help="The root path is used to tell your app that it is being served to the outside world with some [bold]path prefix[/bold] set up in some termination proxy or similar.",
envvar="FASTAPI_ROOTPATH",
),
] = "",
app: Annotated[
Union[str, None],
typer.Option(
help="The name of the variable that contains the [bold]FastAPI[/bold] app in the imported module or package. If not provided, it is detected automatically."
help="The name of the variable that contains the [bold]FastAPI[/bold] app in the imported module or package. If not provided, it is detected automatically.",
envvar="FASTAPI_APP",
),
] = None,
entrypoint: Annotated[
Expand All @@ -236,18 +241,21 @@ def dev(
"--entrypoint",
"-e",
help="The FastAPI app import string in the format 'some.importable_module:app_name'.",
envvar="FASTAPI_ENTRYPOINT",
),
] = None,
proxy_headers: Annotated[
bool,
typer.Option(
help="Enable/Disable X-Forwarded-Proto, X-Forwarded-For, X-Forwarded-Port to populate remote address info."
help="Enable/Disable X-Forwarded-Proto, X-Forwarded-For, X-Forwarded-Port to populate remote address info.",
envvar="FASTAPI_PROXY_HEADERS",
),
] = True,
forwarded_allow_ips: Annotated[
Union[str, None],
typer.Option(
help="Comma separated list of IP Addresses to trust with proxy headers. The literal '*' means trust everything."
help="Comma separated list of IP Addresses to trust with proxy headers. The literal '*' means trust everything.",
envvar="FASTAPI_FORWARDED_ALLOW_IPS",
),
] = None,
) -> Any:
Expand Down Expand Up @@ -295,45 +303,51 @@ def run(
path: Annotated[
Union[Path, None],
typer.Argument(
help="A path to a Python file or package directory (with [blue]__init__.py[/blue] files) containing a [bold]FastAPI[/bold] app. If not provided, a default set of paths will be tried."
help="A path to a Python file or package directory (with [blue]__init__.py[/blue] files) containing a [bold]FastAPI[/bold] app. If not provided, a default set of paths will be tried.",
envvar="FASTAPI_PATH",
),
] = None,
*,
host: Annotated[
str,
typer.Option(
help="The host to serve on. For local development in localhost use [blue]127.0.0.1[/blue]. To enable public access, e.g. in a container, use all the IP addresses available with [blue]0.0.0.0[/blue]."
help="The host to serve on. For local development in localhost use [blue]127.0.0.1[/blue]. To enable public access, e.g. in a container, use all the IP addresses available with [blue]0.0.0.0[/blue].",
envvar="FASTAPI_HOST",
),
] = "0.0.0.0",
port: Annotated[
int,
typer.Option(
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.",
envvar="PORT",
envvar="FASTAPI_PORT",
),
] = 8000,
reload: Annotated[
bool,
typer.Option(
help="Enable auto-reload of the server when (code) files change. This is [bold]resource intensive[/bold], use it only during development."
help="Enable auto-reload of the server when (code) files change. This is [bold]resource intensive[/bold], use it only during development.",
envvar="FASTAPI_RELOAD",
),
] = False,
workers: Annotated[
Union[int, None],
typer.Option(
help="Use multiple worker processes. Mutually exclusive with the --reload flag."
help="Use multiple worker processes. Mutually exclusive with the --reload flag.",
envvar="FASTAPI_WORKERS",
),
] = None,
root_path: Annotated[
str,
typer.Option(
help="The root path is used to tell your app that it is being served to the outside world with some [bold]path prefix[/bold] set up in some termination proxy or similar."
help="The root path is used to tell your app that it is being served to the outside world with some [bold]path prefix[/bold] set up in some termination proxy or similar.",
envvar="FASTAPI_ROOTPATH",
),
] = "",
app: Annotated[
Union[str, None],
typer.Option(
help="The name of the variable that contains the [bold]FastAPI[/bold] app in the imported module or package. If not provided, it is detected automatically."
help="The name of the variable that contains the [bold]FastAPI[/bold] app in the imported module or package. If not provided, it is detected automatically.",
envvar="FASTAPI_APP",
),
] = None,
entrypoint: Annotated[
Expand All @@ -342,18 +356,21 @@ def run(
"--entrypoint",
"-e",
help="The FastAPI app import string in the format 'some.importable_module:app_name'.",
envvar="FASTAPI_ENTRYPOINT",
),
] = None,
proxy_headers: Annotated[
bool,
typer.Option(
help="Enable/Disable X-Forwarded-Proto, X-Forwarded-For, X-Forwarded-Port to populate remote address info."
help="Enable/Disable X-Forwarded-Proto, X-Forwarded-For, X-Forwarded-Port to populate remote address info.",
envvar="FASTAPI_PROXY_HEADERS",
),
] = True,
forwarded_allow_ips: Annotated[
Union[str, None],
typer.Option(
help="Comma separated list of IP Addresses to trust with proxy headers. The literal '*' means trust everything."
help="Comma separated list of IP Addresses to trust with proxy headers. The literal '*' means trust everything.",
envvar="FASTAPI_FORWARDED_ALLOW_IPS",
),
] = None,
) -> Any:
Expand Down
67 changes: 44 additions & 23 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,26 +126,36 @@ def test_dev_env_vars() -> None:
with changing_dir(assets_path):
with patch.object(uvicorn, "run") as mock_run:
result = runner.invoke(
app, ["dev", "single_file_app.py"], env={"PORT": "8111"}
app,
["dev", "single_file_app.py"],
env={
"FASTAPI_HOST": "127.0.0.2",
"FASTAPI_PORT": "8111",
"FASTAPI_RELOAD": "false",
"FASTAPI_ROOTPATH": "/api",
"FASTAPI_APP": "api",
"FASTAPI_PROXY_HEADERS": "false",
"FASTAPI_FORWARDED_ALLOW_IPS": "*",
},
)
assert result.exit_code == 0, result.output
assert mock_run.called
assert mock_run.call_args
assert mock_run.call_args.kwargs == {
"app": "single_file_app:app",
"host": "127.0.0.1",
"app": "single_file_app:api",
"host": "127.0.0.2",
"port": 8111,
"reload": True,
"reload": False,
"workers": None,
"root_path": "",
"proxy_headers": True,
"forwarded_allow_ips": None,
"root_path": "/api",
"proxy_headers": False,
"forwarded_allow_ips": "*",
"log_config": get_uvicorn_log_config(),
}
assert "Using import string: single_file_app:app" in result.output
assert "Using import string: single_file_app:api" in result.output
assert "Starting development server 🚀" in result.output
assert "Server started at http://127.0.0.1:8111" in result.output
assert "Documentation at http://127.0.0.1:8111/docs" in result.output
assert "Server started at http://127.0.0.2:8111" in result.output
assert "Documentation at http://127.0.0.2:8111/docs" in result.output
assert (
"Running in development mode, for production use: fastapi run"
in result.output
Expand All @@ -163,7 +173,7 @@ def test_dev_env_vars_and_args() -> None:
"--port",
"8080",
],
env={"PORT": "8111"},
env={"FASTAPI_PORT": "8111"},
)
assert result.exit_code == 0, result.output
assert mock_run.called
Expand Down Expand Up @@ -294,26 +304,37 @@ def test_run_env_vars() -> None:
with changing_dir(assets_path):
with patch.object(uvicorn, "run") as mock_run:
result = runner.invoke(
app, ["run", "single_file_app.py"], env={"PORT": "8111"}
app,
["run", "single_file_app.py"],
env={
"FASTAPI_HOST": "192.168.1.1",
"FASTAPI_PORT": "8111",
"FASTAPI_RELOAD": "true",
"FASTAPI_WORKERS": "4",
"FASTAPI_ROOTPATH": "/api",
"FASTAPI_APP": "api",
"FASTAPI_PROXY_HEADERS": "false",
"FASTAPI_FORWARDED_ALLOW_IPS": "*",
},
)
assert result.exit_code == 0, result.output
assert mock_run.called
assert mock_run.call_args
assert mock_run.call_args.kwargs == {
"app": "single_file_app:app",
"host": "0.0.0.0",
"app": "single_file_app:api",
"host": "192.168.1.1",
"port": 8111,
"reload": False,
"workers": None,
"root_path": "",
"proxy_headers": True,
"forwarded_allow_ips": None,
"reload": True,
"workers": 4,
"root_path": "/api",
"proxy_headers": False,
"forwarded_allow_ips": "*",
"log_config": get_uvicorn_log_config(),
}
assert "Using import string: single_file_app:app" in result.output
assert "Using import string: single_file_app:api" in result.output
assert "Starting production server 🚀" in result.output
assert "Server started at http://0.0.0.0:8111" in result.output
assert "Documentation at http://0.0.0.0:8111/docs" in result.output
assert "Server started at http://192.168.1.1:8111" in result.output
assert "Documentation at http://192.168.1.1:8111/docs" in result.output


def test_run_env_vars_and_args() -> None:
Expand All @@ -327,7 +348,7 @@ def test_run_env_vars_and_args() -> None:
"--port",
"8080",
],
env={"PORT": "8111"},
env={"FASTAPI_PORT": "8111"},
)
assert result.exit_code == 0, result.output
assert mock_run.called
Expand Down
Loading