import logging import os import platform import ssl import sys import typing import click from asgiref.typing import ASGIApplication import uvicorn from uvicorn.config import ( HTTP_PROTOCOLS, INTERFACES, LIFESPAN, LOG_LEVELS, LOGGING_CONFIG, LOOP_SETUPS, SSL_PROTOCOL_VERSION, WS_PROTOCOLS, Config, ) 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 %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( "--debug", is_flag=True, default=False, help="Enable debug mode.", hidden=True ) @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 watchgod 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 watchgod 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 seperated 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( "--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: str, http: str, ws: str, ws_max_size: int, ws_ping_interval: float, ws_ping_timeout: float, ws_per_message_deflate: bool, lifespan: str, interface: str, debug: bool, 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, factory: bool, ) -> None: kwargs = { "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, "debug": debug, "reload": reload, "reload_dirs": reload_dirs if reload_dirs else None, "reload_includes": reload_includes if reload_includes else None, "reload_excludes": reload_excludes if reload_excludes else 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], "use_colors": use_colors, "factory": factory, "app_dir": app_dir, } run(app, **kwargs) def run(app: typing.Union[ASGIApplication, str], **kwargs: typing.Any) -> None: app_dir = kwargs.pop("app_dir", None) if app_dir is not None: sys.path.insert(0, app_dir) config = Config(app, **kwargs) 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: 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