8000 Remove support for python <3.11 (#1273) · python-kasa/python-kasa@0f4de45 · GitHub
[go: up one dir, main page]

Skip to content

Commit 0f4de45

Browse files
sdb9696rytilahti
authored andcommitted
Remove support for python <3.11 (#1273)
Python 3.11 ships with latest Debian Bookworm. pypy is not that widely used with this library based on statistics. It could be added back when pypy supports python 3.11.
1 parent 38da7f1 commit 0f4de45

Some content is hidden

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

55 files changed

+176
-620
lines changed

.github/workflows/ci.yml

Lines changed: 2 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ jobs:
1717

1818
strategy:
1919
matrix:
20-
python-version: ["3.12"]
20+
python-version: ["3.13"]
2121

2222
steps:
2323
- name: "Checkout source files"
@@ -39,37 +39,17 @@ jobs:
3939
name: Python ${{ matrix.python-version}} on ${{ matrix.os }}${{ fromJSON('[" (extras)", ""]')[matrix.extras == ''] }}
4040
needs: linting
4141
runs-on: ${{ matrix.os }}
42-
continue-on-error: ${{ startsWith(matrix.python-version, 'pypy') }}
4342

4443
strategy:
4544
matrix:
46-
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "pypy-3.9", "pypy-3.10"]
45+
python-version: ["3.11", "3.12", "3.13"]
4746
os: [ubuntu-latest, macos-latest, windows-latest]
4847
extras: [false, true]
4948
exclude:
5049
- os: macos-latest
5150
extras: true
5251
- os: windows-latest
5352
extras: true
54-
- os: ubuntu-latest
55-
python-version: "pypy-3.9"
56-
extras: true
57-
- os: ubuntu-latest
58-
python-version: "pypy-3.10"
59-
extras: true
60-
- os: ubuntu-latest
61-
python-version: "3.9"
62-
extras: true
63-
- os: ubuntu-latest
64-
python-version: "3.10"
65-
extras: true
66-
# Exclude pypy on windows due to significant performance issues
67-
# running pytest requires ~12 min instead of 2 min on other platforms
68-
- os: windows-latest
69-
python-version: "pypy-3.9"
70-
- os: windows-latest
71-
python-version: "pypy-3.10"
72-
7353

7454
steps:
7555
- uses: "actions/checkout@v4"
@@ -79,16 +59,10 @@ jobs:
7959
python-version: ${{ matrix.python-version }}
8060
uv-version: ${{ env.UV_VERSION }}
8161
uv-install-options: ${{ matrix.extras == true && '--all-extras' || '' }}
82-
- name: "Run tests (no coverage)"
83-
if: ${{ startsWith(matrix.python-version, 'pypy') }}
84-
run: |
85-
uv run pytest -n auto
8662
- name: "Run tests (with coverage)"
87-
if: ${{ !startsWith(matrix.python-version, 'pypy') }}
8863
run: |
8964
uv run pytest -n auto --cov kasa --cov-report xml
9065
- name: "Upload coverage to Codecov"
91-
if: ${{ !startsWith(matrix.python-version, 'pypy') }}
9266
uses: "codecov/codecov-action@v4"
9367
with:
9468
token: ${{ secrets.CODECOV_TOKEN }}

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ repos:
1818
- id: check-ast
1919

2020
- repo: https://github.com/astral-sh/ruff-pre-commit
21-
rev: v0.3.7
21+
rev: v0.7.4
2222
hooks:
2323
- id: ruff
2424
args: [--fix, --exit-non-zero-on-fix]

devtools/bench/utils/original.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""Original implementation of the TP-Link Smart Home protocol."""
22

33
import struct
4-
from typing import Generator
4+
from collections.abc import Generator
55

66

77
class OriginalTPLinkSmartHomeProtocol:

devtools/dump_devinfo.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -315,7 +315,7 @@ async def cli(
315315
credentials = Credentials(username=username, password=password)
316316
if host is not None:
317317
if discovery_info:
318-
click.echo("Host and discovery info given, trying connect on %s." % host)
318+
click.echo(f"Host and discovery info given, trying connect on {host}.")
319319

320320
di = json.loads(discovery_info)
321321
dr = DiscoveryResult.from_dict(di)
@@ -358,7 +358,7 @@ async def cli(
358358
"Could not find a protocol for the given parameters."
359359
)
360360
else:
361-
click.echo("Host given, performing discovery on %s." % host)
361+
click.echo(f"Host given, performing discovery on {host}.")
362362
device = await Discover.discover_single(
363363
host,
364364
credentials=credentials,
@@ -374,13 +374,13 @@ async def cli(
374374
)
375375
else:
376376
click.echo(
377-
"No --host given, performing discovery on %s. Use --target to override."
378-
% target
377+
"No --host given, performing discovery on"
378+
f" {target}. Use --target to override."
379379
)
380380
devices = await Discover.discover(
381381
target=target, credentials=credentials, discovery_timeout=discovery_timeout
382382
)
383-
click.echo("Detected %s devices" % len(devices))
383+
click.echo(f"Detected {len(devices)} devices")
384384
for dev in devices.values():
385385
await handle_device(
386386
basedir,
@@ -446,7 +446,7 @@ async def get_legacy_fixture(
446446
dr = DiscoveryResult.from_dict(discovery_info)
447447
final["discovery_result"] = dr.to_dict()
448448

449-
click.echo("Got %s successes" % len(successes))
449+
click.echo(f"Got {len(successes)} successes")
450450
click.echo(click.style("## device info file ##", bold=True))
451451

452452
sysinfo = final["system"]["get_sysinfo"]
@@ -959,7 +959,7 @@ async def get_smart_fixtures(
959959
dr = DiscoveryResult.from_dict(discovery_info) # type: ignore
960960
final["discovery_result"] = dr.to_dict()
961961

962-
click.echo("Got %s successes" % len(successes))
962+
click.echo(f"Got {len(successes)} successes")
963963
click.echo(click.style("## device info file ##", bold=True))
964964

965965
if "get_device_info" in final:

devtools/parse_pcap.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ def parse_pcap(file):
6767
for module, cmds in json_payload.items():
6868
seen_items["modules"][module] += 1
6969
if "err_code" in cmds:
70-
echo("[red]Got error for module: %s[/red]" % cmds)
70+
echo(f"[red]Got error for module: {cmds}[/red]")
7171
continue
7272

7373
for cmd, response in cmds.items():

kasa/cachedzoneinfo.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
from __future__ import annotations
44

55
import asyncio
6-
76
from zoneinfo import ZoneInfo
87

98

kasa/cli/common.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@
55
import json
66
import re
77
import sys
8+
from collections.abc import Callable
89
from contextlib import contextmanager
910
from functools import singledispatch, update_wrapper, wraps
10-
from typing import TYPE_CHECKING, Any, Callable, Final
11+
from typing import TYPE_CHECKING, Any, Final
1112

1213
import asyncclick as click
1314

kasa/cli/time.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22

33
from __future__ import annotations
44

5+
import zoneinfo
56
from datetime import datetime
67

78
import asyncclick as click
8-
import zoneinfo
99

1010
from kasa import (
1111
Device,

kasa/device.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -110,11 +110,9 @@
110110
from collections.abc import Mapping, Sequence
111111
from dataclasses import dataclass
112112
from datetime import datetime, tzinfo
113-
from typing import TYPE_CHECKING, Any
113+
from typing import TYPE_CHECKING, Any, TypeAlias
114114
from warnings import warn
115115

116-
from typing_extensions import TypeAlias
117-
118116
from .credentials import Credentials as _Credentials
119117
from .device_type import DeviceType
120118
from .deviceconfig import (
@@ -213,7 +211,7 @@ def __init__(
213211
self._last_update: Any = None
214212
_LOGGER.debug("Initializing %s of type %s", host, type(self))
215213
self._device_type = DeviceType.Unknown
216-
# TODO: typing Any is just as using Optional[Dict] would require separate
214+
# TODO: typing Any is just as using dict | None would require separate
217215
# checks in accessors. the @updated_required decorator does not ensure
218216
# mypy that these are not accessed incorrectly.
219217
self._discovery_info: dict[str, Any] | None = None

kasa/deviceconfig.py

Lines changed: 18 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,12 @@
2727
2828
"""
2929

30-
# Note that this module does not work with from __future__ import annotations
31-
# due to it's use of type returned by fields() which becomes a string with the import.
32-
# https://bugs.python.org/issue39442
33-
# ruff: noqa: FA100
30+
# Module cannot use from __future__ import annotations until migrated to mashumaru
31+
# as dataclass.fields() will not resolve the type.
3432
import logging
3533
from dataclasses import asdict, dataclass, field, fields, is_dataclass
3634
from enum import Enum
37-
from typing import TYPE_CHECKING, Any, Dict, Optional, TypedDict, Union
35+
from typing import TYPE_CHECKING, Any, Optional, TypedDict
3836

3937
from .credentials import Credentials
4038
from .exceptions import KasaException
@@ -118,15 +116,15 @@ class DeviceConnectionParameters:
118116

119117
device_family: DeviceFamily
120118
encryption_type: DeviceEncryptionType
121-
login_version: Optional[int] = None
119+
login_version: int | None = None
122120
https: bool = False
123121

124122
@staticmethod
125123
def from_values(
126124
device_family: str,
127125
encryption_type: str,
128-
login_version: Optional[int] = None,
129-
https: Optional[bool] = None,
126+
login_version: int | None = None,
127+
https: bool | None = None,
130128
) -> "DeviceConnectionParameters":
131129
"""Return connection parameters from string values."""
132130
try:
@@ -145,7 +143,7 @@ def from_values(
145143
) from ex
146144

147145
@staticmethod
148-
def from_dict(connection_type_dict: Dict[str, Any]) -> "DeviceConnectionParameters":
146+
def from_dict(connection_type_dict: dict[str, Any]) -> "DeviceConnectionParameters":
149147
"""Return connection parameters from dict."""
150148
if (
151149
isinstance(connection_type_dict, dict)
@@ -163,9 +161,9 @@ def from_dict(connection_type_dict: Dict[str, Any]) -> "DeviceConnectionParamete
163161

164162
raise KasaException(f"Invalid connection type data for {connection_type_dict}")
165163

166-
def to_dict(self) -> Dict[str, Union[str, int, bool]]:
164+
def to_dict(self) -> dict[str, str | int | bool]:
167165
"""Convert connection params to dict."""
168-
result: Dict[str, Union[str, int]] = {
166+
result: dict[str, str | int] = {
169167
"device_family": self.device_family.value,
170168
"encryption_type": self.encryption_type.value,
171169
"https": self.https,
@@ -183,17 +181,17 @@ class DeviceConfig:
183181
#: IP address or hostname
184182
host: str
185183
#: Timeout for querying the device
186-
timeout: Optional[int] = DEFAULT_TIMEOUT
184+
timeout: int | None = DEFAULT_TIMEOUT
187185
#: Override the default 9999 port to support port forwarding
188-
port_override: Optional[int] = None
186+
port_override: int | None = None
189187
#: Credentials for devices requiring authentication
190-
credentials: Optional[Credentials] = None
188+
credentials: Credentials | None = None
191189
#: Credentials hash for devices requiring authentication.
192190
#: If credentials are also supplied they take precendence over credentials_hash.
193191
#: Credentials hash can be retrieved from :attr:`Device.credentials_hash`
194-
credentials_hash: Optional[str] = None
192+
credentials_hash: str | None = None
195193
#: The protocol specific type of connection. Defaults to the legacy type.
196-
batch_size: Optional[int] = None
194+
batch_size: int | None = None
197195
#: The batch size for protoools supporting multiple request batches.
198196
connection_type: DeviceConnectionParameters = field(
199197
default_factory=lambda: DeviceConnectionParameters(
@@ -208,7 +206,7 @@ class DeviceConfig:
208206
#: Set a custom http_client for the device to use.
209207
http_client: Optional["ClientSession"] = field(default=None, compare=False)
210208

211-
aes_keys: Optional[KeyPairDict] = None
209+
aes_keys: KeyPairDict | None = None
212210

213211
def __post_init__(self) -> None:
214212
if self.connection_type is None:
@@ -219,9 +217,9 @@ def __post_init__(self) -> None:
219217
def to_dict(
220218
self,
221219
*,
222-
credentials_hash: Optional[str] = None,
220+
credentials_hash: str | None = None,
223221
exclude_credentials: bool = False,
224-
) -> Dict[str, Dict[str, str]]:
222+
) -> dict[str, dict[str, str]]:
225223
"""Convert device config to dict."""
226224
if credentials_hash is not None or exclude_credentials:
227225
self.credentials = None
@@ -230,7 +228,7 @@ def to_dict(
230228
return _dataclass_to_dict(self)
231229

232230
@staticmethod
233-
def from_dict(config_dict: Dict[str, Dict[str, str]]) -> "DeviceConfig":
231+
def from_dict(config_dict: dict[str, dict[str, str]]) -> "DeviceConfig":
234232
"""Return device config from dict."""
235233
if isinstance(config_dict, dict):
236234
return _dataclass_from_dict(DeviceConfig, config_dict)

0 commit comments

Comments
 (0)
0