From e31548f6938453739fffda99a6cfdb4cb3fb5421 Mon Sep 17 00:00:00 2001 From: Marcelo Trylesinski Date: Thu, 8 Jun 2023 08:33:45 +0200 Subject: [PATCH 1/3] Drop asgiref dependency at all --- pyproject.toml | 3 + requirements.txt | 3 - scripts/check | 2 +- tests/middleware/test_proxy_headers.py | 2 +- tests/middleware/test_wsgi.py | 7 +- tests/supervisors/test_multiprocess.py | 6 +- tests/test_config.py | 17 +- tests/test_subprocess.py | 6 +- uvicorn/_types.py | 322 +++++++++++++++++- uvicorn/config.py | 5 +- uvicorn/lifespan/on.py | 40 ++- uvicorn/main.py | 4 +- uvicorn/middleware/asgi2.py | 15 +- uvicorn/middleware/message_logger.py | 20 +- uvicorn/middleware/proxy_headers.py | 21 +- uvicorn/middleware/wsgi.py | 31 +- uvicorn/protocols/http/flow_control.py | 16 +- uvicorn/protocols/http/h11_impl.py | 25 +- uvicorn/protocols/http/httptools_impl.py | 23 +- uvicorn/protocols/utils.py | 5 +- .../protocols/websockets/websockets_impl.py | 25 +- uvicorn/protocols/websockets/wsproto_impl.py | 30 +- 22 files changed, 446 insertions(+), 182 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 8d6eefd6c..bf76ab36b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -68,6 +68,9 @@ ignore = ["B904", "B028"] combine-as-imports = true [tool.mypy] +warn_unused_ignores = true +warn_redundant_casts = true +show_error_codes = true disallow_untyped_defs = true ignore_missing_imports = true follow_imports = "silent" diff --git a/requirements.txt b/requirements.txt index 5b8685603..5e8f3118d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,8 +1,5 @@ -e .[standard] -# Type annotation -asgiref==3.5.2 - # Explicit optionals a2wsgi==1.7.0 wsproto==1.2.0 diff --git a/scripts/check b/scripts/check index c2963f13c..2cbe36662 100755 --- a/scripts/check +++ b/scripts/check @@ -11,6 +11,6 @@ set -x ./scripts/sync-version ${PREFIX}black --check --diff --target-version=py37 $SOURCE_FILES -${PREFIX}mypy --show-error-codes $SOURCE_FILES +${PREFIX}mypy $SOURCE_FILES ${PREFIX}ruff check $SOURCE_FILES ${PREFIX}python -m tools.cli_usage --check diff --git a/tests/middleware/test_proxy_headers.py b/tests/middleware/test_proxy_headers.py index 648407248..7dba44f6b 100644 --- a/tests/middleware/test_proxy_headers.py +++ b/tests/middleware/test_proxy_headers.py @@ -2,9 +2,9 @@ import httpx import pytest -from asgiref.typing import ASGIReceiveCallable, ASGISendCallable, Scope from tests.response import Response +from uvicorn._types import ASGIReceiveCallable, ASGISendCallable, Scope from uvicorn.middleware.proxy_headers import ProxyHeadersMiddleware diff --git a/tests/middleware/test_wsgi.py b/tests/middleware/test_wsgi.py index 26cd7e0f2..34730ec92 100644 --- a/tests/middleware/test_wsgi.py +++ b/tests/middleware/test_wsgi.py @@ -1,17 +1,14 @@ import io import sys -from typing import TYPE_CHECKING, AsyncGenerator, Callable, List +from typing import AsyncGenerator, Callable, List import a2wsgi import httpx import pytest -from uvicorn._types import Environ, StartResponse +from uvicorn._types import Environ, HTTPRequestEvent, HTTPScope, StartResponse from uvicorn.middleware import wsgi -if TYPE_CHECKING: - from asgiref.typing import HTTPRequestEvent, HTTPScope - def hello_world(environ: Environ, start_response: StartResponse) -> List[bytes]: status = "200 OK" diff --git a/tests/supervisors/test_multiprocess.py b/tests/supervisors/test_multiprocess.py index ede80ad30..82dc1118a 100644 --- a/tests/supervisors/test_multiprocess.py +++ b/tests/supervisors/test_multiprocess.py @@ -1,13 +1,11 @@ import signal import socket -from typing import TYPE_CHECKING, List, Optional +from typing import List, Optional from uvicorn import Config +from uvicorn._types import ASGIReceiveCallable, ASGISendCallable, Scope from uvicorn.supervisors import Multiprocess -if TYPE_CHECKING: - from asgiref.typing import ASGIReceiveCallable, ASGISendCallable, Scope - async def app( scope: "Scope", receive: "ASGIReceiveCallable", send: "ASGISendCallable" diff --git a/tests/test_config.py b/tests/test_config.py index 803edc2e1..9be19207e 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -13,7 +13,14 @@ from pytest_mock import MockerFixture from tests.utils import as_cwd -from uvicorn._types import Environ, StartResponse +from uvicorn._types import ( + ASGIApplication, + ASGIReceiveCallable, + ASGISendCallable, + Environ, + Scope, + StartResponse, +) from uvicorn.config import Config from uvicorn.middleware.proxy_headers import ProxyHeadersMiddleware from uvicorn.middleware.wsgi import WSGIMiddleware @@ -24,14 +31,6 @@ else: # pragma: py-lt-38 from typing import Literal -if typing.TYPE_CHECKING: - from asgiref.typing import ( - ASGIApplication, - ASGIReceiveCallable, - ASGISendCallable, - Scope, - ) - @pytest.fixture def mocked_logging_config_module(mocker: MockerFixture) -> MagicMock: diff --git a/tests/test_subprocess.py b/tests/test_subprocess.py index 95696d577..f32721a6c 100644 --- a/tests/test_subprocess.py +++ b/tests/test_subprocess.py @@ -1,13 +1,11 @@ import socket -from typing import TYPE_CHECKING, List +from typing import List from unittest.mock import patch from uvicorn._subprocess import SpawnProcess, get_subprocess, subprocess_started +from uvicorn._types import ASGIReceiveCallable, ASGISendCallable, Scope from uvicorn.config import Config -if TYPE_CHECKING: - from asgiref.typing import ASGIReceiveCallable, ASGISendCallable, Scope - def server_run(sockets: List[socket.socket]): # pragma: no cover ... diff --git a/uvicorn/_types.py b/uvicorn/_types.py index 0a547a50d..d381f39e0 100644 --- a/uvicorn/_types.py +++ b/uvicorn/_types.py @@ -1,14 +1,320 @@ +""" +Copyright (c) Django Software Foundation and individual contributors. +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of Django nor the names of its contributors may be used + to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +""" + +import sys import types -import typing +from typing import ( + Any, + Awaitable, + Callable, + Dict, + Iterable, + MutableMapping, + Optional, + Tuple, + Type, + Union, +) + +if sys.version_info >= (3, 8): + from typing import Literal, Protocol, TypedDict +else: + from typing_extensions import Literal, Protocol, TypedDict + +if sys.version_info >= (3, 11): + from typing import NotRequired +else: + from typing_extensions import NotRequired # WSGI -Environ = typing.MutableMapping[str, typing.Any] -ExcInfo = typing.Tuple[ - typing.Type[BaseException], BaseException, typing.Optional[types.TracebackType] +Environ = MutableMapping[str, Any] +ExcInfo = Tuple[Type[BaseException], BaseException, Optional[types.TracebackType]] +StartResponse = Callable[[str, Iterable[Tuple[str, str]], Optional[ExcInfo]], None] +WSGIApp = Callable[[Environ, StartResponse], Union[Iterable[bytes], BaseException]] + +# ASGI + + +__all__ = ( + "ASGIVersions", + "HTTPScope", + "WebSocketScope", + "LifespanScope", + "WWWScope", + "Scope", + "HTTPRequestEvent", + "HTTPResponseStartEvent", + "HTTPResponseBodyEvent", + "HTTPResponseTrailersEvent", + "HTTPServerPushEvent", + "HTTPDisconnectEvent", + "WebSocketConnectEvent", + "WebSocketAcceptEvent", + "WebSocketReceiveEvent", + "WebSocketSendEvent", + "WebSocketResponseStartEvent", + "WebSocketResponseBodyEvent", + "WebSocketDisconnectEvent", + "WebSocketCloseEvent", + "LifespanStartupEvent", + "LifespanShutdownEvent", + "LifespanStartupCompleteEvent", + "LifespanStartupFailedEvent", + "LifespanShutdownCompleteEvent", + "LifespanShutdownFailedEvent", + "ASGIReceiveEvent", + "ASGISendEvent", + "ASGIReceiveCallable", + "ASGISendCallable", + "ASGI2Protocol", + "ASGI2Application", + "ASGI3Application", + "ASGIApplication", +) + + +class ASGIVersions(TypedDict): + spec_version: str + version: Union[Literal["2.0"], Literal["3.0"]] + + +class HTTPScope(TypedDict): + type: Literal["http"] + asgi: ASGIVersions + http_version: str + method: str + scheme: str + path: str + raw_path: bytes + query_string: bytes + root_path: str + headers: Iterable[Tuple[bytes, bytes]] + client: Optional[Tuple[str, int]] + server: Optional[Tuple[str, Optional[int]]] + state: NotRequired[Dict[str, Any]] + extensions: NotRequired[Dict[str, Dict[object, object]]] + + +class WebSocketScope(TypedDict): + type: Literal["websocket"] + asgi: ASGIVersions + http_version: str + scheme: str + path: str + raw_path: bytes + query_string: bytes + root_path: str + headers: Iterable[Tuple[bytes, bytes]] + client: Optional[Tuple[str, int]] + server: Optional[Tuple[str, Optional[int]]] + subprotocols: Iterable[str] + state: NotRequired[Dict[str, Any]] + extensions: NotRequired[Dict[str, Dict[object, object]]] + + +class LifespanScope(TypedDict): + type: Literal["lifespan"] + asgi: ASGIVersions + state: NotRequired[Dict[str, Any]] + + +WWWScope = Union[HTTPScope, WebSocketScope] +Scope = Union[HTTPScope, WebSocketScope, LifespanScope] + + +class HTTPRequestEvent(TypedDict): + type: Literal["http.request"] + body: bytes + more_body: bool + + +class HTTPResponseDebugEvent(TypedDict): + type: Literal["http.response.debug"] + info: Dict[str, object] + + +class HTTPResponseStartEvent(TypedDict): + type: Literal["http.response.start"] + status: int + headers: Iterable[Tuple[bytes, bytes]] + trailers: NotRequired[bool] + + +class HTTPResponseBodyEvent(TypedDict): + type: Literal["http.response.body"] + body: bytes + more_body: bool + + +class HTTPResponseTrailersEvent(TypedDict): + type: Literal["http.response.trailers"] + headers: Iterable[Tuple[bytes, bytes]] + more_trailers: bool + + +class HTTPServerPushEvent(TypedDict): + type: Literal["http.response.push"] + path: str + headers: Iterable[Tuple[bytes, bytes]] + + +class HTTPDisconnectEvent(TypedDict): + type: Literal["http.disconnect"] + + +class WebSocketConnectEvent(TypedDict): + type: Literal["websocket.connect"] + + +class WebSocketAcceptEvent(TypedDict): + type: Literal["websocket.accept"] + subprotocol: Optional[str] + headers: Iterable[Tuple[bytes, bytes]] + + +class WebSocketReceiveEvent(TypedDict): + type: Literal["websocket.receive"] + bytes: Optional[bytes] + text: Optional[str] + + +class WebSocketSendEvent(TypedDict): + type: Literal["websocket.send"] + bytes: Optional[bytes] + text: Optional[str] + + +class WebSocketResponseStartEvent(TypedDict): + type: Literal["websocket.http.response.start"] + status: int + headers: Iterable[Tuple[bytes, bytes]] + + +class WebSocketResponseBodyEvent(TypedDict): + type: Literal["websocket.http.response.body"] + body: bytes + more_body: bool + + +class WebSocketDisconnectEvent(TypedDict): + type: Literal["websocket.disconnect"] + code: int + + +class WebSocketCloseEvent(TypedDict): + type: Literal["websocket.close"] + code: int + reason: Optional[str] + + +class LifespanStartupEvent(TypedDict): + type: Literal["lifespan.startup"] + + +class LifespanShutdownEvent(TypedDict): + type: Literal["lifespan.shutdown"] + + +class LifespanStartupCompleteEvent(TypedDict): + type: Literal["lifespan.startup.complete"] + + +class LifespanStartupFailedEvent(TypedDict): + type: Literal["lifespan.startup.failed"] + message: str + + +class LifespanShutdownCompleteEvent(TypedDict): + type: Literal["lifespan.shutdown.complete"] + + +class LifespanShutdownFailedEvent(TypedDict): + type: Literal["lifespan.shutdown.failed"] + message: str + + +WebSocketEvent = Union[ + WebSocketReceiveEvent, WebSocketDisconnectEvent, WebSocketConnectEvent ] -StartResponse = typing.Callable[ - [str, typing.Iterable[typing.Tuple[str, str]], typing.Optional[ExcInfo]], None + + +ASGIReceiveEvent = Union[ + HTTPRequestEvent, + HTTPDisconnectEvent, + WebSocketConnectEvent, + WebSocketReceiveEvent, + WebSocketDisconnectEvent, + LifespanStartupEvent, + LifespanShutdownEvent, ] -WSGIApp = typing.Callable[ - [Environ, StartResponse], typing.Union[typing.Iterable[bytes], BaseException] + + +ASGISendEvent = Union[ + HTTPResponseStartEvent, + HTTPResponseBodyEvent, + HTTPResponseTrailersEvent, + HTTPServerPushEvent, + HTTPDisconnectEvent, + WebSocketAcceptEvent, + WebSocketSendEvent, + WebSocketResponseStartEvent, + WebSocketResponseBodyEvent, + WebSocketCloseEvent, + LifespanStartupCompleteEvent, + LifespanStartupFailedEvent, + LifespanShutdownCompleteEvent, + LifespanShutdownFailedEvent, +] + + +ASGIReceiveCallable = Callable[[], Awaitable[ASGIReceiveEvent]] +ASGISendCallable = Callable[[ASGISendEvent], Awaitable[None]] + + +class ASGI2Protocol(Protocol): + def __init__(self, scope: Scope) -> None: + ... + + async def __call__( + self, receive: ASGIReceiveCallable, send: ASGISendCallable + ) -> None: + ... + + +ASGI2Application = Type[ASGI2Protocol] +ASGI3Application = Callable[ + [ + Scope, + ASGIReceiveCallable, + ASGISendCallable, + ], + Awaitable[None], ] +ASGIApplication = Union[ASGI2Application, ASGI3Application] diff --git a/uvicorn/config.py b/uvicorn/config.py index 9c5214e1f..2e52fd277 100644 --- a/uvicorn/config.py +++ b/uvicorn/config.py @@ -9,7 +9,6 @@ import sys from pathlib import Path from typing import ( - TYPE_CHECKING, Any, Awaitable, Callable, @@ -30,15 +29,13 @@ import click +from uvicorn._types import ASGIApplication from uvicorn.importer import ImportFromStringError, import_from_string from uvicorn.middleware.asgi2 import ASGI2Middleware from uvicorn.middleware.message_logger import MessageLoggerMiddleware from uvicorn.middleware.proxy_headers import ProxyHeadersMiddleware from uvicorn.middleware.wsgi import WSGIMiddleware -if TYPE_CHECKING: - from asgiref.typing import ASGIApplication - HTTPProtocolType = Literal["auto", "h11", "httptools"] WSProtocolType = Literal["auto", "none", "websockets", "wsproto"] LifespanType = Literal["auto", "on", "off"] diff --git a/uvicorn/lifespan/on.py b/uvicorn/lifespan/on.py index 37e935f01..34dfdb1c5 100644 --- a/uvicorn/lifespan/on.py +++ b/uvicorn/lifespan/on.py @@ -1,28 +1,26 @@ import asyncio import logging from asyncio import Queue -from typing import TYPE_CHECKING, Any, Dict, Union +from typing import Any, Dict, Union from uvicorn import Config - -if TYPE_CHECKING: - from asgiref.typing import ( - LifespanScope, - LifespanShutdownCompleteEvent, - LifespanShutdownEvent, - LifespanShutdownFailedEvent, - LifespanStartupCompleteEvent, - LifespanStartupEvent, - LifespanStartupFailedEvent, - ) - - LifespanReceiveMessage = Union[LifespanStartupEvent, LifespanShutdownEvent] - LifespanSendMessage = Union[ - LifespanStartupFailedEvent, - LifespanShutdownFailedEvent, - LifespanStartupCompleteEvent, - LifespanShutdownCompleteEvent, - ] +from uvicorn._types import ( + LifespanScope, + LifespanShutdownCompleteEvent, + LifespanShutdownEvent, + LifespanShutdownFailedEvent, + LifespanStartupCompleteEvent, + LifespanStartupEvent, + LifespanStartupFailedEvent, +) + +LifespanReceiveMessage = Union[LifespanStartupEvent, LifespanShutdownEvent] +LifespanSendMessage = Union[ + LifespanStartupFailedEvent, + LifespanShutdownFailedEvent, + LifespanStartupCompleteEvent, + LifespanShutdownCompleteEvent, +] STATE_TRANSITION_ERROR = "Got invalid state transition on lifespan protocol." @@ -80,7 +78,7 @@ async def shutdown(self) -> None: async def main(self) -> None: try: app = self.config.loaded_app - scope: LifespanScope = { # type: ignore[typeddict-item] + scope: LifespanScope = { "type": "lifespan", "asgi": {"version": self.config.asgi_version, "spec_version": "2.0"}, "state": self.state, diff --git a/uvicorn/main.py b/uvicorn/main.py index d4fe3bbf4..92edb4580 100644 --- a/uvicorn/main.py +++ b/uvicorn/main.py @@ -9,6 +9,7 @@ import click import uvicorn +from uvicorn._types import ASGIApplication from uvicorn.config import ( HTTP_PROTOCOLS, INTERFACES, @@ -28,9 +29,6 @@ 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())) diff --git a/uvicorn/middleware/asgi2.py b/uvicorn/middleware/asgi2.py index c92b6c8fc..75145f732 100644 --- a/uvicorn/middleware/asgi2.py +++ b/uvicorn/middleware/asgi2.py @@ -1,12 +1,9 @@ -import typing - -if typing.TYPE_CHECKING: - from asgiref.typing import ( - ASGI2Application, - ASGIReceiveCallable, - ASGISendCallable, - Scope, - ) +from uvicorn._types import ( + ASGI2Application, + ASGIReceiveCallable, + ASGISendCallable, + Scope, +) class ASGI2Middleware: diff --git a/uvicorn/middleware/message_logger.py b/uvicorn/middleware/message_logger.py index e3d7a5721..0174bcce6 100644 --- a/uvicorn/middleware/message_logger.py +++ b/uvicorn/middleware/message_logger.py @@ -1,16 +1,14 @@ import logging -from typing import TYPE_CHECKING, Any - -if TYPE_CHECKING: - from asgiref.typing import ( - ASGI3Application, - ASGIReceiveCallable, - ASGIReceiveEvent, - ASGISendCallable, - ASGISendEvent, - WWWScope, - ) +from typing import Any +from uvicorn._types import ( + ASGI3Application, + ASGIReceiveCallable, + ASGIReceiveEvent, + ASGISendCallable, + ASGISendEvent, + WWWScope, +) from uvicorn.logging import TRACE_LOG_LEVEL PLACEHOLDER_FORMAT = { diff --git a/uvicorn/middleware/proxy_headers.py b/uvicorn/middleware/proxy_headers.py index 4b62cf209..3a1e53399 100644 --- a/uvicorn/middleware/proxy_headers.py +++ b/uvicorn/middleware/proxy_headers.py @@ -8,17 +8,16 @@ https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers#Proxies """ -from typing import TYPE_CHECKING, List, Optional, Tuple, Union, cast +from typing import List, Optional, Tuple, Union, cast -if TYPE_CHECKING: - from asgiref.typing import ( - ASGI3Application, - ASGIReceiveCallable, - ASGISendCallable, - HTTPScope, - Scope, - WebSocketScope, - ) +from uvicorn._types import ( + ASGI3Application, + ASGIReceiveCallable, + ASGISendCallable, + HTTPScope, + Scope, + WebSocketScope, +) class ProxyHeadersMiddleware: @@ -61,7 +60,7 @@ async def __call__( # Determine if the incoming request was http or https based on # the X-Forwarded-Proto header. x_forwarded_proto = headers[b"x-forwarded-proto"].decode("latin1") - scope["scheme"] = x_forwarded_proto.strip() # type: ignore[index] + scope["scheme"] = x_forwarded_proto.strip() if b"x-forwarded-for" in headers: # Determine the client address from the last trusted IP in the diff --git a/uvicorn/middleware/wsgi.py b/uvicorn/middleware/wsgi.py index 367c75870..b87984488 100644 --- a/uvicorn/middleware/wsgi.py +++ b/uvicorn/middleware/wsgi.py @@ -4,21 +4,22 @@ import sys import warnings from collections import deque -from typing import TYPE_CHECKING, Deque, Iterable, Optional, Tuple - -if TYPE_CHECKING: - from asgiref.typing import ( - ASGIReceiveCallable, - ASGIReceiveEvent, - ASGISendCallable, - ASGISendEvent, - HTTPRequestEvent, - HTTPResponseBodyEvent, - HTTPResponseStartEvent, - HTTPScope, - ) - -from uvicorn._types import Environ, ExcInfo, StartResponse, WSGIApp +from typing import Deque, Iterable, Optional, Tuple + +from uvicorn._types import ( + ASGIReceiveCallable, + ASGIReceiveEvent, + ASGISendCallable, + ASGISendEvent, + Environ, + ExcInfo, + HTTPRequestEvent, + HTTPResponseBodyEvent, + HTTPResponseStartEvent, + HTTPScope, + StartResponse, + WSGIApp, +) def build_environ( diff --git a/uvicorn/protocols/http/flow_control.py b/uvicorn/protocols/http/flow_control.py index 452c55f43..df642c7b6 100644 --- a/uvicorn/protocols/http/flow_control.py +++ b/uvicorn/protocols/http/flow_control.py @@ -1,14 +1,12 @@ import asyncio -import typing -if typing.TYPE_CHECKING: - from asgiref.typing import ( - ASGIReceiveCallable, - ASGISendCallable, - HTTPResponseBodyEvent, - HTTPResponseStartEvent, - Scope, - ) +from uvicorn._types import ( + ASGIReceiveCallable, + ASGISendCallable, + HTTPResponseBodyEvent, + HTTPResponseStartEvent, + Scope, +) CLOSE_HEADER = (b"connection", b"close") diff --git a/uvicorn/protocols/http/h11_impl.py b/uvicorn/protocols/http/h11_impl.py index d0b202e3a..e9a68fe86 100644 --- a/uvicorn/protocols/http/h11_impl.py +++ b/uvicorn/protocols/http/h11_impl.py @@ -3,7 +3,6 @@ import logging import sys from typing import ( - TYPE_CHECKING, Any, Callable, Dict, @@ -18,6 +17,16 @@ import h11 from h11._connection import DEFAULT_MAX_INCOMPLETE_EVENT_SIZE +from uvicorn._types import ( + ASGI3Application, + ASGIReceiveEvent, + ASGISendEvent, + HTTPDisconnectEvent, + HTTPRequestEvent, + HTTPResponseBodyEvent, + HTTPResponseStartEvent, + HTTPScope, +) from uvicorn.config import Config from uvicorn.logging import TRACE_LOG_LEVEL from uvicorn.protocols.http.flow_control import ( @@ -40,18 +49,6 @@ else: # pragma: py-lt-38 from typing import Literal -if TYPE_CHECKING: - from asgiref.typing import ( - ASGI3Application, - ASGIReceiveEvent, - ASGISendEvent, - HTTPDisconnectEvent, - HTTPRequestEvent, - HTTPResponseBodyEvent, - HTTPResponseStartEvent, - HTTPScope, - ) - H11Event = Union[ h11.Request, @@ -226,7 +223,7 @@ def handle_events(self) -> None: elif event_type is h11.Request: self.headers = [(key.lower(), value) for key, value in event.headers] raw_path, _, query_string = event.target.partition(b"?") - self.scope = { # type: ignore[typeddict-item] + self.scope = { "type": "http", "asgi": { "version": self.config.asgi_version, diff --git a/uvicorn/protocols/http/httptools_impl.py b/uvicorn/protocols/http/httptools_impl.py index 59db0fa04..a1000fb08 100644 --- a/uvicorn/protocols/http/httptools_impl.py +++ b/uvicorn/protocols/http/httptools_impl.py @@ -7,7 +7,6 @@ from asyncio.events import TimerHandle from collections import deque from typing import ( - TYPE_CHECKING, Any, Callable, Deque, @@ -21,6 +20,16 @@ import httptools +from uvicorn._types import ( + ASGI3Application, + ASGIReceiveEvent, + ASGISendEvent, + HTTPDisconnectEvent, + HTTPRequestEvent, + HTTPResponseBodyEvent, + HTTPResponseStartEvent, + HTTPScope, +) from uvicorn.config import Config from uvicorn.logging import TRACE_LOG_LEVEL from uvicorn.protocols.http.flow_control import ( @@ -43,18 +52,6 @@ else: # pragma: py-lt-38 from typing import Literal -if TYPE_CHECKING: - from asgiref.typing import ( - ASGI3Application, - ASGIReceiveEvent, - ASGISendEvent, - HTTPDisconnectEvent, - HTTPRequestEvent, - HTTPResponseBodyEvent, - HTTPResponseStartEvent, - HTTPScope, - ) - HEADER_RE = re.compile(b'[\x00-\x1F\x7F()<>@,;:[]={} \t\\"]') HEADER_VALUE_RE = re.compile(b"[\x00-\x1F\x7F]") diff --git a/uvicorn/protocols/utils.py b/uvicorn/protocols/utils.py index fbd4b4d5d..d0697fe73 100644 --- a/uvicorn/protocols/utils.py +++ b/uvicorn/protocols/utils.py @@ -1,9 +1,8 @@ import asyncio import urllib.parse -from typing import TYPE_CHECKING, Optional, Tuple +from typing import Optional, Tuple -if TYPE_CHECKING: - from asgiref.typing import WWWScope +from uvicorn._types import WWWScope def get_remote_addr(transport: asyncio.Transport) -> Optional[Tuple[str, int]]: diff --git a/uvicorn/protocols/websockets/websockets_impl.py b/uvicorn/protocols/websockets/websockets_impl.py index 3ee3086dd..19f4f326f 100644 --- a/uvicorn/protocols/websockets/websockets_impl.py +++ b/uvicorn/protocols/websockets/websockets_impl.py @@ -3,7 +3,6 @@ import logging import sys from typing import ( - TYPE_CHECKING, Any, Dict, List, @@ -23,6 +22,16 @@ from websockets.server import WebSocketServerProtocol from websockets.typing import Subprotocol +from uvicorn._types import ( + ASGISendEvent, + WebSocketAcceptEvent, + WebSocketCloseEvent, + WebSocketConnectEvent, + WebSocketDisconnectEvent, + WebSocketReceiveEvent, + WebSocketScope, + WebSocketSendEvent, +) from uvicorn.config import Config from uvicorn.logging import TRACE_LOG_LEVEL from uvicorn.protocols.utils import ( @@ -38,18 +47,6 @@ else: # pragma: py-lt-38 from typing import Literal -if TYPE_CHECKING: - from asgiref.typing import ( - ASGISendEvent, - WebSocketAcceptEvent, - WebSocketCloseEvent, - WebSocketConnectEvent, - WebSocketDisconnectEvent, - WebSocketReceiveEvent, - WebSocketScope, - WebSocketSendEvent, - ) - class Server: closing = False @@ -189,7 +186,7 @@ async def process_request( for name, value in headers.raw_items() ] - self.scope = { # type: ignore[typeddict-item] + self.scope = { "type": "websocket", "asgi": {"version": self.config.asgi_version, "spec_version": "2.3"}, "http_version": "1.1", diff --git a/uvicorn/protocols/websockets/wsproto_impl.py b/uvicorn/protocols/websockets/wsproto_impl.py index 206eb6c30..90aaf9fd0 100644 --- a/uvicorn/protocols/websockets/wsproto_impl.py +++ b/uvicorn/protocols/websockets/wsproto_impl.py @@ -10,6 +10,15 @@ from wsproto.extensions import Extension, PerMessageDeflate from wsproto.utilities import RemoteProtocolError +from uvicorn._types import ( + ASGISendEvent, + WebSocketAcceptEvent, + WebSocketCloseEvent, + WebSocketEvent, + WebSocketReceiveEvent, + WebSocketScope, + WebSocketSendEvent, +) from uvicorn.config import Config from uvicorn.logging import TRACE_LOG_LEVEL from uvicorn.protocols.utils import ( @@ -20,24 +29,6 @@ ) from uvicorn.server import ServerState -if typing.TYPE_CHECKING: - from asgiref.typing import ( - ASGISendEvent, - WebSocketAcceptEvent, - WebSocketCloseEvent, - WebSocketConnectEvent, - WebSocketDisconnectEvent, - WebSocketReceiveEvent, - WebSocketScope, - WebSocketSendEvent, - ) - - WebSocketEvent = typing.Union[ - "WebSocketReceiveEvent", - "WebSocketDisconnectEvent", - "WebSocketConnectEvent", - ] - if sys.version_info < (3, 8): # pragma: py-gte-38 from typing_extensions import Literal else: # pragma: py-lt-38 @@ -172,7 +163,7 @@ def handle_connect(self, event: events.Request) -> None: headers = [(b"host", event.host.encode())] headers += [(key.lower(), value) for key, value in event.extra_headers] raw_path, _, query_string = event.target.partition("?") - self.scope: "WebSocketScope" = { # type: ignore[typeddict-item] + self.scope: "WebSocketScope" = { "type": "websocket", "asgi": {"version": self.config.asgi_version, "spec_version": "2.3"}, "http_version": "1.1", @@ -185,7 +176,6 @@ def handle_connect(self, event: events.Request) -> None: "query_string": query_string.encode("ascii"), "headers": headers, "subprotocols": event.subprotocols, - "extensions": None, "state": self.app_state.copy(), } self.queue.put_nowait({"type": "websocket.connect"}) From 7f7535121909a77ab78850b48017d9755d357900 Mon Sep 17 00:00:00 2001 From: Marcelo Trylesinski Date: Thu, 8 Jun 2023 10:03:46 +0200 Subject: [PATCH 2/3] Remove dunder all --- uvicorn/_types.py | 39 --------------------------------------- 1 file changed, 39 deletions(-) diff --git a/uvicorn/_types.py b/uvicorn/_types.py index d381f39e0..3cfc379e8 100644 --- a/uvicorn/_types.py +++ b/uvicorn/_types.py @@ -61,45 +61,6 @@ # ASGI - -__all__ = ( - "ASGIVersions", - "HTTPScope", - "WebSocketScope", - "LifespanScope", - "WWWScope", - "Scope", - "HTTPRequestEvent", - "HTTPResponseStartEvent", - "HTTPResponseBodyEvent", - "HTTPResponseTrailersEvent", - "HTTPServerPushEvent", - "HTTPDisconnectEvent", - "WebSocketConnectEvent", - "WebSocketAcceptEvent", - "WebSocketReceiveEvent", - "WebSocketSendEvent", - "WebSocketResponseStartEvent", - "WebSocketResponseBodyEvent", - "WebSocketDisconnectEvent", - "WebSocketCloseEvent", - "LifespanStartupEvent", - "LifespanShutdownEvent", - "LifespanStartupCompleteEvent", - "LifespanStartupFailedEvent", - "LifespanShutdownCompleteEvent", - "LifespanShutdownFailedEvent", - "ASGIReceiveEvent", - "ASGISendEvent", - "ASGIReceiveCallable", - "ASGISendCallable", - "ASGI2Protocol", - "ASGI2Application", - "ASGI3Application", - "ASGIApplication", -) - - class ASGIVersions(TypedDict): spec_version: str version: Union[Literal["2.0"], Literal["3.0"]] From dddc4bb83542b01db15218aa7b0ba5b42fe8fc47 Mon Sep 17 00:00:00 2001 From: Marcelo Trylesinski Date: Thu, 8 Jun 2023 11:48:51 +0200 Subject: [PATCH 3/3] Fix black --- uvicorn/_types.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/uvicorn/_types.py b/uvicorn/_types.py index 3cfc379e8..394d68be1 100644 --- a/uvicorn/_types.py +++ b/uvicorn/_types.py @@ -59,8 +59,8 @@ StartResponse = Callable[[str, Iterable[Tuple[str, str]], Optional[ExcInfo]], None] WSGIApp = Callable[[Environ, StartResponse], Union[Iterable[bytes], BaseException]] -# ASGI +# ASGI class ASGIVersions(TypedDict): spec_version: str version: Union[Literal["2.0"], Literal["3.0"]]