uvicorn 0.27.1__py3-none-any.whl → 0.28.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
uvicorn/__init__.py CHANGED
@@ -1,5 +1,5 @@
1
1
  from uvicorn.config import Config
2
2
  from uvicorn.main import Server, main, run
3
3
 
4
- __version__ = "0.27.1"
4
+ __version__ = "0.28.1"
5
5
  __all__ = ["main", "run", "Config", "Server"]
uvicorn/_subprocess.py CHANGED
@@ -9,7 +9,7 @@ import os
9
9
  import sys
10
10
  from multiprocessing.context import SpawnProcess
11
11
  from socket import socket
12
- from typing import Callable, Optional
12
+ from typing import Callable
13
13
 
14
14
  from uvicorn.config import Config
15
15
 
@@ -34,10 +34,10 @@ def get_subprocess(
34
34
  """
35
35
  # We pass across the stdin fileno, and reopen it in the child process.
36
36
  # This is required for some debugging environments.
37
- stdin_fileno: Optional[int]
38
37
  try:
39
38
  stdin_fileno = sys.stdin.fileno()
40
- except OSError:
39
+ # The `sys.stdin` can be `None`, see https://docs.python.org/3/library/sys.html#sys.__stdin__.
40
+ except (AttributeError, OSError):
41
41
  stdin_fileno = None
42
42
 
43
43
  kwargs = {
uvicorn/_types.py CHANGED
@@ -36,18 +36,16 @@ from typing import (
36
36
  Awaitable,
37
37
  Callable,
38
38
  Iterable,
39
+ Literal,
39
40
  MutableMapping,
40
41
  Optional,
42
+ Protocol,
41
43
  Tuple,
42
44
  Type,
45
+ TypedDict,
43
46
  Union,
44
47
  )
45
48
 
46
- if sys.version_info >= (3, 8): # pragma: py-lt-38
47
- from typing import Literal, Protocol, TypedDict
48
- else: # pragma: py-gte-38
49
- from typing_extensions import Literal, Protocol, TypedDict
50
-
51
49
  if sys.version_info >= (3, 11): # pragma: py-lt-311
52
50
  from typing import NotRequired
53
51
  else: # pragma: py-gte-311
@@ -239,9 +237,7 @@ class LifespanShutdownFailedEvent(TypedDict):
239
237
  message: str
240
238
 
241
239
 
242
- WebSocketEvent = Union[
243
- WebSocketReceiveEvent, WebSocketDisconnectEvent, WebSocketConnectEvent
244
- ]
240
+ WebSocketEvent = Union[WebSocketReceiveEvent, WebSocketDisconnectEvent, WebSocketConnectEvent]
245
241
 
246
242
 
247
243
  ASGIReceiveEvent = Union[
@@ -281,9 +277,7 @@ class ASGI2Protocol(Protocol):
281
277
  def __init__(self, scope: Scope) -> None:
282
278
  ... # pragma: no cover
283
279
 
284
- async def __call__(
285
- self, receive: ASGIReceiveCallable, send: ASGISendCallable
286
- ) -> None:
280
+ async def __call__(self, receive: ASGIReceiveCallable, send: ASGISendCallable) -> None:
287
281
  ... # pragma: no cover
288
282
 
289
283
 
uvicorn/config.py CHANGED
@@ -127,9 +127,7 @@ def is_dir(path: Path) -> bool:
127
127
  return False
128
128
 
129
129
 
130
- def resolve_reload_patterns(
131
- patterns_list: list[str], directories_list: list[str]
132
- ) -> tuple[list[str], list[Path]]:
130
+ def resolve_reload_patterns(patterns_list: list[str], directories_list: list[str]) -> tuple[list[str], list[Path]]:
133
131
  directories: list[Path] = list(set(map(Path, directories_list.copy())))
134
132
  patterns: list[str] = patterns_list.copy()
135
133
 
@@ -150,9 +148,7 @@ def resolve_reload_patterns(
150
148
  directories = list(set(directories))
151
149
  directories = list(map(Path, directories))
152
150
  directories = list(map(lambda x: x.resolve(), directories))
153
- directories = list(
154
- {reload_path for reload_path in directories if is_dir(reload_path)}
155
- )
151
+ directories = list({reload_path for reload_path in directories if is_dir(reload_path)})
156
152
 
157
153
  children = []
158
154
  for j in range(len(directories)):
@@ -280,12 +276,9 @@ class Config:
280
276
  self.reload_includes: list[str] = []
281
277
  self.reload_excludes: list[str] = []
282
278
 
283
- if (
284
- reload_dirs or reload_includes or reload_excludes
285
- ) and not self.should_reload:
279
+ if (reload_dirs or reload_includes or reload_excludes) and not self.should_reload:
286
280
  logger.warning(
287
- "Current configuration will not reload as not all conditions are met, "
288
- "please refer to documentation."
281
+ "Current configuration will not reload as not all conditions are met, " "please refer to documentation."
289
282
  )
290
283
 
291
284
  if self.should_reload:
@@ -293,22 +286,15 @@ class Config:
293
286
  reload_includes = _normalize_dirs(reload_includes)
294
287
  reload_excludes = _normalize_dirs(reload_excludes)
295
288
 
296
- self.reload_includes, self.reload_dirs = resolve_reload_patterns(
297
- reload_includes, reload_dirs
298
- )
289
+ self.reload_includes, self.reload_dirs = resolve_reload_patterns(reload_includes, reload_dirs)
299
290
 
300
- self.reload_excludes, self.reload_dirs_excludes = resolve_reload_patterns(
301
- reload_excludes, []
302
- )
291
+ self.reload_excludes, self.reload_dirs_excludes = resolve_reload_patterns(reload_excludes, [])
303
292
 
304
293
  reload_dirs_tmp = self.reload_dirs.copy()
305
294
 
306
295
  for directory in self.reload_dirs_excludes:
307
296
  for reload_directory in reload_dirs_tmp:
308
- if (
309
- directory == reload_directory
310
- or directory in reload_directory.parents
311
- ):
297
+ if directory == reload_directory or directory in reload_directory.parents:
312
298
  try:
313
299
  self.reload_dirs.remove(reload_directory)
314
300
  except ValueError:
@@ -343,9 +329,7 @@ class Config:
343
329
 
344
330
  self.forwarded_allow_ips: list[str] | str
345
331
  if forwarded_allow_ips is None:
346
- self.forwarded_allow_ips = os.environ.get(
347
- "FORWARDED_ALLOW_IPS", "127.0.0.1"
348
- )
332
+ self.forwarded_allow_ips = os.environ.get("FORWARDED_ALLOW_IPS", "127.0.0.1")
349
333
  else:
350
334
  self.forwarded_allow_ips = forwarded_allow_ips
351
335
 
@@ -375,12 +359,8 @@ class Config:
375
359
  if self.log_config is not None:
376
360
  if isinstance(self.log_config, dict):
377
361
  if self.use_colors in (True, False):
378
- self.log_config["formatters"]["default"][
379
- "use_colors"
380
- ] = self.use_colors
381
- self.log_config["formatters"]["access"][
382
- "use_colors"
383
- ] = self.use_colors
362
+ self.log_config["formatters"]["default"]["use_colors"] = self.use_colors
363
+ self.log_config["formatters"]["access"]["use_colors"] = self.use_colors
384
364
  logging.config.dictConfig(self.log_config)
385
365
  elif self.log_config.endswith(".json"):
386
366
  with open(self.log_config) as file:
@@ -397,9 +377,7 @@ class Config:
397
377
  else:
398
378
  # See the note about fileConfig() here:
399
379
  # https://docs.python.org/3/library/logging.config.html#configuration-file-format
400
- logging.config.fileConfig(
401
- self.log_config, disable_existing_loggers=False
402
- )
380
+ logging.config.fileConfig(self.log_config, disable_existing_loggers=False)
403
381
 
404
382
  if self.log_level is not None:
405
383
  if isinstance(self.log_level, str):
@@ -430,10 +408,7 @@ class Config:
430
408
  else:
431
409
  self.ssl = None
432
410
 
433
- encoded_headers = [
434
- (key.lower().encode("latin1"), value.encode("latin1"))
435
- for key, value in self.headers
436
- ]
411
+ encoded_headers = [(key.lower().encode("latin1"), value.encode("latin1")) for key, value in self.headers]
437
412
  self.encoded_headers = (
438
413
  [(b"server", b"uvicorn")] + encoded_headers
439
414
  if b"server" not in dict(encoded_headers) and self.server_header
@@ -469,8 +444,7 @@ class Config:
469
444
  else:
470
445
  if not self.factory:
471
446
  logger.warning(
472
- "ASGI app factory detected. Using it, "
473
- "but please consider setting the --factory flag explicitly."
447
+ "ASGI app factory detected. Using it, " "but please consider setting the --factory flag explicitly."
474
448
  )
475
449
 
476
450
  if self.interface == "auto":
@@ -492,9 +466,7 @@ class Config:
492
466
  if logger.getEffectiveLevel() <= TRACE_LOG_LEVEL:
493
467
  self.loaded_app = MessageLoggerMiddleware(self.loaded_app)
494
468
  if self.proxy_headers:
495
- self.loaded_app = ProxyHeadersMiddleware(
496
- self.loaded_app, trusted_hosts=self.forwarded_allow_ips
497
- )
469
+ self.loaded_app = ProxyHeadersMiddleware(self.loaded_app, trusted_hosts=self.forwarded_allow_ips)
498
470
 
499
471
  self.loaded = True
500
472
 
@@ -518,21 +490,13 @@ class Config:
518
490
 
519
491
  message = "Uvicorn running on unix socket %s (Press CTRL+C to quit)"
520
492
  sock_name_format = "%s"
521
- color_message = (
522
- "Uvicorn running on "
523
- + click.style(sock_name_format, bold=True)
524
- + " (Press CTRL+C to quit)"
525
- )
493
+ color_message = "Uvicorn running on " + click.style(sock_name_format, bold=True) + " (Press CTRL+C to quit)"
526
494
  logger_args = [self.uds]
527
495
  elif self.fd: # pragma: py-win32
528
496
  sock = socket.fromfd(self.fd, socket.AF_UNIX, socket.SOCK_STREAM)
529
497
  message = "Uvicorn running on socket %s (Press CTRL+C to quit)"
530
498
  fd_name_format = "%s"
531
- color_message = (
532
- "Uvicorn running on "
533
- + click.style(fd_name_format, bold=True)
534
- + " (Press CTRL+C to quit)"
535
- )
499
+ color_message = "Uvicorn running on " + click.style(fd_name_format, bold=True) + " (Press CTRL+C to quit)"
536
500
  logger_args = [sock.getsockname()]
537
501
  else:
538
502
  family = socket.AF_INET
@@ -552,11 +516,7 @@ class Config:
552
516
  sys.exit(1)
553
517
 
554
518
  message = f"Uvicorn running on {addr_format} (Press CTRL+C to quit)"
555
- color_message = (
556
- "Uvicorn running on "
557
- + click.style(addr_format, bold=True)
558
- + " (Press CTRL+C to quit)"
559
- )
519
+ color_message = "Uvicorn running on " + click.style(addr_format, bold=True) + " (Press CTRL+C to quit)"
560
520
  protocol_name = "https" if self.is_ssl else "http"
561
521
  logger_args = [protocol_name, self.host, sock.getsockname()[1]]
562
522
  logger.info(message, *logger_args, extra={"color_message": color_message})
uvicorn/importer.py CHANGED
@@ -12,9 +12,7 @@ def import_from_string(import_str: Any) -> Any:
12
12
 
13
13
  module_str, _, attrs_str = import_str.partition(":")
14
14
  if not module_str or not attrs_str:
15
- message = (
16
- 'Import string "{import_str}" must be in format "<module>:<attribute>".'
17
- )
15
+ message = 'Import string "{import_str}" must be in format "<module>:<attribute>".'
18
16
  raise ImportFromStringError(message.format(import_str=import_str))
19
17
 
20
18
  try:
@@ -31,8 +29,6 @@ def import_from_string(import_str: Any) -> Any:
31
29
  instance = getattr(instance, attr_str)
32
30
  except AttributeError:
33
31
  message = 'Attribute "{attrs_str}" not found in module "{module_str}".'
34
- raise ImportFromStringError(
35
- message.format(attrs_str=attrs_str, module_str=module_str)
36
- )
32
+ raise ImportFromStringError(message.format(attrs_str=attrs_str, module_str=module_str))
37
33
 
38
34
  return instance
uvicorn/lifespan/off.py CHANGED
@@ -1,4 +1,6 @@
1
- from typing import Any, Dict
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
2
4
 
3
5
  from uvicorn import Config
4
6
 
@@ -6,7 +8,7 @@ from uvicorn import Config
6
8
  class LifespanOff:
7
9
  def __init__(self, config: Config) -> None:
8
10
  self.should_exit = False
9
- self.state: Dict[str, Any] = {}
11
+ self.state: dict[str, Any] = {}
10
12
 
11
13
  async def startup(self) -> None:
12
14
  pass
uvicorn/lifespan/on.py CHANGED
@@ -1,7 +1,9 @@
1
+ from __future__ import annotations
2
+
1
3
  import asyncio
2
4
  import logging
3
5
  from asyncio import Queue
4
- from typing import Any, Dict, Union
6
+ from typing import Any, Union
5
7
 
6
8
  from uvicorn import Config
7
9
  from uvicorn._types import (
@@ -35,12 +37,12 @@ class LifespanOn:
35
37
  self.logger = logging.getLogger("uvicorn.error")
36
38
  self.startup_event = asyncio.Event()
37
39
  self.shutdown_event = asyncio.Event()
38
- self.receive_queue: "Queue[LifespanReceiveMessage]" = asyncio.Queue()
40
+ self.receive_queue: Queue[LifespanReceiveMessage] = asyncio.Queue()
39
41
  self.error_occured = False
40
42
  self.startup_failed = False
41
43
  self.shutdown_failed = False
42
44
  self.should_exit = False
43
- self.state: Dict[str, Any] = {}
45
+ self.state: dict[str, Any] = {}
44
46
 
45
47
  async def startup(self) -> None:
46
48
  self.logger.info("Waiting for application startup.")
@@ -67,9 +69,7 @@ class LifespanOn:
67
69
  await self.receive_queue.put(shutdown_event)
68
70
  await self.shutdown_event.wait()
69
71
 
70
- if self.shutdown_failed or (
71
- self.error_occured and self.config.lifespan == "on"
72
- ):
72
+ if self.shutdown_failed or (self.error_occured and self.config.lifespan == "on"):
73
73
  self.logger.error("Application shutdown failed. Exiting.")
74
74
  self.should_exit = True
75
75
  else:
@@ -99,7 +99,7 @@ class LifespanOn:
99
99
  self.startup_event.set()
100
100
  self.shutdown_event.set()
101
101
 
102
- async def send(self, message: "LifespanSendMessage") -> None:
102
+ async def send(self, message: LifespanSendMessage) -> None:
103
103
  assert message["type"] in (
104
104
  "lifespan.startup.complete",
105
105
  "lifespan.startup.failed",
@@ -133,5 +133,5 @@ class LifespanOn:
133
133
  if message.get("message"):
134
134
  self.logger.error(message["message"])
135
135
 
136
- async def receive(self) -> "LifespanReceiveMessage":
136
+ async def receive(self) -> LifespanReceiveMessage:
137
137
  return await self.receive_queue.get()
uvicorn/logging.py CHANGED
@@ -26,9 +26,7 @@ class ColourizedFormatter(logging.Formatter):
26
26
  logging.INFO: lambda level_name: click.style(str(level_name), fg="green"),
27
27
  logging.WARNING: lambda level_name: click.style(str(level_name), fg="yellow"),
28
28
  logging.ERROR: lambda level_name: click.style(str(level_name), fg="red"),
29
- logging.CRITICAL: lambda level_name: click.style(
30
- str(level_name), fg="bright_red"
31
- ),
29
+ logging.CRITICAL: lambda level_name: click.style(str(level_name), fg="bright_red"),
32
30
  }
33
31
 
34
32
  def __init__(
@@ -86,7 +84,7 @@ class AccessFormatter(ColourizedFormatter):
86
84
  status_phrase = http.HTTPStatus(status_code).phrase
87
85
  except ValueError:
88
86
  status_phrase = ""
89
- status_and_phrase = "%s %s" % (status_code, status_phrase)
87
+ status_and_phrase = f"{status_code} {status_phrase}"
90
88
  if self.use_colors:
91
89
 
92
90
  def default(code: int) -> str:
@@ -106,7 +104,7 @@ class AccessFormatter(ColourizedFormatter):
106
104
  status_code,
107
105
  ) = recordcopy.args # type: ignore[misc]
108
106
  status_code = self.get_status_code(int(status_code)) # type: ignore[arg-type]
109
- request_line = "%s %s HTTP/%s" % (method, full_path, http_version)
107
+ request_line = f"{method} {full_path} HTTP/{http_version}"
110
108
  if self.use_colors:
111
109
  request_line = click.style(request_line, bold=True)
112
110
  recordcopy.__dict__.update(
uvicorn/main.py CHANGED
@@ -47,12 +47,11 @@ def print_version(ctx: click.Context, param: click.Parameter, value: bool) -> No
47
47
  if not value or ctx.resilient_parsing:
48
48
  return
49
49
  click.echo(
50
- "Running uvicorn %s with %s %s on %s"
51
- % (
52
- uvicorn.__version__,
53
- platform.python_implementation(),
54
- platform.python_version(),
55
- platform.system(),
50
+ "Running uvicorn {version} with {py_implementation} {py_version} on {system}".format(
51
+ version=uvicorn.__version__,
52
+ py_implementation=platform.python_implementation(),
53
+ py_version=platform.python_version(),
54
+ system=platform.system(),
56
55
  )
57
56
  )
58
57
  ctx.exit()
@@ -75,16 +74,13 @@ def print_version(ctx: click.Context, param: click.Parameter, value: bool) -> No
75
74
  show_default=True,
76
75
  )
77
76
  @click.option("--uds", type=str, default=None, help="Bind to a UNIX domain socket.")
78
- @click.option(
79
- "--fd", type=int, default=None, help="Bind to socket from this file descriptor."
80
- )
77
+ @click.option("--fd", type=int, default=None, help="Bind to socket from this file descriptor.")
81
78
  @click.option("--reload", is_flag=True, default=False, help="Enable auto-reload.")
82
79
  @click.option(
83
80
  "--reload-dir",
84
81
  "reload_dirs",
85
82
  multiple=True,
86
- help="Set reload directories explicitly, instead of using the current working"
87
- " directory.",
83
+ help="Set reload directories explicitly, instead of using the current working" " directory.",
88
84
  type=click.Path(exists=True),
89
85
  )
90
86
  @click.option(
@@ -109,8 +105,7 @@ def print_version(ctx: click.Context, param: click.Parameter, value: bool) -> No
109
105
  type=float,
110
106
  default=0.25,
111
107
  show_default=True,
112
- help="Delay between previous and next check if application needs to be."
113
- " Defaults to 0.25s.",
108
+ help="Delay between previous and next check if application needs to be." " Defaults to 0.25s.",
114
109
  )
115
110
  @click.option(
116
111
  "--workers",
@@ -226,8 +221,7 @@ def print_version(ctx: click.Context, param: click.Parameter, value: bool) -> No
226
221
  "--proxy-headers/--no-proxy-headers",
227
222
  is_flag=True,
228
223
  default=True,
229
- help="Enable/Disable X-Forwarded-Proto, X-Forwarded-For, X-Forwarded-Port to "
230
- "populate remote address info.",
224
+ help="Enable/Disable X-Forwarded-Proto, X-Forwarded-For, X-Forwarded-Port to " "populate remote address info.",
231
225
  )
232
226
  @click.option(
233
227
  "--server-header/--no-server-header",
@@ -258,8 +252,7 @@ def print_version(ctx: click.Context, param: click.Parameter, value: bool) -> No
258
252
  "--limit-concurrency",
259
253
  type=int,
260
254
  default=None,
261
- help="Maximum number of concurrent connections or tasks to allow, before issuing"
262
- " HTTP 503 responses.",
255
+ help="Maximum number of concurrent connections or tasks to allow, before issuing" " HTTP 503 responses.",
263
256
  )
264
257
  @click.option(
265
258
  "--backlog",
@@ -286,9 +279,7 @@ def print_version(ctx: click.Context, param: click.Parameter, value: bool) -> No
286
279
  default=None,
287
280
  help="Maximum number of seconds to wait for graceful shutdown.",
288
281
  )
289
- @click.option(
290
- "--ssl-keyfile", type=str, default=None, help="SSL key file", show_default=True
291
- )
282
+ @click.option("--ssl-keyfile", type=str, default=None, help="SSL key file", show_default=True)
292
283
  @click.option(
293
284
  "--ssl-certfile",
294
285
  type=str,
@@ -571,10 +562,7 @@ def run(
571
562
 
572
563
  if (config.reload or config.workers > 1) and not isinstance(app, str):
573
564
  logger = logging.getLogger("uvicorn.error")
574
- logger.warning(
575
- "You must pass the application as an import string to enable 'reload' or "
576
- "'workers'."
577
- )
565
+ logger.warning("You must pass the application as an import string to enable 'reload' or " "'workers'.")
578
566
  sys.exit(1)
579
567
 
580
568
  if config.should_reload:
@@ -10,8 +10,6 @@ class ASGI2Middleware:
10
10
  def __init__(self, app: "ASGI2Application"):
11
11
  self.app = app
12
12
 
13
- async def __call__(
14
- self, scope: "Scope", receive: "ASGIReceiveCallable", send: "ASGISendCallable"
15
- ) -> None:
13
+ async def __call__(self, scope: "Scope", receive: "ASGIReceiveCallable", send: "ASGISendCallable") -> None:
16
14
  instance = self.app(scope)
17
15
  await instance(receive, send)
@@ -8,23 +8,18 @@ the connecting client, rather that the connecting proxy.
8
8
 
9
9
  https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers#Proxies
10
10
  """
11
- from typing import List, Optional, Tuple, Union, cast
11
+ from __future__ import annotations
12
12
 
13
- from uvicorn._types import (
14
- ASGI3Application,
15
- ASGIReceiveCallable,
16
- ASGISendCallable,
17
- HTTPScope,
18
- Scope,
19
- WebSocketScope,
20
- )
13
+ from typing import Union, cast
14
+
15
+ from uvicorn._types import ASGI3Application, ASGIReceiveCallable, ASGISendCallable, HTTPScope, Scope, WebSocketScope
21
16
 
22
17
 
23
18
  class ProxyHeadersMiddleware:
24
19
  def __init__(
25
20
  self,
26
- app: "ASGI3Application",
27
- trusted_hosts: Union[List[str], str] = "127.0.0.1",
21
+ app: ASGI3Application,
22
+ trusted_hosts: list[str] | str = "127.0.0.1",
28
23
  ) -> None:
29
24
  self.app = app
30
25
  if isinstance(trusted_hosts, str):
@@ -33,9 +28,7 @@ class ProxyHeadersMiddleware:
33
28
  self.trusted_hosts = set(trusted_hosts)
34
29
  self.always_trust = "*" in self.trusted_hosts
35
30
 
36
- def get_trusted_client_host(
37
- self, x_forwarded_for_hosts: List[str]
38
- ) -> Optional[str]:
31
+ def get_trusted_client_host(self, x_forwarded_for_hosts: list[str]) -> str | None:
39
32
  if self.always_trust:
40
33
  return x_forwarded_for_hosts[0]
41
34
 
@@ -45,12 +38,10 @@ class ProxyHeadersMiddleware:
45
38
 
46
39
  return None
47
40
 
48
- async def __call__(
49
- self, scope: "Scope", receive: "ASGIReceiveCallable", send: "ASGISendCallable"
50
- ) -> None:
41
+ async def __call__(self, scope: Scope, receive: ASGIReceiveCallable, send: ASGISendCallable) -> None:
51
42
  if scope["type"] in ("http", "websocket"):
52
43
  scope = cast(Union["HTTPScope", "WebSocketScope"], scope)
53
- client_addr: Optional[Tuple[str, int]] = scope.get("client")
44
+ client_addr: tuple[str, int] | None = scope.get("client")
54
45
  client_host = client_addr[0] if client_addr else None
55
46
 
56
47
  if self.always_trust or client_host in self.trusted_hosts:
@@ -59,13 +50,9 @@ class ProxyHeadersMiddleware:
59
50
  if b"x-forwarded-proto" in headers:
60
51
  # Determine if the incoming request was http or https based on
61
52
  # the X-Forwarded-Proto header.
62
- x_forwarded_proto = (
63
- headers[b"x-forwarded-proto"].decode("latin1").strip()
64
- )
53
+ x_forwarded_proto = headers[b"x-forwarded-proto"].decode("latin1").strip()
65
54
  if scope["type"] == "websocket":
66
- scope["scheme"] = (
67
- "wss" if x_forwarded_proto == "https" else "ws"
68
- )
55
+ scope["scheme"] = x_forwarded_proto.replace("http", "ws")
69
56
  else:
70
57
  scope["scheme"] = x_forwarded_proto
71
58
 
@@ -74,9 +61,7 @@ class ProxyHeadersMiddleware:
74
61
  # X-Forwarded-For header. We've lost the connecting client's port
75
62
  # information by now, so only include the host.
76
63
  x_forwarded_for = headers[b"x-forwarded-for"].decode("latin1")
77
- x_forwarded_for_hosts = [
78
- item.strip() for item in x_forwarded_for.split(",")
79
- ]
64
+ x_forwarded_for_hosts = [item.strip() for item in x_forwarded_for.split(",")]
80
65
  host = self.get_trusted_client_host(x_forwarded_for_hosts)
81
66
  port = 0
82
67
  scope["client"] = (host, port) # type: ignore[arg-type]
@@ -1,10 +1,12 @@
1
+ from __future__ import annotations
2
+
1
3
  import asyncio
2
4
  import concurrent.futures
3
5
  import io
4
6
  import sys
5
7
  import warnings
6
8
  from collections import deque
7
- from typing import Deque, Iterable, Optional, Tuple
9
+ from typing import Iterable
8
10
 
9
11
  from uvicorn._types import (
10
12
  ASGIReceiveCallable,
@@ -22,9 +24,7 @@ from uvicorn._types import (
22
24
  )
23
25
 
24
26
 
25
- def build_environ(
26
- scope: "HTTPScope", message: "ASGIReceiveEvent", body: io.BytesIO
27
- ) -> Environ:
27
+ def build_environ(scope: HTTPScope, message: ASGIReceiveEvent, body: io.BytesIO) -> Environ:
28
28
  """
29
29
  Builds a scope and request message into a WSGI environ object.
30
30
  """
@@ -91,9 +91,9 @@ class _WSGIMiddleware:
91
91
 
92
92
  async def __call__(
93
93
  self,
94
- scope: "HTTPScope",
95
- receive: "ASGIReceiveCallable",
96
- send: "ASGISendCallable",
94
+ scope: HTTPScope,
95
+ receive: ASGIReceiveCallable,
96
+ send: ASGISendCallable,
97
97
  ) -> None:
98
98
  assert scope["type"] == "http"
99
99
  instance = WSGIResponder(self.app, self.executor, scope)
@@ -105,7 +105,7 @@ class WSGIResponder:
105
105
  self,
106
106
  app: WSGIApp,
107
107
  executor: concurrent.futures.ThreadPoolExecutor,
108
- scope: "HTTPScope",
108
+ scope: HTTPScope,
109
109
  ):
110
110
  self.app = app
111
111
  self.executor = executor
@@ -113,21 +113,19 @@ class WSGIResponder:
113
113
  self.status = None
114
114
  self.response_headers = None
115
115
  self.send_event = asyncio.Event()
116
- self.send_queue: Deque[Optional["ASGISendEvent"]] = deque()
116
+ self.send_queue: deque[ASGISendEvent | None] = deque()
117
117
  self.loop: asyncio.AbstractEventLoop = asyncio.get_event_loop()
118
118
  self.response_started = False
119
- self.exc_info: Optional[ExcInfo] = None
119
+ self.exc_info: ExcInfo | None = None
120
120
 
121
- async def __call__(
122
- self, receive: "ASGIReceiveCallable", send: "ASGISendCallable"
123
- ) -> None:
121
+ async def __call__(self, receive: ASGIReceiveCallable, send: ASGISendCallable) -> None:
124
122
  message: HTTPRequestEvent = await receive() # type: ignore[assignment]
125
123
  body = io.BytesIO(message.get("body", b""))
126
124
  more_body = message.get("more_body", False)
127
125
  if more_body:
128
126
  body.seek(0, io.SEEK_END)
129
127
  while more_body:
130
- body_message: "HTTPRequestEvent" = (
128
+ body_message: HTTPRequestEvent = (
131
129
  await receive() # type: ignore[assignment]
132
130
  )
133
131
  body.write(body_message.get("body", b""))
@@ -135,9 +133,7 @@ class WSGIResponder:
135
133
  body.seek(0)
136
134
  environ = build_environ(self.scope, message, body)
137
135
  self.loop = asyncio.get_event_loop()
138
- wsgi = self.loop.run_in_executor(
139
- self.executor, self.wsgi, environ, self.start_response
140
- )
136
+ wsgi = self.loop.run_in_executor(self.executor, self.wsgi, environ, self.start_response)
141
137
  sender = self.loop.create_task(self.sender(send))
142
138
  try:
143
139
  await asyncio.wait_for(wsgi, None)
@@ -148,7 +144,7 @@ class WSGIResponder:
148
144
  if self.exc_info is not None:
149
145
  raise self.exc_info[0].with_traceback(self.exc_info[1], self.exc_info[2])
150
146
 
151
- async def sender(self, send: "ASGISendCallable") -> None:
147
+ async def sender(self, send: ASGISendCallable) -> None:
152
148
  while True:
153
149
  if self.send_queue:
154
150
  message = self.send_queue.popleft()
@@ -162,18 +158,15 @@ class WSGIResponder:
162
158
  def start_response(
163
159
  self,
164
160
  status: str,
165
- response_headers: Iterable[Tuple[str, str]],
166
- exc_info: Optional[ExcInfo] = None,
161
+ response_headers: Iterable[tuple[str, str]],
162
+ exc_info: ExcInfo | None = None,
167
163
  ) -> None:
168
164
  self.exc_info = exc_info
169
165
  if not self.response_started:
170
166
  self.response_started = True
171
167
  status_code_str, _ = status.split(" ", 1)
172
168
  status_code = int(status_code_str)
173
- headers = [
174
- (name.encode("ascii"), value.encode("ascii"))
175
- for name, value in response_headers
176
- ]
169
+ headers = [(name.encode("ascii"), value.encode("ascii")) for name, value in response_headers]
177
170
  http_response_start_event: HTTPResponseStartEvent = {
178
171
  "type": "http.response.start",
179
172
  "status": status_code,
@@ -45,9 +45,7 @@ class FlowControl:
45
45
  self._is_writable_event.set()
46
46
 
47
47
 
48
- async def service_unavailable(
49
- scope: "Scope", receive: "ASGIReceiveCallable", send: "ASGISendCallable"
50
- ) -> None:
48
+ async def service_unavailable(scope: "Scope", receive: "ASGIReceiveCallable", send: "ASGISendCallable") -> None:
51
49
  response_start: "HTTPResponseStartEvent" = {
52
50
  "type": "http.response.start",
53
51
  "status": 503,