Compare commits
7 Commits
main
...
drop-pytho
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
23984c3457 | ||
|
|
b0bd5e87a3 | ||
|
|
2af31eb8bb | ||
|
|
c7498b3cba | ||
|
|
90f536ea21 | ||
|
|
70562c7140 | ||
|
|
5f3e508407 |
2
.github/workflows/test-suite.yml
vendored
2
.github/workflows/test-suite.yml
vendored
@ -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"
|
||||
|
||||
@ -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)"
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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":
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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"""
|
||||
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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 (
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
Reference in New Issue
Block a user