Skip to content
84 changes: 84 additions & 0 deletions src/fastapi_cli/cli.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import logging
from enum import Enum
from pathlib import Path
from typing import Annotated, Any

Expand All @@ -22,6 +23,13 @@
logger = logging.getLogger(__name__)


class WSProtocolType(str, Enum):
auto = "auto"
none = "none"
websockets = "websockets"
wsproto = "wsproto"


try:
import uvicorn
except ImportError: # pragma: no cover
Expand Down Expand Up @@ -111,6 +119,12 @@ def _run(
app: str | None = None,
entrypoint: str | None = None,
proxy_headers: bool = False,
ws: WSProtocolType = WSProtocolType.auto,
ws_max_size: int = 16777216,
ws_max_queue: int = 32,
ws_ping_interval: float = 20.0,
ws_ping_timeout: float = 20.0,
ws_per_message_deflate: bool = True,
forwarded_allow_ips: str | None = None,
) -> None:
with get_rich_toolkit() as toolkit:
Expand Down Expand Up @@ -230,6 +244,12 @@ def _run(
proxy_headers=proxy_headers,
forwarded_allow_ips=forwarded_allow_ips,
log_config=get_uvicorn_log_config(),
ws=ws.value,
ws_max_size=ws_max_size,
ws_max_queue=ws_max_queue,
ws_ping_interval=ws_ping_interval,
ws_ping_timeout=ws_ping_timeout,
ws_per_message_deflate=ws_per_message_deflate,
)


Expand Down Expand Up @@ -293,6 +313,32 @@ def dev(
help="Enable/Disable X-Forwarded-Proto, X-Forwarded-For, X-Forwarded-Port to populate remote address info."
),
] = True,
ws: Annotated[
WSProtocolType,
typer.Option(
help="The WebSocket protocol.", case_sensitive=False, show_choices=True
),
] = WSProtocolType.auto,
ws_max_size: Annotated[
int,
typer.Option(help="WebSocket max size message in bytes."),
] = 16777216,
ws_max_queue: Annotated[
int,
typer.Option(help="The maximum length of the WebSocket message queue."),
] = 32,
ws_ping_interval: Annotated[
float,
typer.Option(help="WebSocket ping interval in seconds."),
] = 20.0,
ws_ping_timeout: Annotated[
float,
typer.Option(help="WebSocket ping timeout in seconds."),
] = 20.0,
ws_per_message_deflate: Annotated[
bool,
typer.Option(help="WebSocket per-message-deflate compression"),
] = True,
forwarded_allow_ips: Annotated[
str | None,
typer.Option(
Expand Down Expand Up @@ -336,6 +382,12 @@ def dev(
entrypoint=entrypoint,
command="dev",
proxy_headers=proxy_headers,
ws=ws,
ws_max_size=ws_max_size,
ws_max_queue=ws_max_queue,
ws_ping_interval=ws_ping_interval,
ws_ping_timeout=ws_ping_timeout,
ws_per_message_deflate=ws_per_message_deflate,
forwarded_allow_ips=forwarded_allow_ips,
)

Expand Down Expand Up @@ -400,6 +452,32 @@ def run(
help="Enable/Disable X-Forwarded-Proto, X-Forwarded-For, X-Forwarded-Port to populate remote address info."
),
] = True,
ws: Annotated[
WSProtocolType,
typer.Option(
help="The WebSocket protocol.", case_sensitive=False, show_choices=True
),
] = WSProtocolType.auto,
ws_max_size: Annotated[
int,
typer.Option(help="WebSocket max size message in bytes."),
] = 16777216,
ws_max_queue: Annotated[
int,
typer.Option(help="The maximum length of the WebSocket message queue."),
] = 32,
ws_ping_interval: Annotated[
float,
typer.Option(help="WebSocket ping interval in seconds."),
] = 20.0,
ws_ping_timeout: Annotated[
float,
typer.Option(help="WebSocket ping timeout in seconds."),
] = 20.0,
ws_per_message_deflate: Annotated[
bool,
typer.Option(help="WebSocket per-message-deflate compression"),
] = True,
forwarded_allow_ips: Annotated[
str | None,
typer.Option(
Expand Down Expand Up @@ -443,6 +521,12 @@ def run(
entrypoint=entrypoint,
command="run",
proxy_headers=proxy_headers,
ws=ws,
ws_max_size=ws_max_size,
ws_max_queue=ws_max_queue,
ws_ping_interval=ws_ping_interval,
ws_ping_timeout=ws_ping_timeout,
ws_per_message_deflate=ws_per_message_deflate,
forwarded_allow_ips=forwarded_allow_ips,
)

Expand Down
76 changes: 76 additions & 0 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ def test_dev() -> None:
"workers": None,
"root_path": "",
"proxy_headers": True,
"ws": "auto",
"ws_max_size": 16777216,
"ws_max_queue": 32,
"ws_ping_interval": 20.0,
"ws_ping_timeout": 20.0,
"ws_per_message_deflate": True,
"forwarded_allow_ips": None,
"log_config": get_uvicorn_log_config(),
}
Expand Down Expand Up @@ -77,6 +83,12 @@ def test_dev_package() -> None:
"workers": None,
"root_path": "",
"proxy_headers": True,
"ws": "auto",
"ws_max_size": 16777216,
"ws_max_queue": 32,
"ws_ping_interval": 20.0,
"ws_ping_timeout": 20.0,
"ws_per_message_deflate": True,
"forwarded_allow_ips": None,
"log_config": get_uvicorn_log_config(),
}
Expand Down Expand Up @@ -113,6 +125,8 @@ def test_dev_args() -> None:
"--app",
"api",
"--no-proxy-headers",
"--ws",
"auto",
],
)
assert result.exit_code == 0, result.output
Expand All @@ -127,6 +141,12 @@ def test_dev_args() -> None:
"workers": None,
"root_path": "/api",
"proxy_headers": False,
"ws": "auto",
"ws_max_size": 16777216,
"ws_max_queue": 32,
"ws_ping_interval": 20.0,
"ws_ping_timeout": 20.0,
"ws_per_message_deflate": True,
"forwarded_allow_ips": None,
"log_config": get_uvicorn_log_config(),
}
Expand Down Expand Up @@ -158,6 +178,12 @@ def test_dev_env_vars() -> None:
"workers": None,
"root_path": "",
"proxy_headers": True,
"ws": "auto",
"ws_max_size": 16777216,
"ws_max_queue": 32,
"ws_ping_interval": 20.0,
"ws_ping_timeout": 20.0,
"ws_per_message_deflate": True,
"forwarded_allow_ips": None,
"log_config": get_uvicorn_log_config(),
}
Expand Down Expand Up @@ -196,6 +222,12 @@ def test_dev_env_vars_and_args() -> None:
"workers": None,
"root_path": "",
"proxy_headers": True,
"ws": "auto",
"ws_max_size": 16777216,
"ws_max_queue": 32,
"ws_ping_interval": 20.0,
"ws_ping_timeout": 20.0,
"ws_per_message_deflate": True,
"forwarded_allow_ips": None,
"log_config": get_uvicorn_log_config(),
}
Expand Down Expand Up @@ -242,6 +274,12 @@ def test_run() -> None:
"workers": None,
"root_path": "",
"proxy_headers": True,
"ws": "auto",
"ws_max_size": 16777216,
"ws_max_queue": 32,
"ws_ping_interval": 20.0,
"ws_ping_timeout": 20.0,
"ws_per_message_deflate": True,
"forwarded_allow_ips": None,
"log_config": get_uvicorn_log_config(),
}
Expand Down Expand Up @@ -269,6 +307,12 @@ def test_run_trust_proxy() -> None:
"workers": None,
"root_path": "",
"proxy_headers": True,
"ws": "auto",
"ws_max_size": 16777216,
"ws_max_queue": 32,
"ws_ping_interval": 20.0,
"ws_ping_timeout": 20.0,
"ws_per_message_deflate": True,
"forwarded_allow_ips": "*",
"log_config": get_uvicorn_log_config(),
}
Expand Down Expand Up @@ -302,6 +346,8 @@ def test_run_args() -> None:
"--app",
"api",
"--no-proxy-headers",
"--ws",
"websockets",
],
)
assert result.exit_code == 0, result.output
Expand All @@ -316,6 +362,12 @@ def test_run_args() -> None:
"workers": 2,
"root_path": "/api",
"proxy_headers": False,
"ws": "websockets",
"ws_max_size": 16777216,
"ws_max_queue": 32,
"ws_ping_interval": 20.0,
"ws_ping_timeout": 20.0,
"ws_per_message_deflate": True,
"forwarded_allow_ips": None,
"log_config": get_uvicorn_log_config(),
}
Expand Down Expand Up @@ -348,6 +400,12 @@ def test_run_env_vars() -> None:
"workers": None,
"root_path": "",
"proxy_headers": True,
"ws": "auto",
"ws_max_size": 16777216,
"ws_max_queue": 32,
"ws_ping_interval": 20.0,
"ws_ping_timeout": 20.0,
"ws_per_message_deflate": True,
"forwarded_allow_ips": None,
"log_config": get_uvicorn_log_config(),
}
Expand Down Expand Up @@ -382,6 +440,12 @@ def test_run_env_vars_and_args() -> None:
"workers": None,
"root_path": "",
"proxy_headers": True,
"ws": "auto",
"ws_max_size": 16777216,
"ws_max_queue": 32,
"ws_ping_interval": 20.0,
"ws_ping_timeout": 20.0,
"ws_per_message_deflate": True,
"forwarded_allow_ips": None,
"log_config": get_uvicorn_log_config(),
}
Expand Down Expand Up @@ -495,6 +559,12 @@ def test_dev_with_import_string() -> None:
"workers": None,
"root_path": "",
"proxy_headers": True,
"ws": "auto",
"ws_max_size": 16777216,
"ws_max_queue": 32,
"ws_ping_interval": 20.0,
"ws_ping_timeout": 20.0,
"ws_per_message_deflate": True,
"log_config": get_uvicorn_log_config(),
}
assert "Using import string: single_file_app:api" in result.output
Expand All @@ -517,6 +587,12 @@ def test_run_with_import_string() -> None:
"workers": None,
"root_path": "",
"proxy_headers": True,
"ws": "auto",
"ws_max_size": 16777216,
"ws_max_queue": 32,
"ws_ping_interval": 20.0,
"ws_ping_timeout": 20.0,
"ws_per_message_deflate": True,
"log_config": get_uvicorn_log_config(),
}
assert "Using import string: single_file_app:app" in result.output
Expand Down