Add python 3.14 to supported versions (#1599)

* Add python 3.14 to our supported versions and to our CI
* Convert to use pyproject.toml standard dependency groups
* Fix any issues reported by CI

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Teemu R.
2026-07-07 20:42:09 +02:00
committed by GitHub
parent 531be8437a
commit d492dd4a44
11 changed files with 1488 additions and 895 deletions

View File

@@ -25,7 +25,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
python-version: [3.13] python-version: [3.14]
steps: steps:
- name: Checkout Source Files - name: Checkout Source Files
id: checkout id: checkout
@@ -51,7 +51,7 @@ jobs:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
strategy: strategy:
matrix: matrix:
python-version: [3.11, 3.12, 3.13] python-version: [3.11, 3.12, 3.13, 3.14]
os: [ubuntu-latest, macos-latest, windows-latest] os: [ubuntu-latest, macos-latest, windows-latest]
extras: [false, true] extras: [false, true]
exclude: exclude:

View File

@@ -2,13 +2,13 @@ repos:
- repo: https://github.com/astral-sh/uv-pre-commit - repo: https://github.com/astral-sh/uv-pre-commit
# uv version. # uv version.
rev: 0.5.30 rev: 0.9.5
hooks: hooks:
# Update the uv lockfile # Update the uv lockfile
- id: uv-lock - id: uv-lock
- repo: https://github.com/pre-commit/pre-commit-hooks - repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0 rev: v6.0.0
hooks: hooks:
- id: trailing-whitespace - id: trailing-whitespace
- id: end-of-file-fixer - id: end-of-file-fixer
@@ -22,14 +22,14 @@ repos:
- "--indent=4" - "--indent=4"
- repo: https://github.com/astral-sh/ruff-pre-commit - repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.9.6 rev: v0.14.2
hooks: hooks:
- id: ruff - id: ruff
args: [--fix, --exit-non-zero-on-fix] args: [--fix, --exit-non-zero-on-fix]
- id: ruff-format - id: ruff-format
- repo: https://github.com/PyCQA/doc8 - repo: https://github.com/PyCQA/doc8
rev: 'v1.1.2' rev: 'v2.0.0'
hooks: hooks:
- id: doc8 - id: doc8
additional_dependencies: [tomli] additional_dependencies: [tomli]

View File

@@ -11,12 +11,8 @@ build:
tools: tools:
python: "3" python: "3"
jobs: jobs:
install:
- pip install --upgrade pip
- pip install --group 'docs'
pre_build: pre_build:
- python -m sphinx -b linkcheck docs/source/ $READTHEDOCS_OUTPUT/linkcheck - python -m sphinx -b linkcheck docs/source/ $READTHEDOCS_OUTPUT/linkcheck
python:
install:
- method: pip
path: .
extra_requirements:
- docs

View File

@@ -166,7 +166,7 @@ async def handle_device(
if autosave: if autosave:
save = "y" save = "y"
else: else:
save = click.prompt( save = await click.prompt(
f"Do you want to save the above content to {save_filename} (y/n)" f"Do you want to save the above content to {save_filename} (y/n)"
) )
if save == "y": if save == "y":

View File

@@ -96,7 +96,7 @@ def json_formatter_cb(result: Any, **kwargs) -> None:
async def invoke_subcommand( async def invoke_subcommand(
command: click.BaseCommand, command: click.Command,
ctx: click.Context, ctx: click.Context,
args: list[str] | None = None, args: list[str] | None = None,
**extra: Any, **extra: Any,
@@ -200,7 +200,7 @@ async def _get_child_device(
if child_option is not None: if child_option is not None:
if child_option is OPTIONAL_VALUE_FLAG: if child_option is OPTIONAL_VALUE_FLAG:
msg = _list_children() msg = _list_children()
child_index_option = click.prompt( child_index_option = await click.prompt(
f"\n{msg}\nEnter the index number of the child device", f"\n{msg}\nEnter the index number of the child device",
type=click.IntRange(0, len(device.children) - 1), type=click.IntRange(0, len(device.children) - 1),
) )

View File

@@ -414,7 +414,7 @@ async def shell(dev: Device) -> None:
@click.argument("parameters", default=None, required=False) @click.argument("parameters", default=None, required=False)
async def raw_command(ctx, module, command, parameters): async def raw_command(ctx, module, command, parameters):
"""Run a raw command on the device.""" """Run a raw command on the device."""
logging.warning("Deprecated, use 'kasa command --module %s %s'", module, command) logging.warning("Deprecated, use 'kasa command --module %s %s'", module, command) # noqa: LOG015
return await ctx.forward(cmd_command) return await ctx.forward(cmd_command)

View File

@@ -746,6 +746,8 @@ class Discover:
device = device_class(config.host, config=config) device = device_class(config.host, config=config)
sys_info = _extract_sys_info(info) sys_info = _extract_sys_info(info)
device_type = sys_info.get("mic_type", sys_info.get("type")) device_type = sys_info.get("mic_type", sys_info.get("type"))
if device_type is None:
raise UnsupportedDeviceError("type nor mic_type found in sysinfo response")
login_version = ( login_version = (
sys_info.get("stream_version") if device_type == "IOT.IPCAMERA" else None sys_info.get("stream_version") if device_type == "IOT.IPCAMERA" else None
) )

View File

@@ -25,7 +25,7 @@ from warnings import warn
from ..device import Device, DeviceInfo, WifiNetwork from ..device import Device, DeviceInfo, WifiNetwork
from ..device_type import DeviceType from ..device_type import DeviceType
from ..deviceconfig import DeviceConfig from ..deviceconfig import DeviceConfig
from ..exceptions import KasaException from ..exceptions import KasaException, UnsupportedDeviceError
from ..feature import Feature from ..feature import Feature
from ..module import Module from ..module import Module
from ..modulemapping import ModuleMapping, ModuleName from ..modulemapping import ModuleMapping, ModuleName
@@ -763,6 +763,9 @@ class IotDevice(Device):
# Get other info # Get other info
device_family = sys_info.get("type", sys_info.get("mic_type")) device_family = sys_info.get("type", sys_info.get("mic_type"))
if device_family is None:
raise UnsupportedDeviceError("type nor mic_type found in sysinfo response")
device_type = IotDevice._get_device_type_from_sys_info(info) device_type = IotDevice._get_device_type_from_sys_info(info)
fw_version_full = sys_info["sw_ver"] fw_version_full = sys_info["sw_ver"]
if " " in fw_version_full: if " " in fw_version_full:

View File

@@ -11,7 +11,7 @@ dependencies = [
"cryptography>=1.9", "cryptography>=1.9",
"aiohttp>=3", "aiohttp>=3",
"tzdata>=2024.2 ; platform_system == 'Windows'", "tzdata>=2024.2 ; platform_system == 'Windows'",
"mashumaro>=3.14", "mashumaro>=3.20"
] ]
classifiers = [ classifiers = [
@@ -21,17 +21,11 @@ classifiers = [
"Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
] ]
[project.optional-dependencies] [project.optional-dependencies]
speedups = ["orjson>=3.9.1", "kasa-crypt>=0.2.0"] speedups = ["orjson>=3.11.1", "kasa-crypt>=0.2.0"]
docs = [
"sphinx_rtd_theme~=2.0",
"sphinxcontrib-programoutput~=0.0",
"myst-parser",
"docutils>=0.17",
"sphinx>=7.4.7",
]
shell = ["ptpython", "rich"] shell = ["ptpython", "rich"]
[project.urls] [project.urls]
@@ -43,8 +37,8 @@ shell = ["ptpython", "rich"]
[project.scripts] [project.scripts]
kasa = "kasa.cli.__main__:cli" kasa = "kasa.cli.__main__:cli"
[tool.uv] [dependency-groups]
dev-dependencies = [ dev = [
"pytest", "pytest",
"pytest-cov", "pytest-cov",
"pytest-asyncio", "pytest-asyncio",
@@ -64,7 +58,13 @@ dev-dependencies = [
"ruff>=0.9.0", "ruff>=0.9.0",
"uv>=0.11.26", "uv>=0.11.26",
] ]
docs = [
"sphinx_rtd_theme~=2.0",
"sphinxcontrib-programoutput~=0.0",
"myst-parser",
"docutils>=0.17",
"sphinx>=7.4.7",
]
[build-system] [build-system]
requires = ["hatchling"] requires = ["hatchling"]

View File

@@ -15,6 +15,7 @@ import aiohttp
import pytest import pytest
from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import padding as asymmetric_padding from cryptography.hazmat.primitives.asymmetric import padding as asymmetric_padding
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicKey
from freezegun.api import FrozenDateTimeFactory from freezegun.api import FrozenDateTimeFactory
from yarl import URL from yarl import URL
@@ -501,6 +502,7 @@ class MockAesDevice:
client_pub_key_data = base64.b64decode(client_pub_key.encode()) client_pub_key_data = base64.b64decode(client_pub_key.encode())
client_pub_key = serialization.load_der_public_key(client_pub_key_data, None) client_pub_key = serialization.load_der_public_key(client_pub_key_data, None)
assert isinstance(client_pub_key, RSAPublicKey)
encrypted_key = client_pub_key.encrypt(KEY_IV, asymmetric_padding.PKCS1v15()) encrypted_key = client_pub_key.encrypt(KEY_IV, asymmetric_padding.PKCS1v15())
key_64 = base64.b64encode(encrypted_key).decode() key_64 = base64.b64encode(encrypted_key).decode()
return self._mock_response( return self._mock_response(

2320
uv.lock generated

File diff suppressed because it is too large Load Diff