from __future__ import annotations from argparse import ArgumentParser, _ArgumentGroup from typing import List, Optional, Type, Union from sanic_routing import __version__ as __routing_version__ from sanic import __version__ from sanic.http.constants import HTTP class Group: name: Optional[str] container: Union[ArgumentParser, _ArgumentGroup] _registry: List[Type[Group]] = [] def __init_subclass__(cls) -> None: Group._registry.append(cls) def __init__(self, parser: ArgumentParser, title: Optional[str]): self.parser = parser if title: self.container = self.parser.add_argument_group(title=f" {title}") else: self.container = self.parser @classmethod def create(cls, parser: ArgumentParser): instance = cls(parser, cls.name) return instance def add_bool_arguments(self, *args, nullable=False, **kwargs): group = self.container.add_mutually_exclusive_group() kwargs["help"] = kwargs["help"].capitalize() group.add_argument(*args, action="store_true", **kwargs) kwargs["help"] = f"no {kwargs['help'].lower()}".capitalize() group.add_argument( "--no-" + args[0][2:], *args[1:], action="store_false", **kwargs ) if nullable: params = {args[0][2:].replace("-", "_"): None} group.set_defaults(**params) def prepare(self, args) -> None: ... class GeneralGroup(Group): name = None def attach(self): self.container.add_argument( "--version", action="version", version=f"Sanic {__version__}; Routing {__routing_version__}", ) self.container.add_argument( "target", help=( "Path to your Sanic app instance.\n" "\tExample: path.to.server:app\n" "If running a Simple Server, path to directory to serve.\n" "\tExample: ./\n" "Additionally, this can be a path to a factory function\n" "that returns a Sanic app instance.\n" "\tExample: path.to.server:create_app\n" ), ) class ApplicationGroup(Group): name = "Application" def attach(self): group = self.container.add_mutually_exclusive_group() group.add_argument( "--factory", action="store_true", help=( "Treat app as an application factory, " "i.e. a () -> callable" ), ) group.add_argument( "-s", "--simple", dest="simple", action="store_true", help=( "Run Sanic as a Simple Server, and serve the contents of " "a directory\n(module arg should be a path)" ), ) class HTTPVersionGroup(Group): name = "HTTP version" def attach(self): http_values = [http.value for http in HTTP.__members__.values()] self.container.add_argument( "--http", dest="http", action="append", choices=http_values, type=int, help=( "Which HTTP version to use: HTTP/1.1 or HTTP/3. Value should\n" "be either 1, or 3. [default 1]" ), ) self.container.add_argument( "-1", dest="http", action="append_const", const=1, help=("Run Sanic server using HTTP/1.1"), ) self.container.add_argument( "-3", dest="http", action="append_const", const=3, help=("Run Sanic server using HTTP/3"), ) def prepare(self, args): if not args.http: args.http = [1] args.http = tuple(sorted(set(map(HTTP, args.http)), reverse=True)) class SocketGroup(Group): name = "Socket binding" def attach(self): self.container.add_argument( "-H", "--host", dest="host", type=str, help="Host address [default 127.0.0.1]", ) self.container.add_argument( "-p", "--port", dest="port", type=int, help="Port to serve on [default 8000]", ) self.container.add_argument( "-u", "--unix", dest="unix", type=str, default="", help="location of unix socket", ) class TLSGroup(Group): name = "TLS certificate" def attach(self): self.container.add_argument( "--cert", dest="cert", type=str, help="Location of fullchain.pem, bundle.crt or equivalent", ) self.container.add_argument( "--key", dest="key", type=str, help="Location of privkey.pem or equivalent .key file", ) self.container.add_argument( "--tls", metavar="DIR", type=str, action="append", help=( "TLS certificate folder with fullchain.pem and privkey.pem\n" "May be specified multiple times to choose multiple " "certificates" ), ) self.container.add_argument( "--tls-strict-host", dest="tlshost", action="store_true", help="Only allow clients that send an SNI matching server certs", ) class WorkerGroup(Group): name = "Worker" def attach(self): group = self.container.add_mutually_exclusive_group() group.add_argument( "-w", "--workers", dest="workers", type=int, default=1, help="Number of worker processes [default 1]", ) group.add_argument( "--fast", dest="fast", action="store_true", help="Set the number of workers to max allowed", ) group.add_argument( "--single-process", dest="single", action="store_true", help="Do not use multiprocessing, run server in a single process", ) self.add_bool_arguments( "--access-logs", dest="access_log", help="display access logs", default=None, ) class DevelopmentGroup(Group): name = "Development" def attach(self): self.container.add_argument( "--debug", dest="debug", action="store_true", help="Run the server in debug mode", ) self.container.add_argument( "-r", "--reload", "--auto-reload", dest="auto_reload", action="store_true", help=( "Watch source directory for file changes and reload on " "changes" ), ) self.container.add_argument( "-R", "--reload-dir", dest="path", action="append", help="Extra directories to watch and reload on changes", ) self.container.add_argument( "-d", "--dev", dest="dev", action="store_true", help=("debug + auto reload"), ) self.container.add_argument( "--auto-tls", dest="auto_tls", action="store_true", help=( "Create a temporary TLS certificate for local development " "(requires mkcert or trustme)" ), ) class OutputGroup(Group): name = "Output" def attach(self): self.add_bool_arguments( "--coffee", dest="coffee", default=False, help="Uhm, coffee?", ) self.add_bool_arguments( "--motd", dest="motd", default=True, help="Show the startup display", ) self.container.add_argument( "-v", "--verbosity", action="count", help="Control logging noise, eg. -vv or --verbosity=2 [default 0]", ) self.add_bool_arguments( "--noisy-exceptions", dest="noisy_exceptions", help="Output stack traces for all exceptions", )