Compare commits

...

7 Commits

Author SHA1 Message Date
Marcelo Trylesinski
23984c3457
Merge branch 'master' into drop-python-3.8 2024-12-15 00:19:36 +01:00
Marcelo Trylesinski
b0bd5e87a3 fix lint 2024-12-14 23:28:58 +01:00
Marcelo Trylesinski
2af31eb8bb readd type ignore 2024-12-14 23:23:17 +01:00
Marcelo Trylesinski
c7498b3cba Drop Python 3.8 2024-12-14 23:13:22 +01:00
Marcelo Trylesinski
90f536ea21 try to drop it... 2024-12-14 22:31:32 +01:00
Marcelo Trylesinski
70562c7140 Drop Python 3.8 2024-12-14 18:18:59 +01:00
Marcelo Trylesinski
5f3e508407 Drop Python 3.8 2024-12-14 18:06:40 +01:00
18 changed files with 133 additions and 138 deletions

View File

@ -15,7 +15,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
os: [windows-latest, ubuntu-latest, macos-latest]
steps:
- uses: "actions/checkout@v4"

View File

@ -8,7 +8,7 @@ dynamic = ["version"]
description = "The lightning-fast ASGI server."
readme = "README.md"
license = "BSD-3-Clause"
requires-python = ">=3.8"
requires-python = ">=3.9"
authors = [
{ name = "Tom Christie", email = "tom@tomchristie.com" },
{ name = "Marcelo Trylesinski", email = "marcelotryle@gmail.com" },
@ -20,7 +20,6 @@ classifiers = [
"License :: OSI Approved :: BSD License",
"Operating System :: OS Independent",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
@ -93,6 +92,9 @@ filterwarnings = [
"ignore:Uvicorn's native WSGI implementation is deprecated.*:DeprecationWarning",
"ignore: 'cgi' is deprecated and slated for removal in Python 3.13:DeprecationWarning",
"ignore: remove second argument of ws_handler:DeprecationWarning:websockets",
"ignore: websockets.legacy is deprecated.*:DeprecationWarning:websockets",
"ignore: websockets.server.WebSocketServerProtocol is deprecated.*:DeprecationWarning:websockets",
"ignore: websockets.exceptions.InvalidStatusCode.*:DeprecationWarning",
]
[tool.coverage.run]
@ -127,8 +129,6 @@ py-win32 = "sys_platform == 'win32'"
py-not-win32 = "sys_platform != 'win32'"
py-linux = "sys_platform == 'linux'"
py-darwin = "sys_platform == 'darwin'"
py-gte-38 = "sys_version_info >= (3, 8)"
py-lt-38 = "sys_version_info < (3, 8)"
py-gte-39 = "sys_version_info >= (3, 9)"
py-lt-39 = "sys_version_info < (3, 9)"
py-gte-310 = "sys_version_info >= (3, 10)"

View File

@ -7,7 +7,7 @@ h11 @ git+https://github.com/python-hyper/h11.git@master
# Explicit optionals
a2wsgi==1.10.7
wsproto==1.2.0
websockets==13.1
websockets==14.1
# Packaging
build==1.2.2.post1
@ -20,11 +20,9 @@ pytest-mock==3.14.0
mypy==1.13.0
types-click==7.1.8
types-pyyaml==6.0.12.20240917
trustme==1.1.0; python_version < '3.9'
trustme==1.2.0; python_version >= '3.9'
trustme==1.2.0
cryptography==44.0.0
coverage==7.6.1; python_version < '3.9'
coverage==7.6.9; python_version >= '3.9'
coverage==7.6.9
coverage-conditional-plugin==0.9.0
httpx==0.28.1

View File

@ -8,8 +8,7 @@ import typing
import httpx
import pytest
import websockets
import websockets.client
from websockets.asyncio.client import connect
from tests.utils import run_server
from uvicorn import Config
@ -104,9 +103,9 @@ async def test_trace_logging_on_ws_protocol(
elif message["type"] == "websocket.disconnect":
break
async def open_connection(url):
async with websockets.client.connect(url) as websocket:
return websocket.open
async def open_connection(url: str):
async with connect(url):
return True
config = Config(
app=websocket_app,

View File

@ -5,7 +5,7 @@ from typing import TYPE_CHECKING
import httpx
import httpx._transports.asgi
import pytest
import websockets.client
from websockets.asyncio.client import connect
from tests.response import Response
from tests.utils import run_server
@ -478,7 +478,7 @@ async def test_proxy_headers_websocket_x_forwarded_proto(
async with run_server(config):
url = f"ws://127.0.0.1:{unused_tcp_port}"
headers = {X_FORWARDED_FOR: "1.2.3.4", X_FORWARDED_PROTO: forwarded_proto}
async with websockets.client.connect(url, extra_headers=headers) as websocket:
async with connect(url, additional_headers=headers) as websocket:
data = await websocket.recv()
assert data == expected

View File

@ -2,7 +2,8 @@ from __future__ import annotations
import io
import sys
from typing import AsyncGenerator, Callable
from collections.abc import AsyncGenerator
from typing import Callable
import a2wsgi
import httpx

View File

@ -6,10 +6,9 @@ from copy import deepcopy
import httpx
import pytest
import websockets
import websockets.client
import websockets.exceptions
from typing_extensions import TypedDict
from websockets.asyncio.client import ClientConnection, connect
from websockets.exceptions import ConnectionClosed, ConnectionClosedError, InvalidHandshake, InvalidStatus
from websockets.extensions.permessage_deflate import ClientPerMessageDeflateFactory
from websockets.typing import Subprotocol
@ -128,8 +127,8 @@ async def test_accept_connection(ws_protocol_cls: WSProtocol, http_protocol_cls:
await self.send({"type": "websocket.accept"})
async def open_connection(url: str):
async with websockets.client.connect(url) as websocket:
return websocket.open
async with connect(url):
return True
config = Config(app=App, ws=ws_protocol_cls, http=http_protocol_cls, lifespan="off", port=unused_tcp_port)
async with run_server(config):
@ -144,7 +143,7 @@ async def test_shutdown(ws_protocol_cls: WSProtocol, http_protocol_cls: HTTPProt
config = Config(app=App, ws=ws_protocol_cls, http=http_protocol_cls, lifespan="off", port=unused_tcp_port)
async with run_server(config) as server:
async with websockets.client.connect(f"ws://127.0.0.1:{unused_tcp_port}"):
async with connect(f"ws://127.0.0.1:{unused_tcp_port}"):
# Attempt shutdown while connection is still open
await server.shutdown()
@ -158,8 +157,8 @@ async def test_supports_permessage_deflate_extension(
async def open_connection(url: str):
extension_factories = [ClientPerMessageDeflateFactory()]
async with websockets.client.connect(url, extensions=extension_factories) as websocket:
return [extension.name for extension in websocket.extensions]
async with connect(url, extensions=extension_factories) as websocket:
return [extension.name for extension in websocket.protocol.extensions]
config = Config(app=App, ws=ws_protocol_cls, http=http_protocol_cls, lifespan="off", port=unused_tcp_port)
async with run_server(config):
@ -178,8 +177,8 @@ async def test_can_disable_permessage_deflate_extension(
# enable per-message deflate on the client, so that we can check the server
# won't support it when it's disabled.
extension_factories = [ClientPerMessageDeflateFactory()]
async with websockets.client.connect(url, extensions=extension_factories) as websocket:
return [extension.name for extension in websocket.extensions]
async with connect(url, extensions=extension_factories) as websocket:
return [extension.name for extension in websocket.protocol.extensions]
config = Config(
app=App,
@ -201,8 +200,8 @@ async def test_close_connection(ws_protocol_cls: WSProtocol, http_protocol_cls:
async def open_connection(url: str):
try:
await websockets.client.connect(url)
except websockets.exceptions.InvalidHandshake:
await connect(url)
except InvalidHandshake:
return False
return True # pragma: no cover
@ -222,8 +221,8 @@ async def test_headers(ws_protocol_cls: WSProtocol, http_protocol_cls: HTTPProto
await self.send({"type": "websocket.accept"})
async def open_connection(url: str):
async with websockets.client.connect(url, extra_headers=[("username", "abraão")]) as websocket:
return websocket.open
async with connect(url, additional_headers=[("username", "abraão")]):
return True
config = Config(app=App, ws=ws_protocol_cls, http=http_protocol_cls, lifespan="off", port=unused_tcp_port)
async with run_server(config):
@ -237,8 +236,9 @@ async def test_extra_headers(ws_protocol_cls: WSProtocol, http_protocol_cls: HTT
await self.send({"type": "websocket.accept", "headers": [(b"extra", b"header")]})
async def open_connection(url: str):
async with websockets.client.connect(url) as websocket:
return websocket.response_headers
async with connect(url) as websocket:
assert websocket.response
return websocket.response.headers
config = Config(app=App, ws=ws_protocol_cls, http=http_protocol_cls, lifespan="off", port=unused_tcp_port)
async with run_server(config):
@ -256,8 +256,8 @@ async def test_path_and_raw_path(ws_protocol_cls: WSProtocol, http_protocol_cls:
await self.send({"type": "websocket.accept"})
async def open_connection(url: str):
async with websockets.client.connect(url) as websocket:
return websocket.open
async with connect(url):
return True
config = Config(app=App, ws=ws_protocol_cls, http=http_protocol_cls, lifespan="off", port=unused_tcp_port)
async with run_server(config):
@ -274,7 +274,7 @@ async def test_send_text_data_to_client(
await self.send({"type": "websocket.send", "text": "123"})
async def get_data(url: str):
async with websockets.client.connect(url) as websocket:
async with connect(url) as websocket:
return await websocket.recv()
config = Config(app=App, ws=ws_protocol_cls, http=http_protocol_cls, lifespan="off", port=unused_tcp_port)
@ -292,7 +292,7 @@ async def test_send_binary_data_to_client(
await self.send({"type": "websocket.send", "bytes": b"123"})
async def get_data(url: str):
async with websockets.client.connect(url) as websocket:
async with connect(url) as websocket:
return await websocket.recv()
config = Config(app=App, ws=ws_protocol_cls, http=http_protocol_cls, lifespan="off", port=unused_tcp_port)
@ -311,7 +311,7 @@ async def test_send_and_close_connection(
await self.send({"type": "websocket.close"})
async def get_data(url: str):
async with websockets.client.connect(url) as websocket:
async with connect(url) as websocket:
data = await websocket.recv()
is_open = True
try:
@ -340,7 +340,7 @@ async def test_send_text_data_to_server(
await self.send({"type": "websocket.send", "text": _text})
async def send_text(url: str):
async with websockets.client.connect(url) as websocket:
async with connect(url) as websocket:
await websocket.send("abc")
return await websocket.recv()
@ -363,7 +363,7 @@ async def test_send_binary_data_to_server(
await self.send({"type": "websocket.send", "bytes": _bytes})
async def send_text(url: str):
async with websockets.client.connect(url) as websocket:
async with connect(url) as websocket:
await websocket.send(b"abc")
return await websocket.recv()
@ -385,7 +385,7 @@ async def test_send_after_protocol_close(
await self.send({"type": "websocket.send", "text": "123"})
async def get_data(url: str):
async with websockets.client.connect(url) as websocket:
async with connect(url) as websocket:
data = await websocket.recv()
is_open = True
try:
@ -405,14 +405,14 @@ async def test_missing_handshake(ws_protocol_cls: WSProtocol, http_protocol_cls:
async def app(scope: Scope, receive: ASGIReceiveCallable, send: ASGISendCallable):
pass
async def connect(url: str):
await websockets.client.connect(url)
async def open_connection(url: str):
await connect(url)
config = Config(app=app, ws=ws_protocol_cls, http=http_protocol_cls, lifespan="off", port=unused_tcp_port)
async with run_server(config):
with pytest.raises(websockets.exceptions.InvalidStatusCode) as exc_info:
await connect(f"ws://127.0.0.1:{unused_tcp_port}")
assert exc_info.value.status_code == 500
with pytest.raises(InvalidStatus) as exc_info:
await open_connection(f"ws://127.0.0.1:{unused_tcp_port}")
assert exc_info.value.response.status_code == 500
async def test_send_before_handshake(
@ -421,14 +421,14 @@ async def test_send_before_handshake(
async def app(scope: Scope, receive: ASGIReceiveCallable, send: ASGISendCallable):
await send({"type": "websocket.send", "text": "123"})
async def connect(url: str):
await websockets.client.connect(url)
async def open_connection(url: str):
await connect(url)
config = Config(app=app, ws=ws_protocol_cls, http=http_protocol_cls, lifespan="off", port=unused_tcp_port)
async with run_server(config):
with pytest.raises(websockets.exceptions.InvalidStatusCode) as exc_info:
await connect(f"ws://127.0.0.1:{unused_tcp_port}")
assert exc_info.value.status_code == 500
with pytest.raises(InvalidStatus) as exc_info:
await open_connection(f"ws://127.0.0.1:{unused_tcp_port}")
assert exc_info.value.response.status_code == 500
async def test_duplicate_handshake(ws_protocol_cls: WSProtocol, http_protocol_cls: HTTPProtocol, unused_tcp_port: int):
@ -438,10 +438,10 @@ async def test_duplicate_handshake(ws_protocol_cls: WSProtocol, http_protocol_cl
config = Config(app=app, ws=ws_protocol_cls, http=http_protocol_cls, lifespan="off", port=unused_tcp_port)
async with run_server(config):
async with websockets.client.connect(f"ws://127.0.0.1:{unused_tcp_port}") as websocket:
with pytest.raises(websockets.exceptions.ConnectionClosed):
async with connect(f"ws://127.0.0.1:{unused_tcp_port}") as websocket:
with pytest.raises(ConnectionClosed):
_ = await websocket.recv()
assert websocket.close_code == 1006
assert websocket.protocol.close_code == 1006
async def test_asgi_return_value(ws_protocol_cls: WSProtocol, http_protocol_cls: HTTPProtocol, unused_tcp_port: int):
@ -456,10 +456,10 @@ async def test_asgi_return_value(ws_protocol_cls: WSProtocol, http_protocol_cls:
config = Config(app=app, ws=ws_protocol_cls, http=http_protocol_cls, lifespan="off", port=unused_tcp_port)
async with run_server(config):
async with websockets.client.connect(f"ws://127.0.0.1:{unused_tcp_port}") as websocket:
with pytest.raises(websockets.exceptions.ConnectionClosed):
async with connect(f"ws://127.0.0.1:{unused_tcp_port}") as websocket:
with pytest.raises(ConnectionClosed):
_ = await websocket.recv()
assert websocket.close_code == 1006
assert websocket.protocol.close_code == 1006
@pytest.mark.parametrize("code", [None, 1000, 1001])
@ -491,13 +491,13 @@ async def test_app_close(
config = Config(app=app, ws=ws_protocol_cls, http=http_protocol_cls, lifespan="off", port=unused_tcp_port)
async with run_server(config):
async with websockets.client.connect(f"ws://127.0.0.1:{unused_tcp_port}") as websocket:
async with connect(f"ws://127.0.0.1:{unused_tcp_port}") as websocket:
await websocket.ping()
await websocket.send("abc")
with pytest.raises(websockets.exceptions.ConnectionClosed):
with pytest.raises(ConnectionClosed):
await websocket.recv()
assert websocket.close_code == (code or 1000)
assert websocket.close_reason == (reason or "")
assert websocket.protocol.close_code == (code or 1000)
assert websocket.protocol.close_reason == (reason or "")
async def test_client_close(ws_protocol_cls: WSProtocol, http_protocol_cls: HTTPProtocol, unused_tcp_port: int):
@ -516,7 +516,7 @@ async def test_client_close(ws_protocol_cls: WSProtocol, http_protocol_cls: HTTP
break
async def websocket_session(url: str):
async with websockets.client.connect(url) as websocket:
async with connect(url) as websocket:
await websocket.ping()
await websocket.send("abc")
await websocket.close(code=1001, reason="custom reason")
@ -553,7 +553,7 @@ async def test_client_connection_lost(
port=unused_tcp_port,
)
async with run_server(config):
async with websockets.client.connect(f"ws://127.0.0.1:{unused_tcp_port}") as websocket:
async with connect(f"ws://127.0.0.1:{unused_tcp_port}") as websocket:
websocket.transport.close()
await asyncio.sleep(0.1)
got_disconnect_event_before_shutdown = got_disconnect_event
@ -581,7 +581,7 @@ async def test_client_connection_lost_on_send(
config = Config(app=app, ws=ws_protocol_cls, http=http_protocol_cls, lifespan="off", port=unused_tcp_port)
async with run_server(config):
url = f"ws://127.0.0.1:{unused_tcp_port}"
async with websockets.client.connect(url):
async with connect(url):
await asyncio.sleep(0.1)
disconnect.set()
@ -646,11 +646,11 @@ async def test_send_close_on_server_shutdown(
disconnect_message = message
break
websocket: websockets.client.WebSocketClientProtocol | None = None
websocket: ClientConnection | None = None
async def websocket_session(uri: str):
nonlocal websocket
async with websockets.client.connect(uri) as ws_connection:
async with connect(uri) as ws_connection:
websocket = ws_connection
await server_shutdown_event.wait()
@ -680,9 +680,7 @@ async def test_subprotocols(
await self.send({"type": "websocket.accept", "subprotocol": subprotocol})
async def get_subprotocol(url: str):
async with websockets.client.connect(
url, subprotocols=[Subprotocol("proto1"), Subprotocol("proto2")]
) as websocket:
async with connect(url, subprotocols=[Subprotocol("proto1"), Subprotocol("proto2")]) as websocket:
return websocket.subprotocol
config = Config(app=App, ws=ws_protocol_cls, http=http_protocol_cls, lifespan="off", port=unused_tcp_port)
@ -692,7 +690,7 @@ async def test_subprotocols(
MAX_WS_BYTES = 1024 * 1024 * 16
MAX_WS_BYTES_PLUS1 = MAX_WS_BYTES + 1
MAX_WS_BYTES_PLUS1 = MAX_WS_BYTES + 10
@pytest.mark.parametrize(
@ -735,15 +733,15 @@ async def test_send_binary_data_to_server_bigger_than_default_on_websockets(
port=unused_tcp_port,
)
async with run_server(config):
async with websockets.client.connect(f"ws://127.0.0.1:{unused_tcp_port}", max_size=client_size_sent) as ws:
async with connect(f"ws://127.0.0.1:{unused_tcp_port}", max_size=client_size_sent) as ws:
await ws.send(b"\x01" * client_size_sent)
if expected_result == 0:
data = await ws.recv()
assert data == b"\x01" * client_size_sent
else:
with pytest.raises(websockets.exceptions.ConnectionClosedError):
with pytest.raises(ConnectionClosedError):
await ws.recv()
assert ws.close_code == expected_result
assert ws.protocol.close_code == expected_result
async def test_server_reject_connection(
@ -768,10 +766,10 @@ async def test_server_reject_connection(
disconnected_message = await receive()
async def websocket_session(url: str):
with pytest.raises(websockets.exceptions.InvalidStatusCode) as exc_info:
async with websockets.client.connect(url):
with pytest.raises(InvalidStatus) as exc_info:
async with connect(url):
pass # pragma: no cover
assert exc_info.value.status_code == 403
assert exc_info.value.response.status_code == 403
config = Config(app=app, ws=ws_protocol_cls, http=http_protocol_cls, lifespan="off", port=unused_tcp_port)
async with run_server(config):
@ -938,10 +936,10 @@ async def test_server_reject_connection_with_invalid_msg(
await send(message)
async def websocket_session(url: str):
with pytest.raises(websockets.exceptions.InvalidStatusCode) as exc_info:
async with websockets.client.connect(url):
with pytest.raises(InvalidStatus) as exc_info:
async with connect(url):
pass # pragma: no cover
assert exc_info.value.status_code == 404
assert exc_info.value.response.status_code == 404
config = Config(app=app, ws=ws_protocol_cls, http=http_protocol_cls, lifespan="off", port=unused_tcp_port)
async with run_server(config):
@ -969,10 +967,10 @@ async def test_server_reject_connection_with_missing_body(
# no further message
async def websocket_session(url: str):
with pytest.raises(websockets.exceptions.InvalidStatusCode) as exc_info:
async with websockets.client.connect(url):
with pytest.raises(InvalidStatus) as exc_info:
async with connect(url):
pass # pragma: no cover
assert exc_info.value.status_code == 404
assert exc_info.value.response.status_code == 404
config = Config(app=app, ws=ws_protocol_cls, http=http_protocol_cls, lifespan="off", port=unused_tcp_port)
async with run_server(config):
@ -1010,17 +1008,17 @@ async def test_server_multiple_websocket_http_response_start_events(
exception_message = str(exc)
async def websocket_session(url: str):
with pytest.raises(websockets.exceptions.InvalidStatusCode) as exc_info:
async with websockets.client.connect(url):
with pytest.raises(InvalidStatus) as exc_info:
async with connect(url):
pass # pragma: no cover
assert exc_info.value.status_code == 404
assert exc_info.value.response.status_code == 404
config = Config(app=app, ws=ws_protocol_cls, http=http_protocol_cls, lifespan="off", port=unused_tcp_port)
async with run_server(config):
await websocket_session(f"ws://127.0.0.1:{unused_tcp_port}")
assert exception_message == (
"Expected ASGI message 'websocket.http.response.body' but got " "'websocket.http.response.start'."
"Expected ASGI message 'websocket.http.response.body' but got 'websocket.http.response.start'."
)
@ -1049,7 +1047,7 @@ async def test_server_can_read_messages_in_buffer_after_close(
config = Config(app=App, ws=ws_protocol_cls, http=http_protocol_cls, lifespan="off", port=unused_tcp_port)
async with run_server(config):
async with websockets.client.connect(f"ws://127.0.0.1:{unused_tcp_port}") as websocket:
async with connect(f"ws://127.0.0.1:{unused_tcp_port}") as websocket:
await websocket.send(b"abc")
await websocket.send(b"abc")
await websocket.send(b"abc")
@ -1066,8 +1064,9 @@ async def test_default_server_headers(
await self.send({"type": "websocket.accept"})
async def open_connection(url: str):
async with websockets.client.connect(url) as websocket:
return websocket.response_headers
async with connect(url) as websocket:
assert websocket.response
return websocket.response.headers
config = Config(app=App, ws=ws_protocol_cls, http=http_protocol_cls, lifespan="off", port=unused_tcp_port)
async with run_server(config):
@ -1081,8 +1080,9 @@ async def test_no_server_headers(ws_protocol_cls: WSProtocol, http_protocol_cls:
await self.send({"type": "websocket.accept"})
async def open_connection(url: str):
async with websockets.client.connect(url) as websocket:
return websocket.response_headers
async with connect(url) as websocket:
assert websocket.response
return websocket.response.headers
config = Config(
app=App,
@ -1104,8 +1104,9 @@ async def test_no_date_header_on_wsproto(http_protocol_cls: HTTPProtocol, unused
await self.send({"type": "websocket.accept"})
async def open_connection(url: str):
async with websockets.client.connect(url) as websocket:
return websocket.response_headers
async with connect(url) as websocket:
assert websocket.response
return websocket.response.headers
config = Config(
app=App,
@ -1136,8 +1137,9 @@ async def test_multiple_server_header(
)
async def open_connection(url: str):
async with websockets.client.connect(url) as websocket:
return websocket.response_headers
async with connect(url) as websocket:
assert websocket.response
return websocket.response.headers
config = Config(app=App, ws=ws_protocol_cls, http=http_protocol_cls, lifespan="off", port=unused_tcp_port)
async with run_server(config):
@ -1172,8 +1174,8 @@ async def test_lifespan_state(ws_protocol_cls: WSProtocol, http_protocol_cls: HT
await self.send({"type": "websocket.accept"})
async def open_connection(url: str):
async with websockets.client.connect(url) as websocket:
return websocket.open
async with connect(url):
return True
async def app_wrapper(scope: Scope, receive: ASGIReceiveCallable, send: ASGISendCallable):
if scope["type"] == "lifespan":

View File

@ -4,10 +4,11 @@ import platform
import signal
import socket
import sys
from collections.abc import Generator
from pathlib import Path
from threading import Thread
from time import sleep
from typing import Callable, Generator
from typing import Callable
import pytest
from pytest_mock import MockerFixture

View File

@ -3,9 +3,9 @@ import importlib
import os
import platform
import sys
from collections.abc import Iterator
from pathlib import Path
from textwrap import dedent
from typing import Iterator
from unittest import mock
import pytest

View File

@ -5,7 +5,9 @@ import contextlib
import logging
import signal
import sys
from typing import Callable, ContextManager, Generator
from collections.abc import Generator
from contextlib import AbstractContextManager
from typing import Callable
import httpx
import pytest
@ -62,7 +64,7 @@ else: # pragma: py-win32
@pytest.mark.parametrize("exception_signal", signals)
@pytest.mark.parametrize("capture_signal", signal_captures)
async def test_server_interrupt(
exception_signal: signal.Signals, capture_signal: Callable[[signal.Signals], ContextManager[None]]
exception_signal: signal.Signals, capture_signal: Callable[[signal.Signals], AbstractContextManager[None]]
): # pragma: py-win32
"""Test interrupting a Server that is run explicitly inside asyncio"""

View File

@ -32,20 +32,8 @@ from __future__ import annotations
import sys
import types
from typing import (
Any,
Awaitable,
Callable,
Iterable,
Literal,
MutableMapping,
Optional,
Protocol,
Tuple,
Type,
TypedDict,
Union,
)
from collections.abc import Awaitable, Iterable, MutableMapping
from typing import Any, Callable, Literal, Optional, Protocol, TypedDict, Union
if sys.version_info >= (3, 11): # pragma: py-lt-311
from typing import NotRequired
@ -54,8 +42,8 @@ else: # pragma: py-gte-311
# WSGI
Environ = MutableMapping[str, Any]
ExcInfo = Tuple[Type[BaseException], BaseException, Optional[types.TracebackType]]
StartResponse = Callable[[str, Iterable[Tuple[str, str]], Optional[ExcInfo]], None]
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]]
@ -281,7 +269,7 @@ class ASGI2Protocol(Protocol):
async def __call__(self, receive: ASGIReceiveCallable, send: ASGISendCallable) -> None: ... # pragma: no cover
ASGI2Application = Type[ASGI2Protocol]
ASGI2Application = type[ASGI2Protocol]
ASGI3Application = Callable[
[
Scope,

View File

@ -9,9 +9,10 @@ import os
import socket
import ssl
import sys
from collections.abc import Awaitable
from configparser import RawConfigParser
from pathlib import Path
from typing import IO, Any, Awaitable, Callable, Literal
from typing import IO, Any, Callable, Literal
import click

View File

@ -6,7 +6,7 @@ import io
import sys
import warnings
from collections import deque
from typing import Iterable
from collections.abc import Iterable
from uvicorn._types import (
ASGIReceiveCallable,

View File

@ -3,7 +3,8 @@ from __future__ import annotations
import asyncio
import http
import logging
from typing import Any, Literal, Optional, Sequence, cast
from collections.abc import Sequence
from typing import Any, Literal, Optional, cast
from urllib.parse import unquote
import websockets
@ -12,8 +13,7 @@ from websockets.datastructures import Headers
from websockets.exceptions import ConnectionClosed
from websockets.extensions.base import ServerExtensionFactory
from websockets.extensions.permessage_deflate import ServerPerMessageDeflateFactory
from websockets.legacy.server import HTTPResponse
from websockets.server import WebSocketServerProtocol
from websockets.legacy.server import HTTPResponse, WebSocketServerProtocol
from websockets.typing import Subprotocol
from uvicorn._types import (

View File

@ -149,12 +149,13 @@ class WSProtocol(asyncio.Protocol):
self.writable.set() # pragma: full coverage
def shutdown(self) -> None:
if self.handshake_complete:
self.queue.put_nowait({"type": "websocket.disconnect", "code": 1012})
output = self.conn.send(wsproto.events.CloseConnection(code=1012))
self.transport.write(output)
else:
self.send_500_response()
if not self.response_started:
if self.handshake_complete:
self.queue.put_nowait({"type": "websocket.disconnect", "code": 1012})
output = self.conn.send(wsproto.events.CloseConnection(code=1012))
self.transport.write(output)
else:
self.send_500_response()
self.transport.close()
def on_task_complete(self, task: asyncio.Task[None]) -> None:
@ -221,13 +222,15 @@ class WSProtocol(asyncio.Protocol):
def send_500_response(self) -> None:
if self.response_started or self.handshake_complete:
return # we cannot send responses anymore
reject_data = b"Internal Server Error"
headers: list[tuple[bytes, bytes]] = [
(b"content-type", b"text/plain; charset=utf-8"),
(b"content-length", str(len(reject_data)).encode()),
(b"connection", b"close"),
(b"content-length", b"21"),
]
output = self.conn.send(wsproto.events.RejectConnection(status_code=500, headers=headers, has_body=True))
output += self.conn.send(wsproto.events.RejectData(data=b"Internal Server Error"))
output += self.conn.send(wsproto.events.RejectData(data=reject_data))
self.transport.write(output)
async def run_asgi(self) -> None:

View File

@ -10,9 +10,10 @@ import socket
import sys
import threading
import time
from collections.abc import Generator, Sequence
from email.utils import formatdate
from types import FrameType
from typing import TYPE_CHECKING, Generator, Sequence, Union
from typing import TYPE_CHECKING, Union
import click
@ -284,10 +285,7 @@ class Server:
len(self.server_state.tasks),
)
for t in self.server_state.tasks:
if sys.version_info < (3, 9): # pragma: py-gte-39
t.cancel()
else: # pragma: py-lt-39
t.cancel(msg="Task cancelled, timeout graceful shutdown exceeded")
t.cancel(msg="Task cancelled, timeout graceful shutdown exceeded")
# Send the lifespan shutdown event, and wait for application shutdown.
if not self.force_exit:

View File

@ -5,10 +5,11 @@ import os
import signal
import sys
import threading
from collections.abc import Iterator
from pathlib import Path
from socket import socket
from types import FrameType
from typing import Callable, Iterator
from typing import Callable
import click

View File

@ -1,9 +1,10 @@
from __future__ import annotations
import logging
from collections.abc import Iterator
from pathlib import Path
from socket import socket
from typing import Callable, Iterator
from typing import Callable
from uvicorn.config import Config
from uvicorn.supervisors.basereload import BaseReload