config.py 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601
  1. from __future__ import annotations
  2. import asyncio
  3. import inspect
  4. import json
  5. import logging
  6. import logging.config
  7. import os
  8. import socket
  9. import ssl
  10. import sys
  11. from collections.abc import Awaitable, Callable
  12. from configparser import RawConfigParser
  13. from pathlib import Path
  14. from typing import IO, Any, Literal
  15. from uvicorn._ansi import style
  16. from uvicorn._compat import iscoroutinefunction
  17. from uvicorn._types import ASGIApplication
  18. from uvicorn.importer import ImportFromStringError, import_from_string
  19. from uvicorn.logging import TRACE_LOG_LEVEL
  20. from uvicorn.middleware.asgi2 import ASGI2Middleware
  21. from uvicorn.middleware.message_logger import MessageLoggerMiddleware
  22. from uvicorn.middleware.proxy_headers import ProxyHeadersMiddleware
  23. from uvicorn.middleware.wsgi import WSGIMiddleware
  24. class UvicornDeprecationWarning(UserWarning):
  25. """A custom deprecation warning for Uvicorn.
  26. Unlike the built-in DeprecationWarning, this inherits from UserWarning to ensure it is visible by default, helping
  27. users discover deprecated features without needing to enable warnings explicitly.
  28. Reference: https://sethmlarson.dev/deprecations-via-warnings-dont-work-for-python-libraries
  29. """
  30. HTTPProtocolType = Literal["auto", "h11", "httptools", "zttp"]
  31. WSProtocolType = Literal["auto", "none", "websockets", "websockets-sansio", "wsproto"]
  32. LifespanType = Literal["auto", "on", "off"]
  33. LoopFactoryType = Literal["none", "auto", "asyncio", "uvloop"]
  34. InterfaceType = Literal["auto", "asgi3", "asgi2", "wsgi"]
  35. LOG_LEVELS: dict[str, int] = {
  36. "critical": logging.CRITICAL,
  37. "error": logging.ERROR,
  38. "warning": logging.WARNING,
  39. "info": logging.INFO,
  40. "debug": logging.DEBUG,
  41. "trace": TRACE_LOG_LEVEL,
  42. }
  43. HTTP_PROTOCOLS: dict[str, str] = {
  44. "auto": "uvicorn.protocols.http.auto:AutoHTTPProtocol",
  45. "h11": "uvicorn.protocols.http.h11_impl:H11Protocol",
  46. "httptools": "uvicorn.protocols.http.httptools_impl:HttpToolsProtocol",
  47. "zttp": "uvicorn.protocols.http.zttp_impl:ZttpProtocol",
  48. }
  49. WS_PROTOCOLS: dict[str, str | None] = {
  50. "auto": "uvicorn.protocols.websockets.auto:AutoWebSocketsProtocol",
  51. "none": None,
  52. "websockets": "uvicorn.protocols.websockets.websockets_impl:WebSocketProtocol",
  53. "websockets-sansio": "uvicorn.protocols.websockets.websockets_sansio_impl:WebSocketsSansIOProtocol",
  54. "wsproto": "uvicorn.protocols.websockets.wsproto_impl:WSProtocol",
  55. }
  56. LIFESPAN: dict[str, str] = {
  57. "auto": "uvicorn.lifespan.on:LifespanOn",
  58. "on": "uvicorn.lifespan.on:LifespanOn",
  59. "off": "uvicorn.lifespan.off:LifespanOff",
  60. }
  61. LOOP_FACTORIES: dict[str, str | None] = {
  62. "none": None,
  63. "auto": "uvicorn.loops.auto:auto_loop_factory",
  64. "asyncio": "uvicorn.loops.asyncio:asyncio_loop_factory",
  65. "uvloop": "uvicorn.loops.uvloop:uvloop_loop_factory",
  66. }
  67. INTERFACES: list[InterfaceType] = ["auto", "asgi3", "asgi2", "wsgi"]
  68. SSL_PROTOCOL_VERSION: int = ssl.PROTOCOL_TLS_SERVER
  69. STARTUP_FAILURE = 3
  70. LOGGING_CONFIG: dict[str, Any] = {
  71. "version": 1,
  72. "disable_existing_loggers": False,
  73. "formatters": {
  74. "default": {
  75. "()": "uvicorn.logging.DefaultFormatter",
  76. "fmt": "%(levelprefix)s %(message)s",
  77. "use_colors": None,
  78. },
  79. "access": {
  80. "()": "uvicorn.logging.AccessFormatter",
  81. "fmt": '%(levelprefix)s %(client_addr)s - "%(request_line)s" %(status_code)s', # noqa: E501
  82. },
  83. },
  84. "handlers": {
  85. "default": {
  86. "formatter": "default",
  87. "class": "logging.StreamHandler",
  88. "stream": "ext://sys.stderr",
  89. },
  90. "access": {
  91. "formatter": "access",
  92. "class": "logging.StreamHandler",
  93. "stream": "ext://sys.stdout",
  94. },
  95. },
  96. "loggers": {
  97. "uvicorn": {"handlers": ["default"], "level": "INFO", "propagate": False},
  98. "uvicorn.error": {"level": "INFO"},
  99. "uvicorn.access": {"handlers": ["access"], "level": "INFO", "propagate": False},
  100. },
  101. }
  102. logger = logging.getLogger("uvicorn.error")
  103. def create_ssl_context(
  104. certfile: str | os.PathLike[str],
  105. keyfile: str | os.PathLike[str] | None,
  106. password: str | None,
  107. ssl_version: int,
  108. cert_reqs: int,
  109. ca_certs: str | os.PathLike[str] | None,
  110. ciphers: str | None,
  111. ) -> ssl.SSLContext:
  112. ctx = ssl.SSLContext(ssl_version)
  113. get_password = (lambda: password) if password else None
  114. ctx.load_cert_chain(certfile, keyfile, get_password)
  115. ctx.verify_mode = ssl.VerifyMode(cert_reqs)
  116. if ca_certs:
  117. ctx.load_verify_locations(ca_certs)
  118. if ciphers:
  119. ctx.set_ciphers(ciphers)
  120. return ctx
  121. def is_dir(path: Path) -> bool:
  122. try:
  123. if not path.is_absolute():
  124. path = path.resolve()
  125. return path.is_dir()
  126. except OSError: # pragma: full coverage
  127. return False
  128. def resolve_reload_patterns(patterns_list: list[str], directories_list: list[str]) -> tuple[list[str], list[Path]]:
  129. directories: list[Path] = list(set(map(Path, directories_list.copy())))
  130. patterns: list[str] = patterns_list.copy()
  131. current_working_directory = Path.cwd()
  132. for pattern in patterns_list:
  133. # Special case for the .* pattern, otherwise this would only match
  134. # hidden directories which is probably undesired
  135. if pattern == ".*":
  136. continue # pragma: py-not-linux
  137. patterns.append(pattern)
  138. if is_dir(Path(pattern)):
  139. directories.append(Path(pattern))
  140. else:
  141. for match in current_working_directory.glob(pattern):
  142. if is_dir(match):
  143. directories.append(match)
  144. directories = list(set(directories))
  145. directories = list(map(Path, directories))
  146. directories = list(map(lambda x: x.resolve(), directories))
  147. directories = list({reload_path for reload_path in directories if is_dir(reload_path)})
  148. children = []
  149. for j in range(len(directories)):
  150. for k in range(j + 1, len(directories)): # pragma: full coverage
  151. if directories[j] in directories[k].parents:
  152. children.append(directories[k])
  153. elif directories[k] in directories[j].parents:
  154. children.append(directories[j])
  155. directories = list(set(directories).difference(set(children)))
  156. return list(set(patterns)), directories
  157. def _normalize_dirs(dirs: list[str] | str | None) -> list[str]:
  158. if dirs is None:
  159. return []
  160. if isinstance(dirs, str):
  161. return [dirs]
  162. return list(set(dirs))
  163. class Config:
  164. def __init__(
  165. self,
  166. app: ASGIApplication | Callable[..., Any] | str,
  167. host: str = "127.0.0.1",
  168. port: int = 8000,
  169. uds: str | None = None,
  170. fd: int | None = None,
  171. loop: LoopFactoryType | str = "auto",
  172. http: type[asyncio.Protocol] | HTTPProtocolType | str = "auto",
  173. ws: type[asyncio.Protocol] | WSProtocolType | str = "auto",
  174. ws_max_size: int = 16 * 1024 * 1024,
  175. ws_max_queue: int = 32,
  176. ws_ping_interval: float | None = 20.0,
  177. ws_ping_timeout: float | None = 20.0,
  178. ws_per_message_deflate: bool = True,
  179. lifespan: LifespanType = "auto",
  180. env_file: str | os.PathLike[str] | None = None,
  181. log_config: dict[str, Any] | str | os.PathLike[str] | RawConfigParser | IO[Any] | None = LOGGING_CONFIG,
  182. log_level: str | int | None = None,
  183. access_log: bool = True,
  184. use_colors: bool | None = None,
  185. interface: InterfaceType = "auto",
  186. reload: bool = False,
  187. reload_dirs: list[str] | str | None = None,
  188. reload_delay: float = 0.25,
  189. reload_includes: list[str] | str | None = None,
  190. reload_excludes: list[str] | str | None = None,
  191. workers: int | None = None,
  192. proxy_headers: bool = True,
  193. server_header: bool = True,
  194. date_header: bool = True,
  195. forwarded_allow_ips: list[str] | str | None = None,
  196. root_path: str = "",
  197. limit_concurrency: int | None = None,
  198. limit_max_requests: int | None = None,
  199. limit_max_requests_jitter: int = 0,
  200. backlog: int = 2048,
  201. timeout_keep_alive: int = 5,
  202. timeout_notify: int = 30,
  203. timeout_graceful_shutdown: int | None = None,
  204. timeout_worker_healthcheck: int = 5,
  205. callback_notify: Callable[..., Awaitable[None]] | None = None,
  206. ssl_keyfile: str | os.PathLike[str] | None = None,
  207. ssl_certfile: str | os.PathLike[str] | None = None,
  208. ssl_keyfile_password: str | None = None,
  209. ssl_version: int = SSL_PROTOCOL_VERSION,
  210. ssl_cert_reqs: int = ssl.CERT_NONE,
  211. ssl_ca_certs: str | os.PathLike[str] | None = None,
  212. ssl_ciphers: str | None = None,
  213. ssl_context_factory: Callable[[Config, Callable[[], ssl.SSLContext]], ssl.SSLContext] | None = None,
  214. headers: list[tuple[str, str]] | None = None,
  215. factory: bool = False,
  216. h11_max_incomplete_event_size: int | None = None,
  217. reset_contextvars: bool = False,
  218. ):
  219. self.app = app
  220. self.host = host
  221. self.port = port
  222. self.uds = uds
  223. self.fd = fd
  224. self.loop = loop
  225. self.http = http
  226. self.ws = ws
  227. self.ws_max_size = ws_max_size
  228. self.ws_max_queue = ws_max_queue
  229. self.ws_ping_interval = ws_ping_interval
  230. self.ws_ping_timeout = ws_ping_timeout
  231. self.ws_per_message_deflate = ws_per_message_deflate
  232. self.lifespan = lifespan
  233. self.log_config = log_config
  234. self.log_level = log_level
  235. self.access_log = access_log
  236. self.use_colors = use_colors
  237. self.interface = interface
  238. self.reload = reload
  239. self.reload_delay = reload_delay
  240. self.workers = workers or 1
  241. self.proxy_headers = proxy_headers
  242. self.server_header = server_header
  243. self.date_header = date_header
  244. self.root_path = root_path
  245. self.limit_concurrency = limit_concurrency
  246. self.limit_max_requests = limit_max_requests
  247. self.limit_max_requests_jitter = limit_max_requests_jitter
  248. self.backlog = backlog
  249. self.timeout_keep_alive = timeout_keep_alive
  250. self.timeout_notify = timeout_notify
  251. self.timeout_graceful_shutdown = timeout_graceful_shutdown
  252. self.timeout_worker_healthcheck = timeout_worker_healthcheck
  253. self.callback_notify = callback_notify
  254. self.ssl_keyfile = ssl_keyfile
  255. self.ssl_certfile = ssl_certfile
  256. self.ssl_keyfile_password = ssl_keyfile_password
  257. self.ssl_version = ssl_version
  258. self.ssl_cert_reqs = ssl_cert_reqs
  259. self.ssl_ca_certs = ssl_ca_certs
  260. self.ssl_ciphers = ssl_ciphers
  261. self.ssl_context_factory = ssl_context_factory
  262. self.headers: list[tuple[str, str]] = headers or []
  263. self.encoded_headers: list[tuple[bytes, bytes]] = []
  264. self.factory = factory
  265. self.h11_max_incomplete_event_size = h11_max_incomplete_event_size
  266. self.reset_contextvars = reset_contextvars
  267. self.loaded = False
  268. self.configure_logging()
  269. self.reload_dirs: list[Path] = []
  270. self.reload_dirs_excludes: list[Path] = []
  271. self.reload_includes: list[str] = []
  272. self.reload_excludes: list[str] = []
  273. if (reload_dirs or reload_includes or reload_excludes) and not self.should_reload:
  274. logger.warning(
  275. "Current configuration will not reload as not all conditions are met, please refer to documentation."
  276. )
  277. if self.should_reload:
  278. reload_dirs = _normalize_dirs(reload_dirs)
  279. reload_includes = _normalize_dirs(reload_includes)
  280. reload_excludes = _normalize_dirs(reload_excludes)
  281. self.reload_includes, self.reload_dirs = resolve_reload_patterns(reload_includes, reload_dirs)
  282. self.reload_excludes, self.reload_dirs_excludes = resolve_reload_patterns(reload_excludes, [])
  283. reload_dirs_tmp = self.reload_dirs.copy()
  284. for directory in self.reload_dirs_excludes:
  285. for reload_directory in reload_dirs_tmp:
  286. if directory == reload_directory or directory in reload_directory.parents:
  287. try:
  288. self.reload_dirs.remove(reload_directory)
  289. except ValueError: # pragma: full coverage
  290. pass
  291. for pattern in self.reload_excludes:
  292. if pattern in self.reload_includes:
  293. self.reload_includes.remove(pattern) # pragma: full coverage
  294. if not self.reload_dirs:
  295. if reload_dirs:
  296. logger.warning(
  297. "Provided reload directories %s did not contain valid "
  298. + "directories, watching current working directory.",
  299. reload_dirs,
  300. )
  301. self.reload_dirs = [Path.cwd()]
  302. logger.info(
  303. "Will watch for changes in these directories: %s",
  304. sorted(list(map(str, self.reload_dirs))),
  305. )
  306. if env_file is not None:
  307. from dotenv import load_dotenv
  308. logger.info("Loading environment from '%s'", env_file)
  309. load_dotenv(dotenv_path=env_file)
  310. if workers is None and "WEB_CONCURRENCY" in os.environ:
  311. self.workers = int(os.environ["WEB_CONCURRENCY"])
  312. self.forwarded_allow_ips: list[str] | str
  313. if forwarded_allow_ips is None:
  314. self.forwarded_allow_ips = os.environ.get("FORWARDED_ALLOW_IPS", "127.0.0.1")
  315. else:
  316. self.forwarded_allow_ips = forwarded_allow_ips # pragma: full coverage
  317. if self.reload and self.workers > 1:
  318. logger.warning('"workers" flag is ignored when reloading is enabled.')
  319. @property
  320. def asgi_version(self) -> Literal["2.0", "3.0"]:
  321. mapping: dict[str, Literal["2.0", "3.0"]] = {
  322. "asgi2": "2.0",
  323. "asgi3": "3.0",
  324. "wsgi": "3.0",
  325. }
  326. return mapping[self.interface]
  327. @property
  328. def is_ssl(self) -> bool:
  329. return bool(self.ssl_keyfile or self.ssl_certfile or self.ssl_context_factory)
  330. @property
  331. def use_subprocess(self) -> bool:
  332. return bool(self.reload or self.workers > 1)
  333. def configure_logging(self) -> None:
  334. logging.addLevelName(TRACE_LOG_LEVEL, "TRACE")
  335. if self.log_config is not None:
  336. if isinstance(self.log_config, os.PathLike):
  337. self.log_config = os.fspath(self.log_config)
  338. if isinstance(self.log_config, dict):
  339. if self.use_colors in (True, False):
  340. self.log_config["formatters"]["default"]["use_colors"] = self.use_colors
  341. self.log_config["formatters"]["access"]["use_colors"] = self.use_colors
  342. logging.config.dictConfig(self.log_config)
  343. elif isinstance(self.log_config, str) and self.log_config.endswith(".json"):
  344. with open(self.log_config) as file:
  345. loaded_config = json.load(file)
  346. logging.config.dictConfig(loaded_config)
  347. elif isinstance(self.log_config, str) and self.log_config.endswith((".yaml", ".yml")):
  348. try:
  349. import yaml
  350. except ImportError as e:
  351. raise ImportError(
  352. "Install the PyYAML package or uvicorn[standard] to use `--log-config` with YAML files."
  353. ) from e
  354. with open(self.log_config) as file:
  355. loaded_config = yaml.safe_load(file)
  356. logging.config.dictConfig(loaded_config)
  357. else:
  358. # See the note about fileConfig() here:
  359. # https://docs.python.org/3/library/logging.config.html#configuration-file-format
  360. logging.config.fileConfig(self.log_config, disable_existing_loggers=False)
  361. if self.log_level is not None:
  362. if isinstance(self.log_level, str):
  363. log_level = LOG_LEVELS[self.log_level.lower()]
  364. else:
  365. log_level = self.log_level
  366. logging.getLogger("uvicorn.error").setLevel(log_level)
  367. logging.getLogger("uvicorn.access").setLevel(log_level)
  368. logging.getLogger("uvicorn.asgi").setLevel(log_level)
  369. if self.access_log is False:
  370. logging.getLogger("uvicorn.access").handlers = []
  371. logging.getLogger("uvicorn.access").propagate = False
  372. def load_app(self) -> Any:
  373. """Import the app and return it. Exits on failure."""
  374. try:
  375. return import_from_string(self.app)
  376. except ImportFromStringError as exc:
  377. logger.error("Error loading ASGI app. %s" % exc)
  378. sys.exit(STARTUP_FAILURE)
  379. def load(self) -> None:
  380. assert not self.loaded
  381. if self.ssl_context_factory is not None:
  382. def default_factory() -> ssl.SSLContext:
  383. if not self.ssl_certfile:
  384. raise RuntimeError(
  385. "`default_ssl_context_factory()` requires `ssl_certfile` to be set on `Config`. "
  386. "Either pass `ssl_certfile` (and optionally `ssl_keyfile`) or build the `SSLContext` "
  387. "directly inside `ssl_context_factory` without calling the default factory."
  388. )
  389. return create_ssl_context(
  390. keyfile=self.ssl_keyfile,
  391. certfile=self.ssl_certfile,
  392. password=self.ssl_keyfile_password,
  393. ssl_version=self.ssl_version,
  394. cert_reqs=self.ssl_cert_reqs,
  395. ca_certs=self.ssl_ca_certs,
  396. ciphers=self.ssl_ciphers,
  397. )
  398. context = self.ssl_context_factory(self, default_factory)
  399. if not isinstance(context, ssl.SSLContext):
  400. raise TypeError(f"`ssl_context_factory` must return an `ssl.SSLContext`, got {type(context).__name__}")
  401. self.ssl: ssl.SSLContext | None = context
  402. elif self.is_ssl:
  403. assert self.ssl_certfile
  404. self.ssl = create_ssl_context(
  405. keyfile=self.ssl_keyfile,
  406. certfile=self.ssl_certfile,
  407. password=self.ssl_keyfile_password,
  408. ssl_version=self.ssl_version,
  409. cert_reqs=self.ssl_cert_reqs,
  410. ca_certs=self.ssl_ca_certs,
  411. ciphers=self.ssl_ciphers,
  412. )
  413. else:
  414. self.ssl = None
  415. encoded_headers = [(key.lower().encode("latin1"), value.encode("latin1")) for key, value in self.headers]
  416. self.encoded_headers = (
  417. [(b"server", b"uvicorn")] + encoded_headers
  418. if b"server" not in dict(encoded_headers) and self.server_header
  419. else encoded_headers
  420. )
  421. if isinstance(self.http, str):
  422. http_protocol_class = import_from_string(HTTP_PROTOCOLS.get(self.http, self.http))
  423. self.http_protocol_class: type[asyncio.Protocol] = http_protocol_class
  424. else:
  425. self.http_protocol_class = self.http
  426. if isinstance(self.ws, str):
  427. ws_protocol_class = import_from_string(WS_PROTOCOLS.get(self.ws, self.ws))
  428. self.ws_protocol_class: type[asyncio.Protocol] | None = ws_protocol_class
  429. else:
  430. self.ws_protocol_class = self.ws
  431. self.lifespan_class = import_from_string(LIFESPAN[self.lifespan])
  432. self.loaded_app = self.load_app()
  433. try:
  434. self.loaded_app = self.loaded_app()
  435. except TypeError as exc:
  436. if self.factory:
  437. logger.error("Error loading ASGI app factory: %s", exc)
  438. sys.exit(STARTUP_FAILURE)
  439. else:
  440. if not self.factory:
  441. logger.warning(
  442. "ASGI app factory detected. Using it, but please consider setting the --factory flag explicitly."
  443. )
  444. if self.interface == "auto":
  445. if inspect.isclass(self.loaded_app):
  446. use_asgi_3 = hasattr(self.loaded_app, "__await__")
  447. elif inspect.isfunction(self.loaded_app):
  448. use_asgi_3 = iscoroutinefunction(self.loaded_app)
  449. else:
  450. call = getattr(self.loaded_app, "__call__", None)
  451. use_asgi_3 = iscoroutinefunction(call)
  452. self.interface = "asgi3" if use_asgi_3 else "asgi2"
  453. if self.interface == "wsgi":
  454. self.loaded_app = WSGIMiddleware(self.loaded_app)
  455. self.ws_protocol_class = None
  456. elif self.interface == "asgi2":
  457. self.loaded_app = ASGI2Middleware(self.loaded_app)
  458. if logger.getEffectiveLevel() <= TRACE_LOG_LEVEL:
  459. self.loaded_app = MessageLoggerMiddleware(self.loaded_app)
  460. if self.proxy_headers:
  461. self.loaded_app = ProxyHeadersMiddleware(self.loaded_app, trusted_hosts=self.forwarded_allow_ips)
  462. self.loaded = True
  463. def setup_event_loop(self) -> None:
  464. raise AttributeError(
  465. "The `setup_event_loop` method was replaced by `get_loop_factory` in uvicorn 0.36.0.\n"
  466. "None of those methods are supposed to be used directly. If you are doing it, please let me know here: "
  467. "https://github.com/Kludex/uvicorn/discussions/2706. Thank you, and sorry for the inconvenience."
  468. )
  469. def get_loop_factory(self) -> Callable[[], asyncio.AbstractEventLoop] | None:
  470. if self.loop in LOOP_FACTORIES:
  471. loop_factory: Callable[..., Any] | None = import_from_string(LOOP_FACTORIES[self.loop])
  472. else:
  473. try:
  474. return import_from_string(self.loop)
  475. except ImportFromStringError as exc:
  476. logger.error("Error loading custom loop setup function. %s" % exc)
  477. sys.exit(STARTUP_FAILURE)
  478. if loop_factory is None:
  479. return None
  480. return loop_factory(use_subprocess=self.use_subprocess)
  481. def bind_socket(self) -> socket.socket:
  482. logger_args: list[str | int]
  483. if self.uds is not None: # pragma: py-win32
  484. path = self.uds
  485. sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
  486. try:
  487. sock.bind(path)
  488. uds_perms = 0o666
  489. os.chmod(self.uds, uds_perms)
  490. except OSError as exc: # pragma: full coverage
  491. logger.error(exc)
  492. sys.exit(STARTUP_FAILURE)
  493. message = "Uvicorn running on unix socket %s (Press CTRL+C to quit)"
  494. sock_name_format = "%s"
  495. color_message = "Uvicorn running on " + style(sock_name_format, bold=True) + " (Press CTRL+C to quit)"
  496. logger_args = [self.uds]
  497. elif self.fd is not None: # pragma: py-win32
  498. sock = socket.fromfd(self.fd, socket.AF_UNIX, socket.SOCK_STREAM)
  499. message = "Uvicorn running on socket %s (Press CTRL+C to quit)"
  500. fd_name_format = "%s"
  501. color_message = "Uvicorn running on " + style(fd_name_format, bold=True) + " (Press CTRL+C to quit)"
  502. logger_args = [sock.getsockname()]
  503. else:
  504. family = socket.AF_INET
  505. addr_format = "%s://%s:%d"
  506. if self.host and ":" in self.host: # pragma: full coverage
  507. # It's an IPv6 address.
  508. family = socket.AF_INET6
  509. addr_format = "%s://[%s]:%d"
  510. sock = socket.socket(family=family)
  511. sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  512. try:
  513. sock.bind((self.host, self.port))
  514. except OSError as exc: # pragma: full coverage
  515. logger.error(exc)
  516. sys.exit(STARTUP_FAILURE)
  517. message = f"Uvicorn running on {addr_format} (Press CTRL+C to quit)"
  518. color_message = "Uvicorn running on " + style(addr_format, bold=True) + " (Press CTRL+C to quit)"
  519. protocol_name = "https" if self.is_ssl else "http"
  520. logger_args = [protocol_name, self.host, sock.getsockname()[1]]
  521. logger.info(message, *logger_args, extra={"color_message": color_message})
  522. sock.set_inheritable(True)
  523. return sock
  524. @property
  525. def should_reload(self) -> bool:
  526. return isinstance(self.app, str) and self.reload