from __future__ import annotations import asyncio import logging import os import platform import ssl import sys from typing import Any, Callable import click import uvicorn from uvicorn._types import ASGIApplication from uvicorn.config import ( HTTP_PROTOCOLS, INTERFACES, LIFESPAN, LOG_LEVELS, LOGGING_CONFIG, LOOP_SETUPS, SSL_PROTOCOL_VERSION, WS_PROTOCOLS, Config, HTTPProtocolType, InterfaceType, LifespanType, LoopSetupType, WSProtocolType, ) from uvicorn.server import Server, ServerState # noqa: F401 # Used to be defined here. from uvicorn.supervisors import ChangeReload, Multiprocess LEVEL_CHOICES = click.Choice(list(LOG_LEVELS.keys())) HTTP_CHOICES = click.Choice(list(HTTP_PROTOCOLS.keys())) WS_CHOICES = click.Choice(list(WS_PROTOCOLS.keys())) LIFESPAN_CHOICES = click.Choice(list(LIFESPAN.keys())) LOOP_CHOICES = click.Choice([key for key in LOOP_SETUPS.keys() if key != "none"]) INTERFACE_CHOICES = click.Choice(INTERFACES) STARTUP_FAILURE = 3 logger = logging.getLogger("uvicorn.error") def print_version(ctx: click.Context, param: click.Parameter, value: bool) -> None: if not value or ctx.resilient_parsing: return click.echo( "Running uvicorn {version} with {py_implementation} {py_version} on {system}".format( version=uvicorn.__version__, py_implementation=platform.python_implementation(), py_version=platform.python_version(), system=platform.system(), ) ) ctx.exit() @click.command(context_settings={"auto_envvar_prefix": "UVICORN"}) @click.argument("app", envvar="UVICORN_APP") @click.option( "--host", type=str, default="127.0.0.1", help="Bind socket to this host.", show_default=True, ) @click.option( "--port", type=int, default=8000, help="Bind socket to this port. If 0, an available port will be picked.", show_default=True, ) @click.option("--uds", type=str, default=None, help="Bind to a UNIX domain socket.") @click.option("--fd", type=int, default=None, help="Bind to socket from this file descriptor.") @click.option("--reload", is_flag=True, default=False, help="Enable auto-reload.") @click.option( "--reload-dir", "reload_dirs", multiple=True, help="Set reload directories explicitly, instead of using the current working" " directory.", type=click.Path(exists=True), ) @click.option( "--reload-include", "reload_includes", multiple=True, help="Set glob patterns to include while watching for files. Includes '*.py' " "by default; these defaults can be overridden with `--reload-exclude`. " "This option has no effect unless watchfiles is installed.", ) @click.option( "--reload-exclude", "reload_excludes", multiple=True, help="Set glob patterns to exclude while watching for files. Includes " "'.*, .py[cod], .sw.*, ~*' by default; these defaults can be overridden " "with `--reload-include`. This option has no effect unless watchfiles is " "installed.", ) @click.option( "--reload-delay", type=float, default=0.25, show_default=True, help="Delay between previous and next check if application needs to be." " Defaults to 0.25s.", ) @click.option( "--workers", default=None, type=int, help="Number of worker processes. Defaults to the $WEB_CONCURRENCY environment" " variable if available, or 1. Not valid with --reload.", ) @click.option( "--loop", type=LOOP_CHOICES, default="auto", help="Event loop implementation.", show_default=True, ) @click.option( "--http", type=HTTP_CHOICES, default="auto", help="HTTP protocol implementation.", show_default=True, ) @click.option( "--ws", type=WS_CHOICES, default="auto", help="WebSocket protocol implementation.", show_default=True, ) @click.option( "--ws-max-size", type=int, default=16777216, help="WebSocket max size message in bytes", show_default=True, ) @click.option( "--ws-max-queue", type=int, default=32, help="The maximum length of the WebSocket message queue.", show_default=True, ) @click.option( "--ws-ping-interval", type=float, default=20.0, help="WebSocket ping interval in seconds.", show_default=True, ) @click.option( "--ws-ping-timeout", type=float, default=20.0, help="WebSocket ping timeout in seconds.", show_default=True, ) @click.option( "--ws-per-message-deflate", type=bool, default=True, help="WebSocket per-message-deflate compression", show_default=True, ) @click.option( "--lifespan", type=LIFESPAN_CHOICES, default="auto", help="Lifespan implementation.", show_default=True, ) @click.option( "--interface", type=INTERFACE_CHOICES, default="auto", help="Select ASGI3, ASGI2, or WSGI as the application interface.", show_default=True, ) @click.option( "--env-file", type=click.Path(exists=True), default=None, help="Environment configuration file.", show_default=True, ) @click.option( "--log-config", type=click.Path(exists=True), default=None, help="Logging configuration file. Supported formats: .ini, .json, .yaml.", show_default=True, ) @click.option( "--log-level", type=LEVEL_CHOICES, default=None, help="Log level. [default: info]", show_default=True, ) @click.option( "--access-log/--no-access-log", is_flag=True, default=True, help="Enable/Disable access log.", ) @click.option( "--use-colors/--no-use-colors", is_flag=True, default=None, help="Enable/Disable colorized logging.", ) @click.option( "--proxy-headers/--no-proxy-headers", is_flag=True, default=True, help="Enable/Disable X-Forwarded-Proto, X-Forwarded-For, X-Forwarded-Port to " "populate remote address info.", ) @click.option( "--server-header/--no-server-header", is_flag=True, default=True, help="Enable/Disable default Server header.", ) @click.option( "--date-header/--no-date-header", is_flag=True, default=True, help="Enable/Disable default Date header.", ) @click.option( "--forwarded-allow-ips", type=str, default=None, help="Comma separated list of IPs to trust with proxy headers. Defaults to" " the $FORWARDED_ALLOW_IPS environment variable if available, or '127.0.0.1'.", ) @click.option( "--root-path", type=str, default="", help="Set the ASGI 'root_path' for applications submounted below a given URL path.", ) @click.option( "--limit-concurrency", type=int, default=None, help="Maximum number of concurrent connections or tasks to allow, before issuing" " HTTP 503 responses.", ) @click.option( "--backlog", type=int, default=2048, help="Maximum number of connections to hold in backlog", ) @click.option( "--limit-max-requests", type=int, default=None, help="Maximum number of requests to service before terminating the process.", ) @click.option( "--timeout-keep-alive", type=int, default=5, help="Close Keep-Alive connections if no new data is received within this timeout.", show_default=True, ) @click.option( "--timeout-graceful-shutdown", type=int, default=None, help="Maximum number of seconds to wait for graceful shutdown.", ) @click.option("--ssl-keyfile", type=str, default=None, help="SSL key file", show_default=True) @click.option( "--ssl-certfile", type=str, default=None, help="SSL certificate file", show_default=True, ) @click.option( "--ssl-keyfile-password", type=str, default=None, help="SSL keyfile password", show_default=True, ) @click.option( "--ssl-version", type=int, default=int(SSL_PROTOCOL_VERSION), help="SSL version to use (see stdlib ssl module's)", show_default=True, ) @click.option( "--ssl-cert-reqs", type=int, default=int(ssl.CERT_NONE), help="Whether client certificate is required (see stdlib ssl module's)", show_default=True, ) @click.option( "--ssl-ca-certs", type=str, default=None, help="CA certificates file", show_default=True, ) @click.option( "--ssl-ciphers", type=str, default="TLSv1", help="Ciphers to use (see stdlib ssl module's)", show_default=True, ) @click.option( "--header", "headers", multiple=True, help="Specify custom default HTTP response headers as a Name:Value pair", ) @click.option( "--version", is_flag=True, callback=print_version, expose_value=False, is_eager=True, help="Display the uvicorn version and exit.", ) @click.option( "--app-dir", default="", show_default=True, help="Look for APP in the specified directory, by adding this to the PYTHONPATH." " Defaults to the current working directory.", ) @click.option( "--h11-max-incomplete-event-size", "h11_max_incomplete_event_size", type=int, default=None, help="For h11, the maximum number of bytes to buffer of an incomplete event.", ) @click.option( "--factory", is_flag=True, default=False, help="Treat APP as an application factory, i.e. a () -> callable.", show_default=True, ) def main( app: str, host: str, port: int, uds: str, fd: int, loop: LoopSetupType, http: HTTPProtocolType, ws: WSProtocolType, ws_max_size: int, ws_max_queue: int, ws_ping_interval: float, ws_ping_timeout: float, ws_per_message_deflate: bool, lifespan: LifespanType, interface: InterfaceType, reload: bool, reload_dirs: list[str], reload_includes: list[str], reload_excludes: list[str], reload_delay: float, workers: int, env_file: str, log_config: str, log_level: str, access_log: bool, proxy_headers: bool, server_header: bool, date_header: bool, forwarded_allow_ips: str, root_path: str, limit_concurrency: int, backlog: int, limit_max_requests: int, timeout_keep_alive: int, timeout_graceful_shutdown: int | None, ssl_keyfile: str, ssl_certfile: str, ssl_keyfile_password: str, ssl_version: int, ssl_cert_reqs: int, ssl_ca_certs: str, ssl_ciphers: str, headers: list[str], use_colors: bool, app_dir: str, h11_max_incomplete_event_size: int | None, factory: bool, ) -> None: run( app, host=host, port=port, uds=uds, fd=fd, loop=loop, http=http, 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, lifespan=lifespan, env_file=env_file, log_config=LOGGING_CONFIG if log_config is None else log_config, log_level=log_level, access_log=access_log, interface=interface, reload=reload, reload_dirs=reload_dirs or None, reload_includes=reload_includes or None, reload_excludes=reload_excludes or None, reload_delay=reload_delay, workers=workers, proxy_headers=proxy_headers, server_header=server_header, date_header=date_header, forwarded_allow_ips=forwarded_allow_ips, root_path=root_path, limit_concurrency=limit_concurrency, backlog=backlog, limit_max_requests=limit_max_requests, timeout_keep_alive=timeout_keep_alive, timeout_graceful_shutdown=timeout_graceful_shutdown, ssl_keyfile=ssl_keyfile, ssl_certfile=ssl_certfile, ssl_keyfile_password=ssl_keyfile_password, ssl_version=ssl_version, ssl_cert_reqs=ssl_cert_reqs, ssl_ca_certs=ssl_ca_certs, ssl_ciphers=ssl_ciphers, headers=[header.split(":", 1) for header in headers], # type: ignore[misc] use_colors=use_colors, factory=factory, app_dir=app_dir, h11_max_incomplete_event_size=h11_max_incomplete_event_size, ) def run( app: ASGIApplication | Callable[..., Any] | str, *, host: str = "127.0.0.1", port: int = 8000, uds: str | None = None, fd: int | None = None, loop: LoopSetupType = "auto", http: type[asyncio.Protocol] | HTTPProtocolType = "auto", ws: type[asyncio.Protocol] | WSProtocolType = "auto", ws_max_size: int = 16777216, ws_max_queue: int = 32, ws_ping_interval: float | None = 20.0, ws_ping_timeout: float | None = 20.0, ws_per_message_deflate: bool = True, lifespan: LifespanType = "auto", interface: InterfaceType = "auto", reload: bool = False, reload_dirs: list[str] | str | None = None, reload_includes: list[str] | str | None = None, reload_excludes: list[str] | str | None = None, reload_delay: float = 0.25, workers: int | None = None, env_file: str | os.PathLike[str] | None = None, log_config: dict[str, Any] | str | None = LOGGING_CONFIG, log_level: str | int | None = None, access_log: bool = True, proxy_headers: bool = True, server_header: bool = True, date_header: bool = True, forwarded_allow_ips: list[str] | str | None = None, root_path: str = "", limit_concurrency: int | None = None, backlog: int = 2048, limit_max_requests: int | None = None, timeout_keep_alive: int = 5, timeout_graceful_shutdown: int | None = None, ssl_keyfile: str | None = None, ssl_certfile: str | os.PathLike[str] | None = None, ssl_keyfile_password: str | None = None, ssl_version: int = SSL_PROTOCOL_VERSION, ssl_cert_reqs: int = ssl.CERT_NONE, ssl_ca_certs: str | None = None, ssl_ciphers: str = "TLSv1", headers: list[tuple[str, str]] | None = None, use_colors: bool | None = None, app_dir: str | None = None, factory: bool = False, h11_max_incomplete_event_size: int | None = None, ) -> None: if app_dir is not None: sys.path.insert(0, app_dir) config = Config( app, host=host, port=port, uds=uds, fd=fd, loop=loop, http=http, 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, lifespan=lifespan, interface=interface, reload=reload, reload_dirs=reload_dirs, reload_includes=reload_includes, reload_excludes=reload_excludes, reload_delay=reload_delay, workers=workers, env_file=env_file, log_config=log_config, log_level=log_level, access_log=access_log, proxy_headers=proxy_headers, server_header=server_header, date_header=date_header, forwarded_allow_ips=forwarded_allow_ips, root_path=root_path, limit_concurrency=limit_concurrency, backlog=backlog, limit_max_requests=limit_max_requests, timeout_keep_alive=timeout_keep_alive, timeout_graceful_shutdown=timeout_graceful_shutdown, ssl_keyfile=ssl_keyfile, ssl_certfile=ssl_certfile, ssl_keyfile_password=ssl_keyfile_password, ssl_version=ssl_version, ssl_cert_reqs=ssl_cert_reqs, ssl_ca_certs=ssl_ca_certs, ssl_ciphers=ssl_ciphers, headers=headers, use_colors=use_colors, factory=factory, h11_max_incomplete_event_size=h11_max_incomplete_event_size, ) server = Server(config=config) if (config.reload or config.workers > 1) and not isinstance(app, str): logger = logging.getLogger("uvicorn.error") logger.warning("You must pass the application as an import string to enable 'reload' or " "'workers'.") sys.exit(1) if config.should_reload: sock = config.bind_socket() ChangeReload(config, target=server.run, sockets=[sock]).run() elif config.workers > 1: sock = config.bind_socket() Multiprocess(config, target=server.run, sockets=[sock]).run() else: server.run() if config.uds and os.path.exists(config.uds): os.remove(config.uds) # pragma: py-win32 if not server.started and not config.should_reload and config.workers == 1: sys.exit(STARTUP_FAILURE) if __name__ == "__main__": main() # pragma: no cover