import asyncio import logging import os import platform import ssl import sys import typing import click from h11._connection import DEFAULT_MAX_INCOMPLETE_EVENT_SIZE import uvicorn 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 if typing.TYPE_CHECKING: from asgiref.typing import ASGIApplication 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 %s with %s %s on %s" % ( uvicorn.__version__, platform.python_implementation(), platform.python_version(), platform.system(), ) ) ctx.exit() @click.command(context_settings={"auto_envvar_prefix": "UVICORN"}) @click.argument("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.", 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-ping-interval", type=float, default=20.0, help="WebSocket ping interval", show_default=True, ) @click.option( "--ws-ping-timeout", type=float, default=20.0, help="WebSocket ping timeout", 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( "--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=DEFAULT_MAX_INCOMPLETE_EVENT_SIZE, 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_ping_interval: float, ws_ping_timeout: float, ws_per_message_deflate: bool, lifespan: LifespanType, interface: InterfaceType, reload: bool, reload_dirs: typing.List[str], reload_includes: typing.List[str], reload_excludes: typing.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, 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: typing.List[str], use_colors: bool, app_dir: str, h11_max_incomplete_event_size: int, 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_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, 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: typing.Union["ASGIApplication", typing.Callable, str], *, host: str = "127.0.0.1", port: int = 8000, uds: typing.Optional[str] = None, fd: typing.Optional[int] = None, loop: LoopSetupType = "auto", http: typing.Union[typing.Type[asyncio.Protocol], HTTPProtocolType] = "auto", ws: typing.Union[typing.Type[asyncio.Protocol], WSProtocolType] = "auto", ws_max_size: int = 16777216, ws_ping_interval: typing.Optional[float] = 20.0, ws_ping_timeout: typing.Optional[float] = 20.0, ws_per_message_deflate: bool = True, lifespan: LifespanType = "auto", interface: InterfaceType = "auto", reload: bool = False, reload_dirs: typing.Optional[typing.Union[typing.List[str], str]] = None, reload_includes: typing.Optional[typing.Union[typing.List[str], str]] = None, reload_excludes: typing.Optional[typing.Union[typing.List[str], str]] = None, reload_delay: float = 0.25, workers: typing.Optional[int] = None, env_file: typing.Optional[typing.Union[str, os.PathLike]] = None, log_config: typing.Optional[ typing.Union[typing.Dict[str, typing.Any], str] ] = LOGGING_CONFIG, log_level: typing.Optional[typing.Union[str, int]] = None, access_log: bool = True, proxy_headers: bool = True, server_header: bool = True, date_header: bool = True, forwarded_allow_ips: typing.Optional[typing.Union[typing.List[str], str]] = None, root_path: str = "", limit_concurrency: typing.Optional[int] = None, backlog: int = 2048, limit_max_requests: typing.Optional[int] = None, timeout_keep_alive: int = 5, ssl_keyfile: typing.Optional[str] = None, ssl_certfile: typing.Optional[typing.Union[str, os.PathLike]] = None, ssl_keyfile_password: typing.Optional[str] = None, ssl_version: int = SSL_PROTOCOL_VERSION, ssl_cert_reqs: int = ssl.CERT_NONE, ssl_ca_certs: typing.Optional[str] = None, ssl_ciphers: str = "TLSv1", headers: typing.Optional[typing.List[typing.Tuple[str, str]]] = None, use_colors: typing.Optional[bool] = None, app_dir: typing.Optional[str] = None, factory: bool = False, h11_max_incomplete_event_size: int = DEFAULT_MAX_INCOMPLETE_EVENT_SIZE, ) -> 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_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, 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