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")