Skip to content

Commit 5e98938

Browse files
authored
Remove support for Python 3.9 (end of life) (#1116)
* Remove Pytnon 3.9 support (end of life) * Safe ruff fixes * Unsafe ruff fixes * Manual fixes
1 parent 81a5f36 commit 5e98938

35 files changed

+266
-315
lines changed

.github/workflows/publish.yml

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ jobs:
1515
- uses: actions/checkout@v4
1616
- uses: actions/setup-python@v5
1717
with:
18-
python-version: "3.9"
18+
python-version: "3.10"
1919
- name: Prepare C files to include
2020
run: |
2121
python -m pip install --upgrade pip build
@@ -52,7 +52,7 @@ jobs:
5252
- uses: actions/checkout@v4
5353
- uses: actions/setup-python@v5
5454
with:
55-
python-version: "3.9"
55+
python-version: "3.10"
5656
- name: Set up QEMU
5757
if: ${{ matrix.arch == 'aarch64' }}
5858
uses: docker/setup-qemu-action@v3
@@ -82,10 +82,8 @@ jobs:
8282

8383
strategy:
8484
matrix:
85-
python: ["3.9", "3.10", "3.11", "3.12", "3.13"]
85+
python: ["3.10", "3.11", "3.12", "3.13"]
8686
include:
87-
- python: "3.9"
88-
aiokafka_whl: dist/aiokafka-*-cp39-cp39-win_amd64.whl
8987
- python: "3.10"
9088
aiokafka_whl: dist/aiokafka-*-cp310-cp310-win_amd64.whl
9189
- python: "3.11"
@@ -127,10 +125,8 @@ jobs:
127125

128126
strategy:
129127
matrix:
130-
python: ["3.9", "3.10", "3.11", "3.12", "3.13"]
128+
python: ["3.10", "3.11", "3.12", "3.13"]
131129
include:
132-
- python: "3.9"
133-
aiokafka_whl: dist/aiokafka-*-cp39-cp39-macosx_*_x86_64.whl
134130
- python: "3.10"
135131
aiokafka_whl: dist/aiokafka-*-cp310-cp310-macosx_*_x86_64.whl
136132
- python: "3.11"
@@ -170,10 +166,8 @@ jobs:
170166

171167
strategy:
172168
matrix:
173-
python: ["3.9", "3.10", "3.11", "3.12", "3.13"]
169+
python: ["3.10", "3.11", "3.12", "3.13"]
174170
include:
175-
- python: "3.9"
176-
aiokafka_whl: dist/aiokafka-*-cp39-cp39-macosx_*_arm64.whl
177171
- python: "3.10"
178172
aiokafka_whl: dist/aiokafka-*-cp310-cp310-macosx_*_arm64.whl
179173
- python: "3.11"
@@ -213,10 +207,8 @@ jobs:
213207

214208
strategy:
215209
matrix:
216-
python: ["3.9", "3.10", "3.11", "3.12", "3.13"]
210+
python: ["3.10", "3.11", "3.12", "3.13"]
217211
include:
218-
- python: "3.9"
219-
aiokafka_whl: dist/aiokafka-*-cp39-cp39-manylinux*_x86_64.whl
220212
- python: "3.10"
221213
aiokafka_whl: dist/aiokafka-*-cp310-cp310-manylinux*_x86_64.whl
222214
- python: "3.11"

.github/workflows/tests.yml

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ jobs:
2222
- name: Set up Python
2323
uses: actions/setup-python@v5
2424
with:
25-
python-version: 3.9
25+
python-version: "3.10"
2626

2727
- name: Install system dependencies
2828
run: |
@@ -39,10 +39,10 @@ jobs:
3939
uses: actions/cache@v4
4040
with:
4141
path: ${{ steps.pip-cache.outputs.dir }}
42-
key: ${{ runner.os }}-py-3.9-${{ hashFiles('requirements-ci.txt') }}-${{ hashFiles('setup.py') }}
42+
key: ${{ runner.os }}-py-3.10-${{ hashFiles('requirements-ci.txt') }}-${{ hashFiles('setup.py') }}
4343
# If miss on key takes any other cache with different hashes, will download correct ones on next step anyway
4444
restore-keys: |
45-
${{ runner.os }}-py-3.9-
45+
${{ runner.os }}-py-3.10-
4646
4747
- name: Install python dependencies
4848
run: |
@@ -73,7 +73,7 @@ jobs:
7373

7474
strategy:
7575
matrix:
76-
python: ["3.9", "3.10", "3.11", "3.12", "3.13"]
76+
python: ["3.10", "3.11", "3.12", "3.13"]
7777

7878
steps:
7979
- uses: actions/checkout@v4
@@ -141,7 +141,7 @@ jobs:
141141

142142
strategy:
143143
matrix:
144-
python: ["3.9", "3.10", "3.11", "3.12", "3.13"]
144+
python: ["3.10", "3.11", "3.12", "3.13"]
145145

146146
steps:
147147
- uses: actions/checkout@v4
@@ -215,9 +215,6 @@ jobs:
215215
scala: "2.13"
216216

217217
# Older python versions against latest broker
218-
- python: "3.9"
219-
kafka: "2.8.1"
220-
scala: "2.13"
221218
- python: "3.10"
222219
kafka: "2.8.1"
223220
scala: "2.13"

aiokafka/admin/client.py

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from collections import defaultdict
44
from collections.abc import Sequence
55
from ssl import SSLContext
6-
from typing import Any, Optional, Union
6+
from typing import Any
77

88
import async_timeout
99

@@ -89,21 +89,21 @@ def __init__(
8989
self,
9090
*,
9191
loop=None,
92-
bootstrap_servers: Union[str, list[str]] = "localhost",
92+
bootstrap_servers: str | list[str] = "localhost",
9393
client_id: str = "aiokafka-" + __version__,
9494
request_timeout_ms: int = 40000,
9595
connections_max_idle_ms: int = 540000,
9696
retry_backoff_ms: int = 100,
9797
metadata_max_age_ms: int = 300000,
9898
security_protocol: str = "PLAINTEXT",
99-
ssl_context: Optional[SSLContext] = None,
99+
ssl_context: SSLContext | None = None,
100100
api_version: str = "auto",
101101
sasl_mechanism: str = "PLAIN",
102-
sasl_plain_username: Optional[str] = None,
103-
sasl_plain_password: Optional[str] = None,
102+
sasl_plain_username: str | None = None,
103+
sasl_plain_password: str | None = None,
104104
sasl_kerberos_service_name: str = "kafka",
105-
sasl_kerberos_domain_name: Optional[str] = None,
106-
sasl_oauth_token_provider: Optional[str] = None,
105+
sasl_kerberos_domain_name: str | None = None,
106+
sasl_oauth_token_provider: str | None = None,
107107
):
108108
self._closed = False
109109
self._started = False
@@ -141,7 +141,7 @@ async def close(self):
141141
async def _send_request(
142142
self,
143143
request: Request,
144-
node_id: Optional[int] = None,
144+
node_id: int | None = None,
145145
) -> Response:
146146
if node_id is None:
147147
node_id = self._client.get_random_node()
@@ -227,7 +227,7 @@ def _convert_new_topic_request(new_topic):
227227
async def create_topics(
228228
self,
229229
new_topics: list[NewTopic],
230-
timeout_ms: Optional[int] = None,
230+
timeout_ms: int | None = None,
231231
validate_only: bool = False,
232232
) -> Response:
233233
"""Create new topics in the cluster.
@@ -269,7 +269,7 @@ async def create_topics(
269269
async def delete_topics(
270270
self,
271271
topics: list[str],
272-
timeout_ms: Optional[int] = None,
272+
timeout_ms: int | None = None,
273273
) -> Response:
274274
"""Delete topics from the cluster.
275275
@@ -285,7 +285,7 @@ async def delete_topics(
285285

286286
async def _get_cluster_metadata(
287287
self,
288-
topics: Optional[list[str]] = None,
288+
topics: list[str] | None = None,
289289
) -> Response:
290290
"""
291291
Retrieve cluster metadata
@@ -303,7 +303,7 @@ async def list_topics(self) -> list[str]:
303303

304304
async def describe_topics(
305305
self,
306-
topics: Optional[list[str]] = None,
306+
topics: list[str] | None = None,
307307
) -> list[Any]:
308308
metadata = await self._get_cluster_metadata(topics=topics)
309309
obj = metadata.to_object()
@@ -426,7 +426,7 @@ def _convert_topic_partitions(topic_partitions: dict[str, NewPartitions]):
426426
async def create_partitions(
427427
self,
428428
topic_partitions: dict[str, NewPartitions],
429-
timeout_ms: Optional[int] = None,
429+
timeout_ms: int | None = None,
430430
validate_only: bool = False,
431431
) -> Response:
432432
"""Create additional partitions for an existing topic.
@@ -457,7 +457,7 @@ async def create_partitions(
457457
async def describe_consumer_groups(
458458
self,
459459
group_ids: list[str],
460-
group_coordinator_id: Optional[int] = None,
460+
group_coordinator_id: int | None = None,
461461
include_authorized_operations: bool = False,
462462
) -> list[Response]:
463463
"""Describe a set of consumer groups.
@@ -509,7 +509,7 @@ async def describe_consumer_groups(
509509

510510
async def list_consumer_groups(
511511
self,
512-
broker_ids: Optional[list[int]] = None,
512+
broker_ids: list[int] | None = None,
513513
) -> list[tuple[Any, ...]]:
514514
"""List all consumer groups known to the cluster.
515515
@@ -578,8 +578,8 @@ async def find_coordinator(self, group_id: str, coordinator_type: int = 0) -> in
578578
async def list_consumer_group_offsets(
579579
self,
580580
group_id: str,
581-
group_coordinator_id: Optional[int] = None,
582-
partitions: Optional[list[TopicPartition]] = None,
581+
group_coordinator_id: int | None = None,
582+
partitions: list[TopicPartition] | None = None,
583583
) -> dict[TopicPartition, OffsetAndMetadata]:
584584
"""Fetch Consumer Offsets for a single consumer group.
585585
@@ -638,7 +638,7 @@ async def list_consumer_group_offsets(
638638
async def delete_records(
639639
self,
640640
records_to_delete: dict[TopicPartition, RecordsToDelete],
641-
timeout_ms: Optional[int] = None,
641+
timeout_ms: int | None = None,
642642
) -> dict[TopicPartition, int]:
643643
"""Delete records from partitions.
644644

aiokafka/cluster.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
import threading
55
import time
66
from concurrent.futures import Future
7-
from typing import Optional
87

98
from aiokafka import errors as Errors
109
from aiokafka.conn import collect_hosts
@@ -104,7 +103,7 @@ def broker_metadata(self, broker_id):
104103
or self._coordinator_brokers.get(broker_id)
105104
)
106105

107-
def partitions_for_topic(self, topic: str) -> Optional[set[int]]:
106+
def partitions_for_topic(self, topic: str) -> set[int] | None:
108107
"""Return set of all partitions for topic (whether available or not)
109108
110109
Arguments:

aiokafka/codec.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import gzip
22
import io
33
import struct
4-
from typing import Optional
54

65
from typing_extensions import Buffer
76

@@ -30,7 +29,7 @@ def has_lz4() -> bool:
3029
return cramjam is not None
3130

3231

33-
def gzip_encode(payload: Buffer, compresslevel: Optional[int] = None) -> bytes:
32+
def gzip_encode(payload: Buffer, compresslevel: int | None = None) -> bytes:
3433
if not compresslevel:
3534
compresslevel = 9
3635

@@ -94,7 +93,7 @@ def snappy_encode(
9493
return cramjam.snappy.compress_raw(payload)
9594

9695
out = io.BytesIO()
97-
for fmt, dat in zip(_XERIAL_V1_FORMAT, _XERIAL_V1_HEADER):
96+
for fmt, dat in zip(_XERIAL_V1_FORMAT, _XERIAL_V1_HEADER, strict=False):
9897
out.write(struct.pack("!" + fmt, dat))
9998

10099
payload = memoryview(payload)
@@ -186,7 +185,7 @@ def lz4_decode(payload: Buffer) -> bytes:
186185
return bytes(cramjam.lz4.decompress(payload))
187186

188187

189-
def zstd_encode(payload: Buffer, level: Optional[int] = None) -> bytes:
188+
def zstd_encode(payload: Buffer, level: int | None = None) -> bytes:
190189
if not has_zstd():
191190
raise NotImplementedError("Zstd codec is not available")
192191

aiokafka/conn.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -411,7 +411,7 @@ def _on_read_task_error(cls, self_ref, read_task):
411411
try:
412412
read_task.result()
413413
except Exception as exc:
414-
if not isinstance(exc, (OSError, EOFError, ConnectionError)):
414+
if not isinstance(exc, OSError | EOFError | ConnectionError):
415415
log.exception("Unexpected exception in AIOKafkaConnection")
416416

417417
self = self_ref()
@@ -783,7 +783,7 @@ def create_salted_password(self, salt, iterations):
783783

784784
@staticmethod
785785
def _xor_bytes(left, right):
786-
return bytes(lb ^ rb for lb, rb in zip(left, right))
786+
return bytes(lb ^ rb for lb, rb in zip(left, right, strict=False))
787787

788788

789789
class OAuthAuthenticator(BaseSaslAuthenticator):

aiokafka/consumer/consumer.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -452,7 +452,7 @@ async def _wait_topics(self):
452452
await self._client._wait_on_metadata(topic)
453453

454454
def _validate_topics(self, topics):
455-
if not isinstance(topics, (tuple, set, list)):
455+
if not isinstance(topics, tuple | set | list):
456456
raise TypeError("Topics should be list of strings")
457457
return set(topics)
458458

aiokafka/consumer/group_coordinator.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -442,7 +442,7 @@ async def _perform_assignment(self, response: Response):
442442
member_id, group_instance_id, metadata_bytes = member
443443
elif isinstance(
444444
response,
445-
(JoinGroupResponse[0], JoinGroupResponse[1], JoinGroupResponse[2]),
445+
JoinGroupResponse[0] | JoinGroupResponse[1] | JoinGroupResponse[2],
446446
):
447447
member_id, metadata_bytes = member
448448
else:
@@ -986,11 +986,9 @@ def _is_commit_retriable(self, error):
986986
# as retriable.
987987
return error.retriable or isinstance(
988988
error,
989-
(
990-
Errors.UnknownMemberIdError,
991-
Errors.IllegalGenerationError,
992-
Errors.RebalanceInProgressError,
993-
),
989+
Errors.UnknownMemberIdError
990+
| Errors.IllegalGenerationError
991+
| Errors.RebalanceInProgressError,
994992
)
995993

996994
async def _maybe_do_last_autocommit(self, assignment):

aiokafka/consumer/subscription_state.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -391,7 +391,7 @@ class Assignment:
391391
"""
392392

393393
def __init__(self, topic_partitions: Iterable[TopicPartition]):
394-
assert isinstance(topic_partitions, (list, set, tuple))
394+
assert isinstance(topic_partitions, list | set | tuple)
395395

396396
self._topic_partitions = frozenset(topic_partitions)
397397

aiokafka/coordinator/assignors/sticky/sorted_set.py

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
1-
from collections.abc import Collection, Iterable, Iterator
1+
from collections.abc import Callable, Collection, Iterable, Iterator
22
from typing import (
33
Any,
4-
Callable,
54
Generic,
6-
Optional,
75
TypeVar,
86
final,
97
)
@@ -15,16 +13,16 @@
1513
class SortedSet(Generic[T], Collection[T]):
1614
def __init__(
1715
self,
18-
iterable: Optional[Iterable[T]] = None,
19-
key: Optional[Callable[[T], Any]] = None,
16+
iterable: Iterable[T] | None = None,
17+
key: Callable[[T], Any] | None = None,
2018
) -> None:
2119
self._key: Callable[[T], Any] = key if key is not None else lambda x: x
2220
self._set: set[T] = set(iterable) if iterable is not None else set()
2321

24-
self._cached_last: Optional[T] = None
25-
self._cached_first: Optional[T] = None
22+
self._cached_last: T | None = None
23+
self._cached_first: T | None = None
2624

27-
def first(self) -> Optional[T]:
25+
def first(self) -> T | None:
2826
if self._cached_first is not None:
2927
return self._cached_first
3028

@@ -35,7 +33,7 @@ def first(self) -> Optional[T]:
3533
self._cached_first = first
3634
return first
3735

38-
def last(self) -> Optional[T]:
36+
def last(self) -> T | None:
3937
if self._cached_last is not None:
4038
return self._cached_last
4139

0 commit comments

Comments
 (0)