Skip to content

Commit 4a00caa

Browse files
authored
Fix mypy issues in docker, hardware and homeassistant modules (#5805)
* Fix mypy issues in docker and hardware modules * Fix mypy issues in homeassistant module * Fix async_send_command typing * Fixes from feedback
1 parent 59a7e95 commit 4a00caa

22 files changed

+248
-154
lines changed

Diff for: supervisor/api/homeassistant.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ async def options(self, request: web.Request) -> None:
118118
body = await api_validate(SCHEMA_OPTIONS, request)
119119

120120
if ATTR_IMAGE in body:
121-
self.sys_homeassistant.image = body[ATTR_IMAGE]
121+
self.sys_homeassistant.set_image(body[ATTR_IMAGE])
122122
self.sys_homeassistant.override_image = (
123123
self.sys_homeassistant.image != self.sys_homeassistant.default_image
124124
)

Diff for: supervisor/auth.py

+13-5
Original file line numberDiff line numberDiff line change
@@ -145,13 +145,21 @@ async def change_password(self, username: str, password: str) -> None:
145145
async def list_users(self) -> list[dict[str, Any]]:
146146
"""List users on the Home Assistant instance."""
147147
try:
148-
return await self.sys_homeassistant.websocket.async_send_command(
148+
users: (
149+
list[dict[str, Any]] | None
150+
) = await self.sys_homeassistant.websocket.async_send_command(
149151
{ATTR_TYPE: "config/auth/list"}
150152
)
151-
except HomeAssistantWSError:
152-
_LOGGER.error("Can't request listing users on Home Assistant!")
153-
154-
raise AuthListUsersError()
153+
except HomeAssistantWSError as err:
154+
raise AuthListUsersError(
155+
f"Can't request listing users on Home Assistant: {err}", _LOGGER.error
156+
) from err
157+
158+
if users is not None:
159+
return users
160+
raise AuthListUsersError(
161+
"Can't request listing users on Home Assistant!", _LOGGER.error
162+
)
155163

156164
@staticmethod
157165
def _rehash(value: str, salt2: str = "") -> str:

Diff for: supervisor/bootstrap.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ async def initialize_coresys() -> CoreSys:
7070
coresys.addons = await AddonManager(coresys).load_config()
7171
coresys.backups = await BackupManager(coresys).load_config()
7272
coresys.host = await HostManager(coresys).post_init()
73-
coresys.hardware = await HardwareManager(coresys).post_init()
73+
coresys.hardware = await HardwareManager.create(coresys)
7474
coresys.ingress = await Ingress(coresys).load_config()
7575
coresys.tasks = Tasks(coresys)
7676
coresys.services = await ServiceManager(coresys).load_config()

Diff for: supervisor/const.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from dataclasses import dataclass
44
from enum import StrEnum
5-
from ipaddress import ip_network
5+
from ipaddress import IPv4Network
66
from pathlib import Path
77
from sys import version_info as systemversion
88
from typing import Self
@@ -41,8 +41,8 @@
4141
SYSTEMD_JOURNAL_VOLATILE = Path("/run/log/journal")
4242

4343
DOCKER_NETWORK = "hassio"
44-
DOCKER_NETWORK_MASK = ip_network("172.30.32.0/23")
45-
DOCKER_NETWORK_RANGE = ip_network("172.30.33.0/24")
44+
DOCKER_NETWORK_MASK = IPv4Network("172.30.32.0/23")
45+
DOCKER_NETWORK_RANGE = IPv4Network("172.30.33.0/24")
4646

4747
# This needs to match the dockerd --cpu-rt-runtime= argument.
4848
DOCKER_CPU_RUNTIME_TOTAL = 950_000

Diff for: supervisor/docker/addon.py

+26-19
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,12 @@
22

33
from __future__ import annotations
44

5-
from collections.abc import Awaitable
65
from contextlib import suppress
7-
from ipaddress import IPv4Address, ip_address
6+
from ipaddress import IPv4Address
87
import logging
98
import os
109
from pathlib import Path
11-
from typing import TYPE_CHECKING
10+
from typing import TYPE_CHECKING, cast
1211

1312
from attr import evolve
1413
from awesomeversion import AwesomeVersion
@@ -73,7 +72,7 @@
7372

7473
_LOGGER: logging.Logger = logging.getLogger(__name__)
7574

76-
NO_ADDDRESS = ip_address("0.0.0.0")
75+
NO_ADDDRESS = IPv4Address("0.0.0.0")
7776

7877

7978
class DockerAddon(DockerInterface):
@@ -101,10 +100,12 @@ def ip_address(self) -> IPv4Address:
101100
"""Return IP address of this container."""
102101
if self.addon.host_network:
103102
return self.sys_docker.network.gateway
103+
if not self._meta:
104+
return NO_ADDDRESS
104105

105106
# Extract IP-Address
106107
try:
107-
return ip_address(
108+
return IPv4Address(
108109
self._meta["NetworkSettings"]["Networks"]["hassio"]["IPAddress"]
109110
)
110111
except (KeyError, TypeError, ValueError):
@@ -121,7 +122,7 @@ def version(self) -> AwesomeVersion:
121122
return self.addon.version
122123

123124
@property
124-
def arch(self) -> str:
125+
def arch(self) -> str | None:
125126
"""Return arch of Docker image."""
126127
if self.addon.legacy:
127128
return self.sys_arch.default
@@ -133,9 +134,9 @@ def name(self) -> str:
133134
return DockerAddon.slug_to_name(self.addon.slug)
134135

135136
@property
136-
def environment(self) -> dict[str, str | None]:
137+
def environment(self) -> dict[str, str | int | None]:
137138
"""Return environment for Docker add-on."""
138-
addon_env = self.addon.environment or {}
139+
addon_env = cast(dict[str, str | int | None], self.addon.environment or {})
139140

140141
# Provide options for legacy add-ons
141142
if self.addon.legacy:
@@ -336,7 +337,7 @@ def mounts(self) -> list[Mount]:
336337
"""Return mounts for container."""
337338
addon_mapping = self.addon.map_volumes
338339

339-
target_data_path = ""
340+
target_data_path: str | None = None
340341
if MappingType.DATA in addon_mapping:
341342
target_data_path = addon_mapping[MappingType.DATA].path
342343

@@ -677,12 +678,12 @@ def build_image():
677678
)
678679

679680
try:
680-
image, log = await self.sys_run_in_executor(build_image)
681+
docker_image, log = await self.sys_run_in_executor(build_image)
681682

682683
_LOGGER.debug("Build %s:%s done: %s", self.image, version, log)
683684

684685
# Update meta data
685-
self._meta = image.attrs
686+
self._meta = docker_image.attrs
686687

687688
except (docker.errors.DockerException, requests.RequestException) as err:
688689
_LOGGER.error("Can't build %s:%s: %s", self.image, version, err)
@@ -699,9 +700,14 @@ def build_image():
699700

700701
_LOGGER.info("Build %s:%s done", self.image, version)
701702

702-
def export_image(self, tar_file: Path) -> Awaitable[None]:
703-
"""Export current images into a tar file."""
704-
return self.sys_docker.export_image(self.image, self.version, tar_file)
703+
def export_image(self, tar_file: Path) -> None:
704+
"""Export current images into a tar file.
705+
706+
Must be run in executor.
707+
"""
708+
if not self.image:
709+
raise RuntimeError("Cannot export without image!")
710+
self.sys_docker.export_image(self.image, self.version, tar_file)
705711

706712
@Job(
707713
name="docker_addon_import_image",
@@ -805,15 +811,15 @@ async def stop(self, remove_container: bool = True) -> None:
805811
):
806812
self.sys_resolution.dismiss_issue(self.addon.device_access_missing_issue)
807813

808-
async def _validate_trust(
809-
self, image_id: str, image: str, version: AwesomeVersion
810-
) -> None:
814+
async def _validate_trust(self, image_id: str) -> None:
811815
"""Validate trust of content."""
812816
if not self.addon.signed:
813817
return
814818

815819
checksum = image_id.partition(":")[2]
816-
return await self.sys_security.verify_content(self.addon.codenotary, checksum)
820+
return await self.sys_security.verify_content(
821+
cast(str, self.addon.codenotary), checksum
822+
)
817823

818824
@Job(
819825
name="docker_addon_hardware_events",
@@ -834,7 +840,8 @@ async def _hardware_events(self, device: Device) -> None:
834840
self.sys_docker.containers.get, self.name
835841
)
836842
except docker.errors.NotFound:
837-
self.sys_bus.remove_listener(self._hw_listener)
843+
if self._hw_listener:
844+
self.sys_bus.remove_listener(self._hw_listener)
838845
self._hw_listener = None
839846
return
840847
except (docker.errors.DockerException, requests.RequestException) as err:

Diff for: supervisor/docker/homeassistant.py

+3-5
Original file line numberDiff line numberDiff line change
@@ -248,14 +248,12 @@ def is_initialize(self) -> Awaitable[bool]:
248248
self.sys_homeassistant.version,
249249
)
250250

251-
async def _validate_trust(
252-
self, image_id: str, image: str, version: AwesomeVersion
253-
) -> None:
251+
async def _validate_trust(self, image_id: str) -> None:
254252
"""Validate trust of content."""
255253
try:
256-
if version != LANDINGPAGE and version < _VERIFY_TRUST:
254+
if self.version in {None, LANDINGPAGE} or self.version < _VERIFY_TRUST:
257255
return
258256
except AwesomeVersionCompareException:
259257
return
260258

261-
await super()._validate_trust(image_id, image, version)
259+
await super()._validate_trust(image_id)

Diff for: supervisor/docker/interface.py

+20-16
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,14 @@
22

33
from __future__ import annotations
44

5+
from abc import ABC, abstractmethod
56
from collections import defaultdict
67
from collections.abc import Awaitable
78
from contextlib import suppress
89
import logging
910
import re
1011
from time import time
11-
from typing import Any
12+
from typing import Any, cast
1213
from uuid import uuid4
1314

1415
from awesomeversion import AwesomeVersion
@@ -79,7 +80,7 @@ def _container_state_from_model(docker_container: Container) -> ContainerState:
7980
return ContainerState.STOPPED
8081

8182

82-
class DockerInterface(JobGroup):
83+
class DockerInterface(JobGroup, ABC):
8384
"""Docker Supervisor interface."""
8485

8586
def __init__(self, coresys: CoreSys):
@@ -100,9 +101,9 @@ def timeout(self) -> int:
100101
return 10
101102

102103
@property
103-
def name(self) -> str | None:
104+
@abstractmethod
105+
def name(self) -> str:
104106
"""Return name of Docker container."""
105-
return None
106107

107108
@property
108109
def meta_config(self) -> dict[str, Any]:
@@ -153,7 +154,7 @@ def arch(self) -> str | None:
153154
@property
154155
def in_progress(self) -> bool:
155156
"""Return True if a task is in progress."""
156-
return self.active_job
157+
return self.active_job is not None
157158

158159
@property
159160
def restart_policy(self) -> RestartPolicy | None:
@@ -230,7 +231,10 @@ async def install(
230231
) -> None:
231232
"""Pull docker image."""
232233
image = image or self.image
233-
arch = arch or self.sys_arch.supervisor
234+
if not image:
235+
raise ValueError("Cannot pull without an image!")
236+
237+
image_arch = str(arch) if arch else self.sys_arch.supervisor
234238

235239
_LOGGER.info("Downloading docker image %s with tag %s.", image, version)
236240
try:
@@ -242,12 +246,12 @@ async def install(
242246
docker_image = await self.sys_run_in_executor(
243247
self.sys_docker.images.pull,
244248
f"{image}:{version!s}",
245-
platform=MAP_ARCH[arch],
249+
platform=MAP_ARCH[image_arch],
246250
)
247251

248252
# Validate content
249253
try:
250-
await self._validate_trust(docker_image.id, image, version)
254+
await self._validate_trust(cast(str, docker_image.id))
251255
except CodeNotaryError:
252256
with suppress(docker.errors.DockerException):
253257
await self.sys_run_in_executor(
@@ -355,7 +359,7 @@ async def attach(
355359
self.sys_bus.fire_event(
356360
BusEvent.DOCKER_CONTAINER_STATE_CHANGE,
357361
DockerContainerStateEvent(
358-
self.name, state, docker_container.id, int(time())
362+
self.name, state, cast(str, docker_container.id), int(time())
359363
),
360364
)
361365

@@ -454,7 +458,9 @@ async def check_image(
454458
expected_arch: CpuArch | None = None,
455459
) -> None:
456460
"""Check we have expected image with correct arch."""
457-
expected_arch = expected_arch or self.sys_arch.supervisor
461+
expected_image_arch = (
462+
str(expected_arch) if expected_arch else self.sys_arch.supervisor
463+
)
458464
image_name = f"{expected_image}:{version!s}"
459465
if self.image == expected_image:
460466
try:
@@ -472,13 +478,13 @@ async def check_image(
472478
image_arch = f"{image_arch}/{image.attrs['Variant']}"
473479

474480
# If we have an image and its the right arch, all set
475-
if MAP_ARCH[expected_arch] == image_arch:
481+
if MAP_ARCH[expected_image_arch] == image_arch:
476482
return
477483

478484
# We're missing the image we need. Stop and clean up what we have then pull the right one
479485
with suppress(DockerError):
480486
await self.remove()
481-
await self.install(version, expected_image, arch=expected_arch)
487+
await self.install(version, expected_image, arch=expected_image_arch)
482488

483489
@Job(
484490
name="docker_interface_update",
@@ -613,9 +619,7 @@ def run_inside(self, command: str) -> Awaitable[CommandReturn]:
613619
self.sys_docker.container_run_inside, self.name, command
614620
)
615621

616-
async def _validate_trust(
617-
self, image_id: str, image: str, version: AwesomeVersion
618-
) -> None:
622+
async def _validate_trust(self, image_id: str) -> None:
619623
"""Validate trust of content."""
620624
checksum = image_id.partition(":")[2]
621625
return await self.sys_security.verify_own_content(checksum)
@@ -634,4 +638,4 @@ async def check_trust(self) -> None:
634638
except (docker.errors.DockerException, requests.RequestException):
635639
return
636640

637-
await self._validate_trust(image.id, self.image, self.version)
641+
await self._validate_trust(cast(str, image.id))

0 commit comments

Comments
 (0)