From b1be89743943d1cd716dc7dec13ef6d504534485 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cerm=C3=A1k?= Date: Thu, 5 Mar 2026 21:11:25 +0100 Subject: [PATCH] Use Python 3.14(.3) in CI and base image (#6586) * Use Python 3.14(.3) in CI and base image Update base image to the latest tag using Python 3.14.3 and update Python version in CI workflows to 3.14. With Python 3.14, backports.zstd is no longer necessary as it's now available in the standard library. * Update wheels ABI in the wheels builder to cp314 * Use explicit Python fix version in GH actions Specify explicitly Python 3.14.3, as the setup-python action otherwise default to 3.14.2 when 3.14.3, leading to different version in CI and in production. * Update Python version references in pyproject.toml * Fix all ruff quoted-annotation (UP037) errors * Revert unquoting of DBus types in tests and ignore UP037 where needed --- .github/copilot-instructions.md | 4 ++-- .github/workflows/builder.yml | 4 ++-- .github/workflows/ci.yaml | 2 +- build.yaml | 4 ++-- pyproject.toml | 6 +++--- requirements.txt | 1 - supervisor/dbus/enum.py | 4 ++-- supervisor/dbus/udisks2/block.py | 2 +- supervisor/dbus/udisks2/data.py | 12 ++++++------ supervisor/dbus/udisks2/drive.py | 2 +- supervisor/dbus/udisks2/nvme_controller.py | 2 +- supervisor/host/configuration.py | 2 +- supervisor/mounts/mount.py | 4 ++-- supervisor/os/data_disk.py | 2 +- supervisor/os/manager.py | 2 +- tests/dbus/test_interface.py | 4 ++-- tests/utils/test_dbus.py | 2 +- 17 files changed, 29 insertions(+), 30 deletions(-) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 9f2cc75cb..e29381f4d 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -91,8 +91,8 @@ availability. ### Python Requirements -- **Compatibility**: Python 3.13+ -- **Language Features**: Use modern Python features: + - **Compatibility**: Python 3.14+ + - **Language Features**: Use modern Python features: - Type hints with `typing` module - f-strings (preferred over `%` or `.format()`) - Dataclasses and enum classes diff --git a/.github/workflows/builder.yml b/.github/workflows/builder.yml index 671bbab5d..07809fff3 100644 --- a/.github/workflows/builder.yml +++ b/.github/workflows/builder.yml @@ -33,7 +33,7 @@ on: - setup.py env: - DEFAULT_PYTHON: "3.13" + DEFAULT_PYTHON: "3.14.3" COSIGN_VERSION: "v2.5.3" CRANE_VERSION: "v0.20.7" CRANE_SHA256: "8ef3564d264e6b5ca93f7b7f5652704c4dd29d33935aff6947dd5adefd05953e" @@ -106,7 +106,7 @@ jobs: - runs-on: ubuntu-24.04-arm arch: aarch64 env: - WHEELS_ABI: cp313 + WHEELS_ABI: cp314 WHEELS_TAG: musllinux_1_2 WHEELS_APK_DEPS: "libffi-dev;openssl-dev;yaml-dev" WHEELS_SKIP_BINARY: aiohttp diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index d90edc0cd..9c81c47cb 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -8,7 +8,7 @@ on: pull_request: ~ env: - DEFAULT_PYTHON: "3.13" + DEFAULT_PYTHON: "3.14.3" PRE_COMMIT_CACHE: ~/.cache/pre-commit MYPY_CACHE_VERSION: 1 diff --git a/build.yaml b/build.yaml index 74ae80e19..2039ee22e 100644 --- a/build.yaml +++ b/build.yaml @@ -1,7 +1,7 @@ image: ghcr.io/home-assistant/{arch}-hassio-supervisor build_from: - aarch64: ghcr.io/home-assistant/aarch64-base-python:3.13-alpine3.22-2025.12.2 - amd64: ghcr.io/home-assistant/amd64-base-python:3.13-alpine3.22-2025.12.2 + aarch64: ghcr.io/home-assistant/aarch64-base-python:3.14-alpine3.22-2026.02.0 + amd64: ghcr.io/home-assistant/amd64-base-python:3.14-alpine3.22-2026.02.0 cosign: base_identity: https://github.com/home-assistant/docker-base/.* identity: https://github.com/home-assistant/supervisor/.* diff --git a/pyproject.toml b/pyproject.toml index d7cca4a94..ade87d979 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,7 +12,7 @@ authors = [ { name = "The Home Assistant Authors", email = "hello@home-assistant.io" }, ] keywords = ["docker", "home-assistant", "api"] -requires-python = ">=3.13.0" +requires-python = ">=3.14.0" [project.urls] "Homepage" = "https://www.home-assistant.io/" @@ -31,7 +31,7 @@ include-package-data = true include = ["supervisor*"] [tool.pylint.MAIN] -py-version = "3.13" +py-version = "3.14" # Use a conservative default here; 2 should speed up most setups and not hurt # any too bad. Override on command line as appropriate. jobs = 2 @@ -368,7 +368,7 @@ split-on-trailing-comma = false [tool.ruff.lint.per-file-ignores] # DBus Service Mocks must use typing and names understood by dbus-fast -"tests/dbus_service_mocks/*.py" = ["F722", "F821", "N815"] +"tests/dbus_service_mocks/*.py" = ["F722", "F821", "N815", "UP037"] [tool.ruff.lint.mccabe] max-complexity = 25 diff --git a/requirements.txt b/requirements.txt index 18116571b..16fd4a730 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,7 +4,6 @@ aiohttp==3.13.3 atomicwrites-homeassistant==1.4.1 attrs==25.4.0 awesomeversion==25.8.0 -backports.zstd==1.3.0 blockbuster==1.5.26 brotli==1.2.0 ciso8601==2.3.3 diff --git a/supervisor/dbus/enum.py b/supervisor/dbus/enum.py index 3c36d04ff..2e428214c 100644 --- a/supervisor/dbus/enum.py +++ b/supervisor/dbus/enum.py @@ -32,7 +32,7 @@ class DBusStrEnum(StrEnum): """StrEnum that tolerates unknown values from D-Bus.""" @classmethod - def _missing_(cls, value: object) -> "DBusStrEnum | None": + def _missing_(cls, value: object) -> DBusStrEnum | None: if not isinstance(value, str): return None _report_unknown_value(cls, value) @@ -46,7 +46,7 @@ class DBusIntEnum(IntEnum): """IntEnum that tolerates unknown values from D-Bus.""" @classmethod - def _missing_(cls, value: object) -> "DBusIntEnum | None": + def _missing_(cls, value: object) -> DBusIntEnum | None: if not isinstance(value, int): return None _report_unknown_value(cls, value) diff --git a/supervisor/dbus/udisks2/block.py b/supervisor/dbus/udisks2/block.py index a99ba9e1c..9560c7d38 100644 --- a/supervisor/dbus/udisks2/block.py +++ b/supervisor/dbus/udisks2/block.py @@ -72,7 +72,7 @@ class UDisks2Block(DBusInterfaceProxy): @staticmethod async def new( object_path: str, bus: MessageBus, *, sync_properties: bool = True - ) -> "UDisks2Block": + ) -> UDisks2Block: """Create and connect object.""" obj = UDisks2Block(object_path, sync_properties=sync_properties) await obj.connect(bus) diff --git a/supervisor/dbus/udisks2/data.py b/supervisor/dbus/udisks2/data.py index 6097b326c..c323a9600 100644 --- a/supervisor/dbus/udisks2/data.py +++ b/supervisor/dbus/udisks2/data.py @@ -46,7 +46,7 @@ class DeviceSpecification: partlabel: str | None = None @staticmethod - def from_dict(data: DeviceSpecificationDataType) -> "DeviceSpecification": + def from_dict(data: DeviceSpecificationDataType) -> DeviceSpecification: """Create DeviceSpecification from dict.""" return DeviceSpecification( path=Path(data["path"]) if "path" in data else None, @@ -108,7 +108,7 @@ class FormatOptions: auth_no_user_interaction: bool | None = None @staticmethod - def from_dict(data: FormatOptionsDataType) -> "FormatOptions": + def from_dict(data: FormatOptionsDataType) -> FormatOptions: """Create FormatOptions from dict.""" return FormatOptions( label=data.get("label"), @@ -182,7 +182,7 @@ class MountOptions: auth_no_user_interaction: bool | None = None @staticmethod - def from_dict(data: MountOptionsDataType) -> "MountOptions": + def from_dict(data: MountOptionsDataType) -> MountOptions: """Create MountOptions from dict.""" return MountOptions( fstype=data.get("fstype"), @@ -226,7 +226,7 @@ class UnmountOptions: auth_no_user_interaction: bool | None = None @staticmethod - def from_dict(data: UnmountOptionsDataType) -> "UnmountOptions": + def from_dict(data: UnmountOptionsDataType) -> UnmountOptions: """Create MountOptions from dict.""" return UnmountOptions( force=data.get("force"), @@ -268,7 +268,7 @@ class CreatePartitionOptions: auth_no_user_interaction: bool | None = None @staticmethod - def from_dict(data: CreatePartitionOptionsDataType) -> "CreatePartitionOptions": + def from_dict(data: CreatePartitionOptionsDataType) -> CreatePartitionOptions: """Create CreatePartitionOptions from dict.""" return CreatePartitionOptions( partition_type=data.get("partition-type"), @@ -310,7 +310,7 @@ class DeletePartitionOptions: auth_no_user_interaction: bool | None = None @staticmethod - def from_dict(data: DeletePartitionOptionsDataType) -> "DeletePartitionOptions": + def from_dict(data: DeletePartitionOptionsDataType) -> DeletePartitionOptions: """Create DeletePartitionOptions from dict.""" return DeletePartitionOptions( tear_down=data.get("tear-down"), diff --git a/supervisor/dbus/udisks2/drive.py b/supervisor/dbus/udisks2/drive.py index ab51fe6e7..bfb36ab3e 100644 --- a/supervisor/dbus/udisks2/drive.py +++ b/supervisor/dbus/udisks2/drive.py @@ -51,7 +51,7 @@ class UDisks2Drive(DBusInterfaceProxy): await self._reload_interfaces() @staticmethod - async def new(object_path: str, bus: MessageBus) -> "UDisks2Drive": + async def new(object_path: str, bus: MessageBus) -> UDisks2Drive: """Create and connect object.""" obj = UDisks2Drive(object_path) await obj.connect(bus) diff --git a/supervisor/dbus/udisks2/nvme_controller.py b/supervisor/dbus/udisks2/nvme_controller.py index 52b27111c..fd0f38da5 100644 --- a/supervisor/dbus/udisks2/nvme_controller.py +++ b/supervisor/dbus/udisks2/nvme_controller.py @@ -96,7 +96,7 @@ class UDisks2NVMeController(DBusInterfaceProxy): super().__init__() @staticmethod - async def new(object_path: str, bus: MessageBus) -> "UDisks2NVMeController": + async def new(object_path: str, bus: MessageBus) -> UDisks2NVMeController: """Create and connect object.""" obj = UDisks2NVMeController(object_path) await obj.connect(bus) diff --git a/supervisor/host/configuration.py b/supervisor/host/configuration.py index d3e2e5df1..4d09288ce 100644 --- a/supervisor/host/configuration.py +++ b/supervisor/host/configuration.py @@ -153,7 +153,7 @@ class Interface: ) @staticmethod - def from_dbus_interface(inet: NetworkInterface) -> "Interface": + def from_dbus_interface(inet: NetworkInterface) -> Interface: """Coerce a dbus interface into normal Interface.""" if inet.settings and inet.settings.ipv4: ipv4_setting = IpSetting( diff --git a/supervisor/mounts/mount.py b/supervisor/mounts/mount.py index b2318c067..f82c7d7a2 100644 --- a/supervisor/mounts/mount.py +++ b/supervisor/mounts/mount.py @@ -59,7 +59,7 @@ class Mount(CoreSysAttributes, ABC): ) @classmethod - def from_dict(cls, coresys: CoreSys, data: MountData) -> "Mount": + def from_dict(cls, coresys: CoreSys, data: MountData) -> Mount: """Make dictionary into mount object.""" if cls not in [Mount, NetworkMount]: return cls(coresys, data) @@ -562,7 +562,7 @@ class BindMount(Mount): usage: MountUsage | None = None, where: PurePath | None = None, read_only: bool = False, - ) -> "BindMount": + ) -> BindMount: """Create a new bind mount instance.""" return BindMount( coresys, diff --git a/supervisor/os/data_disk.py b/supervisor/os/data_disk.py index 78dd99f91..9bf2af5a9 100644 --- a/supervisor/os/data_disk.py +++ b/supervisor/os/data_disk.py @@ -57,7 +57,7 @@ class Disk: @staticmethod def from_udisks2_drive( drive: UDisks2Drive, drive_block_device: UDisks2Block - ) -> "Disk": + ) -> Disk: """Convert UDisks2Drive into a Disk object.""" return Disk( vendor=drive.vendor, diff --git a/supervisor/os/manager.py b/supervisor/os/manager.py index 0369a8a22..3746df11c 100644 --- a/supervisor/os/manager.py +++ b/supervisor/os/manager.py @@ -52,7 +52,7 @@ class SlotStatus: parent: str | None = None @classmethod - def from_dict(cls, data: SlotStatusDataType) -> "SlotStatus": + def from_dict(cls, data: SlotStatusDataType) -> SlotStatus: """Create SlotStatus from dictionary.""" return cls( class_=data["class"], diff --git a/tests/dbus/test_interface.py b/tests/dbus/test_interface.py index cb1c083a5..59db085a9 100644 --- a/tests/dbus/test_interface.py +++ b/tests/dbus/test_interface.py @@ -28,12 +28,12 @@ class TestInterface(DBusServiceMock): self.object_path = object_path @signal(name="TestSignal") - def test_signal(self, value: str) -> "s": # noqa: F821 + def test_signal(self, value: str) -> "s": # noqa: F821, UP037 """Send test signal.""" return value @dbus_property(access=PropertyAccess.READ, name="TestProp") - def test_prop(self) -> "u": # noqa: F821 + def test_prop(self) -> "u": # noqa: F821, UP037 """Get test property.""" return 4 diff --git a/tests/utils/test_dbus.py b/tests/utils/test_dbus.py index 3fb2445d0..0b8561620 100644 --- a/tests/utils/test_dbus.py +++ b/tests/utils/test_dbus.py @@ -29,7 +29,7 @@ class TestInterface(DBusServiceMock): object_path = DBUS_OBJECT_BASE @method(name="Test") - def test(self, _: "b") -> None: # noqa: F821 + def test(self, _: "b") -> None: # noqa: F821, UP037 """Do Test method.""" @signal(name="Test")