Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/ci-cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ jobs:
needs: gen_llhttp
strategy:
matrix:
pyver: [3.9, '3.10', '3.11', '3.12', '3.13', '3.14']
pyver: ['3.10', '3.11', '3.12', '3.13', '3.14']
no-extensions: ['', 'Y']
os: [ubuntu, macos, windows]
experimental: [false]
Expand All @@ -148,7 +148,7 @@ jobs:
- os: windows
no-extensions: 'Y'
include:
- pyver: pypy-3.9
- pyver: pypy-3.10
no-extensions: 'Y'
os: ubuntu
experimental: false
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/update-pre-commit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: 3.9
python-version: 3.10
- name: Install dependencies
run: >-
pip install -r requirements/lint.in -c requirements/lint.txt
Expand Down
2 changes: 1 addition & 1 deletion CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4546,7 +4546,7 @@ Bugfixes
`#5853 <https://github.com/aio-libs/aiohttp/issues/5853>`_
- Added ``params`` keyword argument to ``ClientSession.ws_connect``. -- :user:`hoh`.
`#5868 <https://github.com/aio-libs/aiohttp/issues/5868>`_
- Uses :py:class:`~asyncio.ThreadedChildWatcher` under POSIX to allow setting up test loop in non-main thread.
- Uses ``asyncio.ThreadedChildWatcher`` under POSIX to allow setting up test loop in non-main thread.
`#5877 <https://github.com/aio-libs/aiohttp/issues/5877>`_
- Fix the error in handling the return value of `getaddrinfo`.
`getaddrinfo` will return an `(int, bytes)` tuple, if CPython could not handle the address family.
Expand Down
1 change: 1 addition & 0 deletions CHANGES/11601.breaking.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Dropped support for Python 3.9 -- by :user:`Dreamsorcerer`.
10 changes: 0 additions & 10 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -128,16 +128,6 @@ define run_tests_in_docker
docker run --rm -ti -v `pwd`:/src -w /src "aiohttp-test-$(1)-$(2)" $(TEST_SPEC)
endef

.PHONY: test-3.9-no-extensions test
test-3.9-no-extensions:
$(call run_tests_in_docker,3.9,y)
test-3.9:
$(call run_tests_in_docker,3.9,n)
test-3.10-no-extensions:
$(call run_tests_in_docker,3.10,y)
test-3.10:
$(call run_tests_in_docker,3.10,n)

.PHONY: clean
clean:
@rm -rf `find . -name __pycache__`
Expand Down
6 changes: 3 additions & 3 deletions aiohttp/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
__version__ = "4.0.0a2.dev0"

from typing import TYPE_CHECKING, Tuple
from typing import TYPE_CHECKING

from . import hdrs
from .client import (
Expand Down Expand Up @@ -113,7 +113,7 @@
# At runtime these are lazy-loaded at the bottom of the file.
from .worker import GunicornUVLoopWebWorker, GunicornWebWorker

__all__: Tuple[str, ...] = (
__all__: tuple[str, ...] = (
"hdrs",
# client
"AddrInfoType",
Expand Down Expand Up @@ -237,7 +237,7 @@
)


def __dir__() -> Tuple[str, ...]:
def __dir__() -> tuple[str, ...]:
return __all__ + ("__doc__",)


Expand Down
13 changes: 7 additions & 6 deletions aiohttp/_cookie_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@
"""

import re
from collections.abc import Sequence
from http.cookies import Morsel
from typing import List, Optional, Sequence, Tuple, cast
from typing import cast

from .log import internal_logger

Expand Down Expand Up @@ -156,7 +157,7 @@ def _unquote(value: str) -> str:
return _unquote_sub(_unquote_replace, value)


def parse_cookie_header(header: str) -> List[Tuple[str, Morsel[str]]]:
def parse_cookie_header(header: str) -> list[tuple[str, Morsel[str]]]:
"""
Parse a Cookie header according to RFC 6265 Section 5.4.

Expand All @@ -176,7 +177,7 @@ def parse_cookie_header(header: str) -> List[Tuple[str, Morsel[str]]]:
if not header:
return []

cookies: List[Tuple[str, Morsel[str]]] = []
cookies: list[tuple[str, Morsel[str]]] = []
i = 0
n = len(header)

Expand Down Expand Up @@ -211,7 +212,7 @@ def parse_cookie_header(header: str) -> List[Tuple[str, Morsel[str]]]:
return cookies


def parse_set_cookie_headers(headers: Sequence[str]) -> List[Tuple[str, Morsel[str]]]:
def parse_set_cookie_headers(headers: Sequence[str]) -> list[tuple[str, Morsel[str]]]:
"""
Parse cookie headers using a vendored version of SimpleCookie parsing.

Expand All @@ -230,7 +231,7 @@ def parse_set_cookie_headers(headers: Sequence[str]) -> List[Tuple[str, Morsel[s
This implementation handles unmatched quotes more gracefully to prevent cookie loss.
See https://github.com/aio-libs/aiohttp/issues/7993
"""
parsed_cookies: List[Tuple[str, Morsel[str]]] = []
parsed_cookies: list[tuple[str, Morsel[str]]] = []

for header in headers:
if not header:
Expand All @@ -239,7 +240,7 @@ def parse_set_cookie_headers(headers: Sequence[str]) -> List[Tuple[str, Morsel[s
# Parse cookie string using SimpleCookie's algorithm
i = 0
n = len(header)
current_morsel: Optional[Morsel[str]] = None
current_morsel: Morsel[str] | None = None
morsel_seen = False

while 0 <= i < n:
Expand Down
7 changes: 4 additions & 3 deletions aiohttp/_websocket/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@

import functools
import re
from re import Pattern
from struct import Struct
from typing import TYPE_CHECKING, Final, List, Optional, Pattern, Tuple
from typing import TYPE_CHECKING, Final

from ..helpers import NO_EXTENSIONS
from .models import WSHandshakeError
Expand All @@ -23,7 +24,7 @@

# Used by _websocket_mask_python
@functools.lru_cache
def _xor_table() -> List[bytes]:
def _xor_table() -> list[bytes]:
return [bytes(a ^ b for a in range(256)) for b in range(256)]


Expand Down Expand Up @@ -74,7 +75,7 @@ def _websocket_mask_python(mask: bytes, data: bytearray) -> None:
_WS_EXT_RE_SPLIT: Final[Pattern[str]] = re.compile(r"permessage-deflate([^,]+)?")


def ws_ext_parse(extstr: Optional[str], isserver: bool = False) -> Tuple[int, bool]:
def ws_ext_parse(extstr: str | None, isserver: bool = False) -> tuple[int, bool]:
if not extstr:
return 0, False

Expand Down
25 changes: 13 additions & 12 deletions aiohttp/_websocket/models.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
"""Models for WebSocket protocol versions 13 and 8."""

import json
from collections.abc import Callable
from enum import IntEnum
from typing import Any, Callable, Final, Literal, NamedTuple, Optional, Union, cast
from typing import Any, Final, Literal, NamedTuple, Union, cast

WS_DEFLATE_TRAILING: Final[bytes] = bytes([0x00, 0x00, 0xFF, 0xFF])

Expand Down Expand Up @@ -41,18 +42,18 @@ class WSMsgType(IntEnum):
class WSMessageContinuation(NamedTuple):
data: bytes
size: int
extra: Optional[str] = None
extra: str | None = None
type: Literal[WSMsgType.CONTINUATION] = WSMsgType.CONTINUATION


class WSMessageText(NamedTuple):
data: str
size: int
extra: Optional[str] = None
extra: str | None = None
type: Literal[WSMsgType.TEXT] = WSMsgType.TEXT

def json(
self, *, loads: Callable[[Union[str, bytes, bytearray]], Any] = json.loads
self, *, loads: Callable[[str | bytes | bytearray], Any] = json.loads
) -> Any:
"""Return parsed JSON data."""
return loads(self.data)
Expand All @@ -61,11 +62,11 @@ def json(
class WSMessageBinary(NamedTuple):
data: bytes
size: int
extra: Optional[str] = None
extra: str | None = None
type: Literal[WSMsgType.BINARY] = WSMsgType.BINARY

def json(
self, *, loads: Callable[[Union[str, bytes, bytearray]], Any] = json.loads
self, *, loads: Callable[[str | bytes | bytearray], Any] = json.loads
) -> Any:
"""Return parsed JSON data."""
return loads(self.data)
Expand All @@ -74,42 +75,42 @@ def json(
class WSMessagePing(NamedTuple):
data: bytes
size: int
extra: Optional[str] = None
extra: str | None = None
type: Literal[WSMsgType.PING] = WSMsgType.PING


class WSMessagePong(NamedTuple):
data: bytes
size: int
extra: Optional[str] = None
extra: str | None = None
type: Literal[WSMsgType.PONG] = WSMsgType.PONG


class WSMessageClose(NamedTuple):
data: int
size: int
extra: Optional[str] = None
extra: str | None = None
type: Literal[WSMsgType.CLOSE] = WSMsgType.CLOSE


class WSMessageClosing(NamedTuple):
data: None = None
size: int = 0
extra: Optional[str] = None
extra: str | None = None
type: Literal[WSMsgType.CLOSING] = WSMsgType.CLOSING


class WSMessageClosed(NamedTuple):
data: None = None
size: int = 0
extra: Optional[str] = None
extra: str | None = None
type: Literal[WSMsgType.CLOSED] = WSMsgType.CLOSED


class WSMessageError(NamedTuple):
data: BaseException
size: int = 0
extra: Optional[str] = None
extra: str | None = None
type: Literal[WSMsgType.ERROR] = WSMsgType.ERROR


Expand Down
34 changes: 16 additions & 18 deletions aiohttp/_websocket/reader_py.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import asyncio
import builtins
from collections import deque
from typing import Deque, Final, Optional, Set, Tuple, Type, Union
from typing import Final

from ..base_protocol import BaseProtocol
from ..compression_utils import ZLibDecompressor
Expand All @@ -23,7 +23,7 @@
WSMsgType,
)

ALLOWED_CLOSE_CODES: Final[Set[int]] = {int(i) for i in WSCloseCode}
ALLOWED_CLOSE_CODES: Final[set[int]] = {int(i) for i in WSCloseCode}

# States for the reader, used to parse the WebSocket frame
# integer values are used so they can be cythonized
Expand Down Expand Up @@ -70,21 +70,21 @@ def __init__(
self._limit = limit * 2
self._loop = loop
self._eof = False
self._waiter: Optional[asyncio.Future[None]] = None
self._exception: Union[Type[BaseException], BaseException, None] = None
self._buffer: Deque[WSMessage] = deque()
self._waiter: asyncio.Future[None] | None = None
self._exception: type[BaseException] | BaseException | None = None
self._buffer: deque[WSMessage] = deque()
self._get_buffer = self._buffer.popleft
self._put_buffer = self._buffer.append

def is_eof(self) -> bool:
return self._eof

def exception(self) -> Optional[Union[Type[BaseException], BaseException]]:
def exception(self) -> type[BaseException] | BaseException | None:
return self._exception

def set_exception(
self,
exc: Union[Type[BaseException], BaseException],
exc: type[BaseException] | BaseException,
exc_cause: builtins.BaseException = _EXC_SENTINEL,
) -> None:
self._eof = True
Expand Down Expand Up @@ -144,7 +144,7 @@ def __init__(
self.queue = queue
self._max_msg_size = max_msg_size

self._exc: Optional[Exception] = None
self._exc: Exception | None = None
self._partial = bytearray()
self._state = READ_HEADER

Expand All @@ -156,11 +156,11 @@ def __init__(

self._tail: bytes = b""
self._has_mask = False
self._frame_mask: Optional[bytes] = None
self._frame_mask: bytes | None = None
self._payload_bytes_to_read = 0
self._payload_len_flag = 0
self._compressed: int = COMPRESSED_NOT_SET
self._decompressobj: Optional[ZLibDecompressor] = None
self._decompressobj: ZLibDecompressor | None = None
self._compress = compress

def feed_eof(self) -> None:
Expand All @@ -169,9 +169,7 @@ def feed_eof(self) -> None:
# data can be bytearray on Windows because proactor event loop uses bytearray
# and asyncio types this to Union[bytes, bytearray, memoryview] so we need
# coerce data to bytes if it is not
def feed_data(
self, data: Union[bytes, bytearray, memoryview]
) -> Tuple[bool, bytes]:
def feed_data(self, data: bytes | bytearray | memoryview) -> tuple[bool, bytes]:
if type(data) is not bytes:
data = bytes(data)

Expand All @@ -190,9 +188,9 @@ def feed_data(
def _handle_frame(
self,
fin: bool,
opcode: Union[int, cython_int], # Union intended: Cython pxd uses C int
payload: Union[bytes, bytearray],
compressed: Union[int, cython_int], # Union intended: Cython pxd uses C int
opcode: int | cython_int, # Union intended: Cython pxd uses C int
payload: bytes | bytearray,
compressed: int | cython_int, # Union intended: Cython pxd uses C int
) -> None:
msg: WSMessage
if opcode in {OP_CODE_TEXT, OP_CODE_BINARY, OP_CODE_CONTINUATION}:
Expand Down Expand Up @@ -228,7 +226,7 @@ def _handle_frame(
f"to be zero, got {opcode!r}",
)

assembled_payload: Union[bytes, bytearray]
assembled_payload: bytes | bytearray
if has_partial:
assembled_payload = self._partial + payload
self._partial.clear()
Expand Down Expand Up @@ -452,7 +450,7 @@ def _feed_data(self, data: bytes) -> None:
self._payload_fragments.append(data_cstr[f_start_pos:f_end_pos])
break

payload: Union[bytes, bytearray]
payload: bytes | bytearray
if had_fragments:
# We have to join the payload fragments get the payload
self._payload_fragments.append(data_cstr[f_start_pos:f_end_pos])
Expand Down
6 changes: 3 additions & 3 deletions aiohttp/_websocket/writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import asyncio
import random
from functools import partial
from typing import Any, Final, Optional, Union
from typing import Any, Final

from ..base_protocol import BaseProtocol
from ..client_exceptions import ClientConnectionResetError
Expand Down Expand Up @@ -65,7 +65,7 @@ def __init__(
self._compressobj: Any = None # actually compressobj

async def send_frame(
self, message: bytes, opcode: int, compress: Optional[int] = None
self, message: bytes, opcode: int, compress: int | None = None
) -> None:
"""Send a frame over the websocket with message as its payload."""
if self._closing and not (opcode & WSMsgType.CLOSE):
Expand Down Expand Up @@ -166,7 +166,7 @@ def _make_compress_obj(self, compress: int) -> ZLibCompressor:
max_sync_chunk_size=WEBSOCKET_MAX_SYNC_CHUNK_SIZE,
)

async def close(self, code: int = 1000, message: Union[bytes, str] = b"") -> None:
async def close(self, code: int = 1000, message: bytes | str = b"") -> None:
"""Close the websocket, sending the specified code and message."""
if isinstance(message, str):
message = message.encode("utf-8")
Expand Down
Loading
Loading