Skip to content

Commit 35754d4

Browse files
Drop support for Python 3.9 (aio-libs#11600)
1 parent b381cb4 commit 35754d4

108 files changed

Lines changed: 1467 additions & 1860 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci-cd.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ jobs:
138138
needs: gen_llhttp
139139
strategy:
140140
matrix:
141-
pyver: [3.9, '3.10', '3.11', '3.12', '3.13', '3.14']
141+
pyver: ['3.10', '3.11', '3.12', '3.13', '3.14']
142142
no-extensions: ['', 'Y']
143143
os: [ubuntu, macos, windows]
144144
experimental: [false]
@@ -148,7 +148,7 @@ jobs:
148148
- os: windows
149149
no-extensions: 'Y'
150150
include:
151-
- pyver: pypy-3.9
151+
- pyver: pypy-3.10
152152
no-extensions: 'Y'
153153
os: ubuntu
154154
experimental: false

.github/workflows/update-pre-commit.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ jobs:
1212
- name: Set up Python
1313
uses: actions/setup-python@v6
1414
with:
15-
python-version: 3.9
15+
python-version: 3.10
1616
- name: Install dependencies
1717
run: >-
1818
pip install -r requirements/lint.in -c requirements/lint.txt

CHANGES.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4546,7 +4546,7 @@ Bugfixes
45464546
`#5853 <https://github.com/aio-libs/aiohttp/issues/5853>`_
45474547
- Added ``params`` keyword argument to ``ClientSession.ws_connect``. -- :user:`hoh`.
45484548
`#5868 <https://github.com/aio-libs/aiohttp/issues/5868>`_
4549-
- Uses :py:class:`~asyncio.ThreadedChildWatcher` under POSIX to allow setting up test loop in non-main thread.
4549+
- Uses ``asyncio.ThreadedChildWatcher`` under POSIX to allow setting up test loop in non-main thread.
45504550
`#5877 <https://github.com/aio-libs/aiohttp/issues/5877>`_
45514551
- Fix the error in handling the return value of `getaddrinfo`.
45524552
`getaddrinfo` will return an `(int, bytes)` tuple, if CPython could not handle the address family.

CHANGES/11601.breaking.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Dropped support for Python 3.9 -- by :user:`Dreamsorcerer`.

Makefile

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -128,16 +128,6 @@ define run_tests_in_docker
128128
docker run --rm -ti -v `pwd`:/src -w /src "aiohttp-test-$(1)-$(2)" $(TEST_SPEC)
129129
endef
130130

131-
.PHONY: test-3.9-no-extensions test
132-
test-3.9-no-extensions:
133-
$(call run_tests_in_docker,3.9,y)
134-
test-3.9:
135-
$(call run_tests_in_docker,3.9,n)
136-
test-3.10-no-extensions:
137-
$(call run_tests_in_docker,3.10,y)
138-
test-3.10:
139-
$(call run_tests_in_docker,3.10,n)
140-
141131
.PHONY: clean
142132
clean:
143133
@rm -rf `find . -name __pycache__`

aiohttp/__init__.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
__version__ = "4.0.0a2.dev0"
22

3-
from typing import TYPE_CHECKING, Tuple
3+
from typing import TYPE_CHECKING
44

55
from . import hdrs
66
from .client import (
@@ -113,7 +113,7 @@
113113
# At runtime these are lazy-loaded at the bottom of the file.
114114
from .worker import GunicornUVLoopWebWorker, GunicornWebWorker
115115

116-
__all__: Tuple[str, ...] = (
116+
__all__: tuple[str, ...] = (
117117
"hdrs",
118118
# client
119119
"AddrInfoType",
@@ -237,7 +237,7 @@
237237
)
238238

239239

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

243243

aiohttp/_cookie_helpers.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@
66
"""
77

88
import re
9+
from collections.abc import Sequence
910
from http.cookies import Morsel
10-
from typing import List, Optional, Sequence, Tuple, cast
11+
from typing import cast
1112

1213
from .log import internal_logger
1314

@@ -156,7 +157,7 @@ def _unquote(value: str) -> str:
156157
return _unquote_sub(_unquote_replace, value)
157158

158159

159-
def parse_cookie_header(header: str) -> List[Tuple[str, Morsel[str]]]:
160+
def parse_cookie_header(header: str) -> list[tuple[str, Morsel[str]]]:
160161
"""
161162
Parse a Cookie header according to RFC 6265 Section 5.4.
162163
@@ -176,7 +177,7 @@ def parse_cookie_header(header: str) -> List[Tuple[str, Morsel[str]]]:
176177
if not header:
177178
return []
178179

179-
cookies: List[Tuple[str, Morsel[str]]] = []
180+
cookies: list[tuple[str, Morsel[str]]] = []
180181
i = 0
181182
n = len(header)
182183

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

213214

214-
def parse_set_cookie_headers(headers: Sequence[str]) -> List[Tuple[str, Morsel[str]]]:
215+
def parse_set_cookie_headers(headers: Sequence[str]) -> list[tuple[str, Morsel[str]]]:
215216
"""
216217
Parse cookie headers using a vendored version of SimpleCookie parsing.
217218
@@ -230,7 +231,7 @@ def parse_set_cookie_headers(headers: Sequence[str]) -> List[Tuple[str, Morsel[s
230231
This implementation handles unmatched quotes more gracefully to prevent cookie loss.
231232
See https://github.com/aio-libs/aiohttp/issues/7993
232233
"""
233-
parsed_cookies: List[Tuple[str, Morsel[str]]] = []
234+
parsed_cookies: list[tuple[str, Morsel[str]]] = []
234235

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

245246
while 0 <= i < n:

aiohttp/_websocket/helpers.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22

33
import functools
44
import re
5+
from re import Pattern
56
from struct import Struct
6-
from typing import TYPE_CHECKING, Final, List, Optional, Pattern, Tuple
7+
from typing import TYPE_CHECKING, Final
78

89
from ..helpers import NO_EXTENSIONS
910
from .models import WSHandshakeError
@@ -23,7 +24,7 @@
2324

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

2930

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

7677

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

aiohttp/_websocket/models.py

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
"""Models for WebSocket protocol versions 13 and 8."""
22

33
import json
4+
from collections.abc import Callable
45
from enum import IntEnum
5-
from typing import Any, Callable, Final, Literal, NamedTuple, Optional, Union, cast
6+
from typing import Any, Final, Literal, NamedTuple, Union, cast
67

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

@@ -41,18 +42,18 @@ class WSMsgType(IntEnum):
4142
class WSMessageContinuation(NamedTuple):
4243
data: bytes
4344
size: int
44-
extra: Optional[str] = None
45+
extra: str | None = None
4546
type: Literal[WSMsgType.CONTINUATION] = WSMsgType.CONTINUATION
4647

4748

4849
class WSMessageText(NamedTuple):
4950
data: str
5051
size: int
51-
extra: Optional[str] = None
52+
extra: str | None = None
5253
type: Literal[WSMsgType.TEXT] = WSMsgType.TEXT
5354

5455
def json(
55-
self, *, loads: Callable[[Union[str, bytes, bytearray]], Any] = json.loads
56+
self, *, loads: Callable[[str | bytes | bytearray], Any] = json.loads
5657
) -> Any:
5758
"""Return parsed JSON data."""
5859
return loads(self.data)
@@ -61,11 +62,11 @@ def json(
6162
class WSMessageBinary(NamedTuple):
6263
data: bytes
6364
size: int
64-
extra: Optional[str] = None
65+
extra: str | None = None
6566
type: Literal[WSMsgType.BINARY] = WSMsgType.BINARY
6667

6768
def json(
68-
self, *, loads: Callable[[Union[str, bytes, bytearray]], Any] = json.loads
69+
self, *, loads: Callable[[str | bytes | bytearray], Any] = json.loads
6970
) -> Any:
7071
"""Return parsed JSON data."""
7172
return loads(self.data)
@@ -74,42 +75,42 @@ def json(
7475
class WSMessagePing(NamedTuple):
7576
data: bytes
7677
size: int
77-
extra: Optional[str] = None
78+
extra: str | None = None
7879
type: Literal[WSMsgType.PING] = WSMsgType.PING
7980

8081

8182
class WSMessagePong(NamedTuple):
8283
data: bytes
8384
size: int
84-
extra: Optional[str] = None
85+
extra: str | None = None
8586
type: Literal[WSMsgType.PONG] = WSMsgType.PONG
8687

8788

8889
class WSMessageClose(NamedTuple):
8990
data: int
9091
size: int
91-
extra: Optional[str] = None
92+
extra: str | None = None
9293
type: Literal[WSMsgType.CLOSE] = WSMsgType.CLOSE
9394

9495

9596
class WSMessageClosing(NamedTuple):
9697
data: None = None
9798
size: int = 0
98-
extra: Optional[str] = None
99+
extra: str | None = None
99100
type: Literal[WSMsgType.CLOSING] = WSMsgType.CLOSING
100101

101102

102103
class WSMessageClosed(NamedTuple):
103104
data: None = None
104105
size: int = 0
105-
extra: Optional[str] = None
106+
extra: str | None = None
106107
type: Literal[WSMsgType.CLOSED] = WSMsgType.CLOSED
107108

108109

109110
class WSMessageError(NamedTuple):
110111
data: BaseException
111112
size: int = 0
112-
extra: Optional[str] = None
113+
extra: str | None = None
113114
type: Literal[WSMsgType.ERROR] = WSMsgType.ERROR
114115

115116

aiohttp/_websocket/reader_py.py

Lines changed: 16 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import asyncio
44
import builtins
55
from collections import deque
6-
from typing import Deque, Final, Optional, Set, Tuple, Type, Union
6+
from typing import Final
77

88
from ..base_protocol import BaseProtocol
99
from ..compression_utils import ZLibDecompressor
@@ -23,7 +23,7 @@
2323
WSMsgType,
2424
)
2525

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

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

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

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

8585
def set_exception(
8686
self,
87-
exc: Union[Type[BaseException], BaseException],
87+
exc: type[BaseException] | BaseException,
8888
exc_cause: builtins.BaseException = _EXC_SENTINEL,
8989
) -> None:
9090
self._eof = True
@@ -144,7 +144,7 @@ def __init__(
144144
self.queue = queue
145145
self._max_msg_size = max_msg_size
146146

147-
self._exc: Optional[Exception] = None
147+
self._exc: Exception | None = None
148148
self._partial = bytearray()
149149
self._state = READ_HEADER
150150

@@ -156,11 +156,11 @@ def __init__(
156156

157157
self._tail: bytes = b""
158158
self._has_mask = False
159-
self._frame_mask: Optional[bytes] = None
159+
self._frame_mask: bytes | None = None
160160
self._payload_bytes_to_read = 0
161161
self._payload_len_flag = 0
162162
self._compressed: int = COMPRESSED_NOT_SET
163-
self._decompressobj: Optional[ZLibDecompressor] = None
163+
self._decompressobj: ZLibDecompressor | None = None
164164
self._compress = compress
165165

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

@@ -190,9 +188,9 @@ def feed_data(
190188
def _handle_frame(
191189
self,
192190
fin: bool,
193-
opcode: Union[int, cython_int], # Union intended: Cython pxd uses C int
194-
payload: Union[bytes, bytearray],
195-
compressed: Union[int, cython_int], # Union intended: Cython pxd uses C int
191+
opcode: int | cython_int, # Union intended: Cython pxd uses C int
192+
payload: bytes | bytearray,
193+
compressed: int | cython_int, # Union intended: Cython pxd uses C int
196194
) -> None:
197195
msg: WSMessage
198196
if opcode in {OP_CODE_TEXT, OP_CODE_BINARY, OP_CODE_CONTINUATION}:
@@ -228,7 +226,7 @@ def _handle_frame(
228226
f"to be zero, got {opcode!r}",
229227
)
230228

231-
assembled_payload: Union[bytes, bytearray]
229+
assembled_payload: bytes | bytearray
232230
if has_partial:
233231
assembled_payload = self._partial + payload
234232
self._partial.clear()
@@ -452,7 +450,7 @@ def _feed_data(self, data: bytes) -> None:
452450
self._payload_fragments.append(data_cstr[f_start_pos:f_end_pos])
453451
break
454452

455-
payload: Union[bytes, bytearray]
453+
payload: bytes | bytearray
456454
if had_fragments:
457455
# We have to join the payload fragments get the payload
458456
self._payload_fragments.append(data_cstr[f_start_pos:f_end_pos])

0 commit comments

Comments
 (0)