diff --git a/.strict-typing b/.strict-typing index 0a0f5a2496c..1f990bd81c5 100644 --- a/.strict-typing +++ b/.strict-typing @@ -210,6 +210,7 @@ homeassistant.components.pvoutput.* homeassistant.components.qnap_qsw.* homeassistant.components.rainmachine.* homeassistant.components.rdw.* +homeassistant.components.radarr.* homeassistant.components.recollect_waste.* homeassistant.components.recorder.* homeassistant.components.remote.* diff --git a/homeassistant/components/radarr/__init__.py b/homeassistant/components/radarr/__init__.py index 5e32f64b7ad..403bedda94a 100644 --- a/homeassistant/components/radarr/__init__.py +++ b/homeassistant/components/radarr/__init__.py @@ -1,6 +1,8 @@ """The Radarr component.""" from __future__ import annotations +from typing import Any, cast + from aiopyarr.models.host_configuration import PyArrHostConfiguration from aiopyarr.radarr_client import RadarrClient @@ -29,6 +31,7 @@ from .coordinator import ( MoviesDataUpdateCoordinator, RadarrDataUpdateCoordinator, StatusDataUpdateCoordinator, + T, ) PLATFORMS = [Platform.BINARY_SENSOR, Platform.SENSOR] @@ -65,7 +68,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: host_configuration=host_configuration, session=async_get_clientsession(hass, entry.data[CONF_VERIFY_SSL]), ) - coordinators: dict[str, RadarrDataUpdateCoordinator] = { + coordinators: dict[str, RadarrDataUpdateCoordinator[Any]] = { "status": StatusDataUpdateCoordinator(hass, host_configuration, radarr), "disk_space": DiskSpaceDataUpdateCoordinator(hass, host_configuration, radarr), "health": HealthDataUpdateCoordinator(hass, host_configuration, radarr), @@ -86,15 +89,15 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: return unload_ok -class RadarrEntity(CoordinatorEntity[RadarrDataUpdateCoordinator]): +class RadarrEntity(CoordinatorEntity[RadarrDataUpdateCoordinator[T]]): """Defines a base Radarr entity.""" _attr_has_entity_name = True - coordinator: RadarrDataUpdateCoordinator + coordinator: RadarrDataUpdateCoordinator[T] def __init__( self, - coordinator: RadarrDataUpdateCoordinator, + coordinator: RadarrDataUpdateCoordinator[T], description: EntityDescription, ) -> None: """Create Radarr entity.""" @@ -113,5 +116,7 @@ class RadarrEntity(CoordinatorEntity[RadarrDataUpdateCoordinator]): name=self.coordinator.config_entry.title, ) if isinstance(self.coordinator, StatusDataUpdateCoordinator): - device_info[ATTR_SW_VERSION] = self.coordinator.data.version + device_info[ATTR_SW_VERSION] = cast( + StatusDataUpdateCoordinator, self.coordinator + ).data.version return device_info diff --git a/homeassistant/components/radarr/binary_sensor.py b/homeassistant/components/radarr/binary_sensor.py index 2a1a729e6f4..3952a694e94 100644 --- a/homeassistant/components/radarr/binary_sensor.py +++ b/homeassistant/components/radarr/binary_sensor.py @@ -1,6 +1,8 @@ """Support for Radarr binary sensors.""" from __future__ import annotations +from aiopyarr import Health + from homeassistant.components.binary_sensor import ( BinarySensorDeviceClass, BinarySensorEntity, @@ -32,7 +34,7 @@ async def async_setup_entry( async_add_entities([RadarrBinarySensor(coordinator, BINARY_SENSOR_TYPE)]) -class RadarrBinarySensor(RadarrEntity, BinarySensorEntity): +class RadarrBinarySensor(RadarrEntity[list[Health]], BinarySensorEntity): """Implementation of a Radarr binary sensor.""" @property diff --git a/homeassistant/components/radarr/coordinator.py b/homeassistant/components/radarr/coordinator.py index 06ea32e790f..dfcd1e3a269 100644 --- a/homeassistant/components/radarr/coordinator.py +++ b/homeassistant/components/radarr/coordinator.py @@ -3,9 +3,9 @@ from __future__ import annotations from abc import abstractmethod from datetime import timedelta -from typing import Generic, TypeVar, cast +from typing import Generic, TypeVar, Union, cast -from aiopyarr import Health, RootFolder, SystemStatus, exceptions +from aiopyarr import Health, RadarrMovie, RootFolder, SystemStatus, exceptions from aiopyarr.models.host_configuration import PyArrHostConfiguration from aiopyarr.radarr_client import RadarrClient @@ -16,10 +16,10 @@ from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, Upda from .const import DOMAIN, LOGGER -T = TypeVar("T", SystemStatus, list[RootFolder], list[Health], int) +T = TypeVar("T", bound=Union[SystemStatus, list[RootFolder], list[Health], int]) -class RadarrDataUpdateCoordinator(DataUpdateCoordinator, Generic[T]): +class RadarrDataUpdateCoordinator(DataUpdateCoordinator[T], Generic[T]): """Data update coordinator for the Radarr integration.""" config_entry: ConfigEntry @@ -58,7 +58,7 @@ class RadarrDataUpdateCoordinator(DataUpdateCoordinator, Generic[T]): raise NotImplementedError -class StatusDataUpdateCoordinator(RadarrDataUpdateCoordinator): +class StatusDataUpdateCoordinator(RadarrDataUpdateCoordinator[SystemStatus]): """Status update coordinator for Radarr.""" async def _fetch_data(self) -> SystemStatus: @@ -66,15 +66,15 @@ class StatusDataUpdateCoordinator(RadarrDataUpdateCoordinator): return await self.api_client.async_get_system_status() -class DiskSpaceDataUpdateCoordinator(RadarrDataUpdateCoordinator): +class DiskSpaceDataUpdateCoordinator(RadarrDataUpdateCoordinator[list[RootFolder]]): """Disk space update coordinator for Radarr.""" async def _fetch_data(self) -> list[RootFolder]: """Fetch the data.""" - return cast(list, await self.api_client.async_get_root_folders()) + return cast(list[RootFolder], await self.api_client.async_get_root_folders()) -class HealthDataUpdateCoordinator(RadarrDataUpdateCoordinator): +class HealthDataUpdateCoordinator(RadarrDataUpdateCoordinator[list[Health]]): """Health update coordinator.""" async def _fetch_data(self) -> list[Health]: @@ -82,9 +82,9 @@ class HealthDataUpdateCoordinator(RadarrDataUpdateCoordinator): return await self.api_client.async_get_failed_health_checks() -class MoviesDataUpdateCoordinator(RadarrDataUpdateCoordinator): +class MoviesDataUpdateCoordinator(RadarrDataUpdateCoordinator[int]): """Movies update coordinator.""" async def _fetch_data(self) -> int: """Fetch the movies data.""" - return len(cast(list, await self.api_client.async_get_movies())) + return len(cast(list[RadarrMovie], await self.api_client.async_get_movies())) diff --git a/homeassistant/components/radarr/sensor.py b/homeassistant/components/radarr/sensor.py index e424844c602..27d1a5487a2 100644 --- a/homeassistant/components/radarr/sensor.py +++ b/homeassistant/components/radarr/sensor.py @@ -4,10 +4,10 @@ from __future__ import annotations from collections.abc import Callable from copy import deepcopy from dataclasses import dataclass -from datetime import timezone -from typing import Generic +from datetime import datetime, timezone +from typing import Any, Generic -from aiopyarr import Diskspace, RootFolder +from aiopyarr import Diskspace, RootFolder, SystemStatus import voluptuous as vol from homeassistant.components.sensor import ( @@ -32,7 +32,7 @@ from homeassistant.core import HomeAssistant import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType, StateType +from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from . import RadarrEntity from .const import DOMAIN @@ -50,8 +50,8 @@ def get_space(data: list[Diskspace], name: str) -> str: def get_modified_description( - description: RadarrSensorEntityDescription, mount: RootFolder -) -> tuple[RadarrSensorEntityDescription, str]: + description: RadarrSensorEntityDescription[T], mount: RootFolder +) -> tuple[RadarrSensorEntityDescription[T], str]: """Return modified description and folder name.""" desc = deepcopy(description) name = mount.path.rsplit("/")[-1].rsplit("\\")[-1] @@ -64,7 +64,7 @@ def get_modified_description( class RadarrSensorEntityDescriptionMixIn(Generic[T]): """Mixin for required keys.""" - value_fn: Callable[[T, str], str] + value_fn: Callable[[T, str], str | int | datetime] @dataclass @@ -74,12 +74,12 @@ class RadarrSensorEntityDescription( """Class to describe a Radarr sensor.""" description_fn: Callable[ - [RadarrSensorEntityDescription, RootFolder], - tuple[RadarrSensorEntityDescription, str] | None, - ] = lambda _, __: None + [RadarrSensorEntityDescription[T], RootFolder], + tuple[RadarrSensorEntityDescription[T], str] | None, + ] | None = None -SENSOR_TYPES: dict[str, RadarrSensorEntityDescription] = { +SENSOR_TYPES: dict[str, RadarrSensorEntityDescription[Any]] = { "disk_space": RadarrSensorEntityDescription( key="disk_space", name="Disk space", @@ -88,7 +88,7 @@ SENSOR_TYPES: dict[str, RadarrSensorEntityDescription] = { value_fn=get_space, description_fn=get_modified_description, ), - "movie": RadarrSensorEntityDescription( + "movie": RadarrSensorEntityDescription[int]( key="movies", name="Movies", native_unit_of_measurement="Movies", @@ -96,7 +96,7 @@ SENSOR_TYPES: dict[str, RadarrSensorEntityDescription] = { entity_registry_enabled_default=False, value_fn=lambda data, _: data, ), - "status": RadarrSensorEntityDescription( + "status": RadarrSensorEntityDescription[SystemStatus]( key="start_time", name="Start time", device_class=SensorDeviceClass.TIMESTAMP, @@ -152,10 +152,10 @@ async def async_setup_entry( async_add_entities: AddEntitiesCallback, ) -> None: """Set up Radarr sensors based on a config entry.""" - coordinators: dict[str, RadarrDataUpdateCoordinator] = hass.data[DOMAIN][ + coordinators: dict[str, RadarrDataUpdateCoordinator[Any]] = hass.data[DOMAIN][ entry.entry_id ] - entities = [] + entities: list[RadarrSensor[Any]] = [] for coordinator_type, description in SENSOR_TYPES.items(): coordinator = coordinators[coordinator_type] if coordinator_type != "disk_space": @@ -169,16 +169,16 @@ async def async_setup_entry( async_add_entities(entities) -class RadarrSensor(RadarrEntity, SensorEntity): +class RadarrSensor(RadarrEntity[T], SensorEntity): """Implementation of the Radarr sensor.""" - coordinator: RadarrDataUpdateCoordinator - entity_description: RadarrSensorEntityDescription + coordinator: RadarrDataUpdateCoordinator[T] + entity_description: RadarrSensorEntityDescription[T] def __init__( self, - coordinator: RadarrDataUpdateCoordinator, - description: RadarrSensorEntityDescription, + coordinator: RadarrDataUpdateCoordinator[T], + description: RadarrSensorEntityDescription[T], folder_name: str = "", ) -> None: """Create Radarr entity.""" @@ -186,6 +186,6 @@ class RadarrSensor(RadarrEntity, SensorEntity): self.folder_name = folder_name @property - def native_value(self) -> StateType: + def native_value(self) -> str | int | datetime: """Return the state of the sensor.""" return self.entity_description.value_fn(self.coordinator.data, self.folder_name) diff --git a/mypy.ini b/mypy.ini index 9b748a21124..31a5268abcf 100644 --- a/mypy.ini +++ b/mypy.ini @@ -1852,6 +1852,16 @@ disallow_untyped_defs = true warn_return_any = true warn_unreachable = true +[mypy-homeassistant.components.radarr.*] +check_untyped_defs = true +disallow_incomplete_defs = true +disallow_subclassing_any = true +disallow_untyped_calls = true +disallow_untyped_decorators = true +disallow_untyped_defs = true +warn_return_any = true +warn_unreachable = true + [mypy-homeassistant.components.recollect_waste.*] check_untyped_defs = true disallow_incomplete_defs = true