diff --git a/.strict-typing b/.strict-typing index 87bef3033c4..e6f0f96368b 100644 --- a/.strict-typing +++ b/.strict-typing @@ -13,6 +13,7 @@ homeassistant.components.camera.* homeassistant.components.cover.* homeassistant.components.device_automation.* homeassistant.components.elgato.* +homeassistant.components.fritzbox.* homeassistant.components.frontend.* homeassistant.components.geo_location.* homeassistant.components.gios.* diff --git a/homeassistant/components/fritzbox/__init__.py b/homeassistant/components/fritzbox/__init__.py index dc200195748..124719b93c1 100644 --- a/homeassistant/components/fritzbox/__init__.py +++ b/homeassistant/components/fritzbox/__init__.py @@ -17,14 +17,16 @@ from homeassistant.const import ( CONF_USERNAME, EVENT_HOMEASSISTANT_STOP, ) -from homeassistant.core import HomeAssistant +from homeassistant.core import Event, HomeAssistant from homeassistant.exceptions import ConfigEntryAuthFailed +from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.update_coordinator import ( CoordinatorEntity, DataUpdateCoordinator, ) from .const import CONF_CONNECTIONS, CONF_COORDINATOR, DOMAIN, LOGGER, PLATFORMS +from .model import EntityInfo async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: @@ -63,7 +65,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: data[device.ain] = device return data - async def async_update_coordinator(): + async def async_update_coordinator() -> dict[str, FritzhomeDevice]: """Fetch all device data.""" return await hass.async_add_executor_job(_update_fritz_devices) @@ -81,7 +83,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: hass.config_entries.async_setup_platforms(entry, PLATFORMS) - def logout_fritzbox(event): + def logout_fritzbox(event: Event) -> None: """Close connections to this fritzbox.""" fritz.logout() @@ -109,10 +111,10 @@ class FritzBoxEntity(CoordinatorEntity): def __init__( self, - entity_info: dict[str, str], - coordinator: DataUpdateCoordinator, + entity_info: EntityInfo, + coordinator: DataUpdateCoordinator[dict[str, FritzhomeDevice]], ain: str, - ): + ) -> None: """Initialize the FritzBox entity.""" super().__init__(coordinator) @@ -128,7 +130,7 @@ class FritzBoxEntity(CoordinatorEntity): return self.coordinator.data[self.ain] @property - def device_info(self): + def device_info(self) -> DeviceInfo: """Return device specific attributes.""" return { "name": self.device.name, @@ -139,21 +141,21 @@ class FritzBoxEntity(CoordinatorEntity): } @property - def unique_id(self): + def unique_id(self) -> str: """Return the unique ID of the device.""" return self._unique_id @property - def name(self): + def name(self) -> str: """Return the name of the device.""" return self._name @property - def unit_of_measurement(self): + def unit_of_measurement(self) -> str | None: """Return the unit of measurement.""" return self._unit_of_measurement @property - def device_class(self): + def device_class(self) -> str | None: """Return the device class.""" return self._device_class diff --git a/homeassistant/components/fritzbox/binary_sensor.py b/homeassistant/components/fritzbox/binary_sensor.py index 807ca41ca64..242e3d6e644 100644 --- a/homeassistant/components/fritzbox/binary_sensor.py +++ b/homeassistant/components/fritzbox/binary_sensor.py @@ -1,4 +1,6 @@ """Support for Fritzbox binary sensors.""" +from __future__ import annotations + from homeassistant.components.binary_sensor import ( DEVICE_CLASS_WINDOW, BinarySensorEntity, @@ -21,7 +23,7 @@ async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback ) -> None: """Set up the FRITZ!SmartHome binary sensor from ConfigEntry.""" - entities = [] + entities: list[FritzboxBinarySensor] = [] coordinator = hass.data[FRITZBOX_DOMAIN][entry.entry_id][CONF_COORDINATOR] for ain, device in coordinator.data.items(): @@ -48,8 +50,8 @@ class FritzboxBinarySensor(FritzBoxEntity, BinarySensorEntity): """Representation of a binary FRITZ!SmartHome device.""" @property - def is_on(self): + def is_on(self) -> bool: """Return true if sensor is on.""" if not self.device.present: return False - return self.device.alert_state + return self.device.alert_state # type: ignore [no-any-return] diff --git a/homeassistant/components/fritzbox/climate.py b/homeassistant/components/fritzbox/climate.py index 947a95a7a5b..c50e0d4f270 100644 --- a/homeassistant/components/fritzbox/climate.py +++ b/homeassistant/components/fritzbox/climate.py @@ -1,4 +1,8 @@ """Support for AVM FRITZ!SmartHome thermostate devices.""" +from __future__ import annotations + +from typing import Any + from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( ATTR_HVAC_MODE, @@ -34,6 +38,7 @@ from .const import ( CONF_COORDINATOR, DOMAIN as FRITZBOX_DOMAIN, ) +from .model import ClimateExtraAttributes SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE @@ -55,7 +60,7 @@ async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback ) -> None: """Set up the FRITZ!SmartHome thermostat from ConfigEntry.""" - entities = [] + entities: list[FritzboxThermostat] = [] coordinator = hass.data[FRITZBOX_DOMAIN][entry.entry_id][CONF_COORDINATOR] for ain, device in coordinator.data.items(): @@ -82,53 +87,53 @@ class FritzboxThermostat(FritzBoxEntity, ClimateEntity): """The thermostat class for FRITZ!SmartHome thermostates.""" @property - def supported_features(self): + def supported_features(self) -> int: """Return the list of supported features.""" return SUPPORT_FLAGS @property - def available(self): + def available(self) -> bool: """Return if thermostat is available.""" - return self.device.present + return self.device.present # type: ignore [no-any-return] @property - def temperature_unit(self): + def temperature_unit(self) -> str: """Return the unit of measurement that is used.""" return TEMP_CELSIUS @property - def precision(self): + def precision(self) -> float: """Return precision 0.5.""" return PRECISION_HALVES @property - def current_temperature(self): + def current_temperature(self) -> float: """Return the current temperature.""" - return self.device.actual_temperature + return self.device.actual_temperature # type: ignore [no-any-return] @property - def target_temperature(self): + def target_temperature(self) -> float: """Return the temperature we try to reach.""" if self.device.target_temperature == ON_API_TEMPERATURE: return ON_REPORT_SET_TEMPERATURE if self.device.target_temperature == OFF_API_TEMPERATURE: return OFF_REPORT_SET_TEMPERATURE - return self.device.target_temperature + return self.device.target_temperature # type: ignore [no-any-return] - async def async_set_temperature(self, **kwargs): + async def async_set_temperature(self, **kwargs: Any) -> None: """Set new target temperature.""" - if ATTR_HVAC_MODE in kwargs: - hvac_mode = kwargs.get(ATTR_HVAC_MODE) + if kwargs.get(ATTR_HVAC_MODE) is not None: + hvac_mode = kwargs[ATTR_HVAC_MODE] await self.async_set_hvac_mode(hvac_mode) - elif ATTR_TEMPERATURE in kwargs: - temperature = kwargs.get(ATTR_TEMPERATURE) + elif kwargs.get(ATTR_TEMPERATURE) is not None: + temperature = kwargs[ATTR_TEMPERATURE] await self.hass.async_add_executor_job( self.device.set_target_temperature, temperature ) await self.coordinator.async_refresh() @property - def hvac_mode(self): + def hvac_mode(self) -> str: """Return the current operation mode.""" if ( self.device.target_temperature == OFF_REPORT_SET_TEMPERATURE @@ -139,11 +144,11 @@ class FritzboxThermostat(FritzBoxEntity, ClimateEntity): return HVAC_MODE_HEAT @property - def hvac_modes(self): + def hvac_modes(self) -> list[str]: """Return the list of available operation modes.""" return OPERATION_LIST - async def async_set_hvac_mode(self, hvac_mode): + async def async_set_hvac_mode(self, hvac_mode: str) -> None: """Set new operation mode.""" if hvac_mode == HVAC_MODE_OFF: await self.async_set_temperature(temperature=OFF_REPORT_SET_TEMPERATURE) @@ -153,7 +158,7 @@ class FritzboxThermostat(FritzBoxEntity, ClimateEntity): ) @property - def preset_mode(self): + def preset_mode(self) -> str | None: """Return current preset mode.""" if self.device.target_temperature == self.device.comfort_temperature: return PRESET_COMFORT @@ -162,11 +167,11 @@ class FritzboxThermostat(FritzBoxEntity, ClimateEntity): return None @property - def preset_modes(self): + def preset_modes(self) -> list[str]: """Return supported preset modes.""" return [PRESET_ECO, PRESET_COMFORT] - async def async_set_preset_mode(self, preset_mode): + async def async_set_preset_mode(self, preset_mode: str) -> None: """Set preset mode.""" if preset_mode == PRESET_COMFORT: await self.async_set_temperature( @@ -176,19 +181,19 @@ class FritzboxThermostat(FritzBoxEntity, ClimateEntity): await self.async_set_temperature(temperature=self.device.eco_temperature) @property - def min_temp(self): + def min_temp(self) -> int: """Return the minimum temperature.""" return MIN_TEMPERATURE @property - def max_temp(self): + def max_temp(self) -> int: """Return the maximum temperature.""" return MAX_TEMPERATURE @property - def extra_state_attributes(self): + def extra_state_attributes(self) -> ClimateExtraAttributes: """Return the device specific state attributes.""" - attrs = { + attrs: ClimateExtraAttributes = { ATTR_STATE_BATTERY_LOW: self.device.battery_low, ATTR_STATE_DEVICE_LOCKED: self.device.device_lock, ATTR_STATE_LOCKED: self.device.lock, diff --git a/homeassistant/components/fritzbox/config_flow.py b/homeassistant/components/fritzbox/config_flow.py index 50a9a5f0117..3ae3368f4ae 100644 --- a/homeassistant/components/fritzbox/config_flow.py +++ b/homeassistant/components/fritzbox/config_flow.py @@ -1,17 +1,22 @@ """Config flow for AVM FRITZ!SmartHome.""" +from __future__ import annotations + +from typing import Any from urllib.parse import urlparse from pyfritzhome import Fritzhome, LoginError from requests.exceptions import HTTPError import voluptuous as vol -from homeassistant import config_entries from homeassistant.components.ssdp import ( ATTR_SSDP_LOCATION, ATTR_UPNP_FRIENDLY_NAME, ATTR_UPNP_UDN, ) +from homeassistant.config_entries import ConfigEntry, ConfigFlow from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME +from homeassistant.data_entry_flow import FlowResult +from homeassistant.helpers.typing import DiscoveryInfoType from .const import DEFAULT_HOST, DEFAULT_USERNAME, DOMAIN @@ -36,22 +41,22 @@ RESULT_NOT_SUPPORTED = "not_supported" RESULT_SUCCESS = "success" -class FritzboxConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class FritzboxConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a AVM FRITZ!SmartHome config flow.""" VERSION = 1 - def __init__(self): + def __init__(self) -> None: """Initialize flow.""" - self._entry = None - self._host = None - self._name = None - self._password = None - self._username = None + self._entry: ConfigEntry | None = None + self._host: str | None = None + self._name: str | None = None + self._password: str | None = None + self._username: str | None = None - def _get_entry(self): + def _get_entry(self, name: str) -> FlowResult: return self.async_create_entry( - title=self._name, + title=name, data={ CONF_HOST: self._host, CONF_PASSWORD: self._password, @@ -59,7 +64,8 @@ class FritzboxConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): }, ) - async def _update_entry(self): + async def _update_entry(self) -> None: + assert self._entry is not None self.hass.config_entries.async_update_entry( self._entry, data={ @@ -70,7 +76,7 @@ class FritzboxConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ) await self.hass.config_entries.async_reload(self._entry.entry_id) - def _try_connect(self): + def _try_connect(self) -> str: """Try to connect and check auth.""" fritzbox = Fritzhome( host=self._host, user=self._username, password=self._password @@ -87,7 +93,9 @@ class FritzboxConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): except OSError: return RESULT_NO_DEVICES_FOUND - async def async_step_user(self, user_input=None): + async def async_step_user( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: """Handle a flow initialized by the user.""" errors = {} @@ -95,14 +103,14 @@ class FritzboxConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): self._async_abort_entries_match({CONF_HOST: user_input[CONF_HOST]}) self._host = user_input[CONF_HOST] - self._name = user_input[CONF_HOST] + self._name = str(user_input[CONF_HOST]) self._password = user_input[CONF_PASSWORD] self._username = user_input[CONF_USERNAME] result = await self.hass.async_add_executor_job(self._try_connect) if result == RESULT_SUCCESS: - return self._get_entry() + return self._get_entry(self._name) if result != RESULT_INVALID_AUTH: return self.async_abort(reason=result) errors["base"] = result @@ -111,9 +119,10 @@ class FritzboxConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): step_id="user", data_schema=DATA_SCHEMA_USER, errors=errors ) - async def async_step_ssdp(self, discovery_info): + async def async_step_ssdp(self, discovery_info: DiscoveryInfoType) -> FlowResult: """Handle a flow initialized by discovery.""" host = urlparse(discovery_info[ATTR_SSDP_LOCATION]).hostname + assert isinstance(host, str) self.context[CONF_HOST] = host uuid = discovery_info.get(ATTR_UPNP_UDN) @@ -135,12 +144,14 @@ class FritzboxConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): return self.async_abort(reason="already_configured") self._host = host - self._name = discovery_info.get(ATTR_UPNP_FRIENDLY_NAME) or host + self._name = str(discovery_info.get(ATTR_UPNP_FRIENDLY_NAME) or host) self.context["title_placeholders"] = {"name": self._name} return await self.async_step_confirm() - async def async_step_confirm(self, user_input=None): + async def async_step_confirm( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: """Handle user-confirmation of discovered node.""" errors = {} @@ -150,7 +161,8 @@ class FritzboxConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): result = await self.hass.async_add_executor_job(self._try_connect) if result == RESULT_SUCCESS: - return self._get_entry() + assert self._name is not None + return self._get_entry(self._name) if result != RESULT_INVALID_AUTH: return self.async_abort(reason=result) errors["base"] = result @@ -162,16 +174,20 @@ class FritzboxConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): errors=errors, ) - async def async_step_reauth(self, data): + async def async_step_reauth(self, data: dict[str, str]) -> FlowResult: """Trigger a reauthentication flow.""" - self._entry = self.hass.config_entries.async_get_entry(self.context["entry_id"]) + entry = self.hass.config_entries.async_get_entry(self.context["entry_id"]) + assert entry is not None + self._entry = entry self._host = data[CONF_HOST] - self._name = data[CONF_HOST] + self._name = str(data[CONF_HOST]) self._username = data[CONF_USERNAME] return await self.async_step_reauth_confirm() - async def async_step_reauth_confirm(self, user_input=None): + async def async_step_reauth_confirm( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: """Handle reauthorization flow.""" errors = {} diff --git a/homeassistant/components/fritzbox/const.py b/homeassistant/components/fritzbox/const.py index edfc13d49fe..af2ec30312f 100644 --- a/homeassistant/components/fritzbox/const.py +++ b/homeassistant/components/fritzbox/const.py @@ -1,26 +1,29 @@ """Constants for the AVM FRITZ!SmartHome integration.""" +from __future__ import annotations + import logging +from typing import Final -ATTR_STATE_BATTERY_LOW = "battery_low" -ATTR_STATE_DEVICE_LOCKED = "device_locked" -ATTR_STATE_HOLIDAY_MODE = "holiday_mode" -ATTR_STATE_LOCKED = "locked" -ATTR_STATE_SUMMER_MODE = "summer_mode" -ATTR_STATE_WINDOW_OPEN = "window_open" +ATTR_STATE_BATTERY_LOW: Final = "battery_low" +ATTR_STATE_DEVICE_LOCKED: Final = "device_locked" +ATTR_STATE_HOLIDAY_MODE: Final = "holiday_mode" +ATTR_STATE_LOCKED: Final = "locked" +ATTR_STATE_SUMMER_MODE: Final = "summer_mode" +ATTR_STATE_WINDOW_OPEN: Final = "window_open" -ATTR_TEMPERATURE_UNIT = "temperature_unit" +ATTR_TEMPERATURE_UNIT: Final = "temperature_unit" -ATTR_TOTAL_CONSUMPTION = "total_consumption" -ATTR_TOTAL_CONSUMPTION_UNIT = "total_consumption_unit" +ATTR_TOTAL_CONSUMPTION: Final = "total_consumption" +ATTR_TOTAL_CONSUMPTION_UNIT: Final = "total_consumption_unit" -CONF_CONNECTIONS = "connections" -CONF_COORDINATOR = "coordinator" +CONF_CONNECTIONS: Final = "connections" +CONF_COORDINATOR: Final = "coordinator" -DEFAULT_HOST = "fritz.box" -DEFAULT_USERNAME = "admin" +DEFAULT_HOST: Final = "fritz.box" +DEFAULT_USERNAME: Final = "admin" -DOMAIN = "fritzbox" +DOMAIN: Final = "fritzbox" -LOGGER: logging.Logger = logging.getLogger(__package__) +LOGGER: Final[logging.Logger] = logging.getLogger(__package__) -PLATFORMS = ["binary_sensor", "climate", "switch", "sensor"] +PLATFORMS: Final[list[str]] = ["binary_sensor", "climate", "switch", "sensor"] diff --git a/homeassistant/components/fritzbox/model.py b/homeassistant/components/fritzbox/model.py new file mode 100644 index 00000000000..1cde7b9ca70 --- /dev/null +++ b/homeassistant/components/fritzbox/model.py @@ -0,0 +1,43 @@ +"""Models for the AVM FRITZ!SmartHome integration.""" +from __future__ import annotations + +from typing import TypedDict + + +class EntityInfo(TypedDict): + """TypedDict for EntityInfo.""" + + name: str + entity_id: str + unit_of_measurement: str | None + device_class: str | None + + +class ClimateExtraAttributes(TypedDict, total=False): + """TypedDict for climates extra attributes.""" + + battery_low: bool + device_locked: bool + locked: bool + battery_level: int + holiday_mode: bool + summer_mode: bool + window_open: bool + + +class SensorExtraAttributes(TypedDict): + """TypedDict for sensors extra attributes.""" + + device_locked: bool + locked: bool + + +class SwitchExtraAttributes(TypedDict, total=False): + """TypedDict for sensors extra attributes.""" + + device_locked: bool + locked: bool + total_consumption: str + total_consumption_unit: str + temperature: str + temperature_unit: str diff --git a/homeassistant/components/fritzbox/sensor.py b/homeassistant/components/fritzbox/sensor.py index 836b4fb407c..ceae0bb757f 100644 --- a/homeassistant/components/fritzbox/sensor.py +++ b/homeassistant/components/fritzbox/sensor.py @@ -1,4 +1,6 @@ """Support for AVM FRITZ!SmartHome temperature sensor only devices.""" +from __future__ import annotations + from homeassistant.components.sensor import SensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( @@ -20,13 +22,14 @@ from .const import ( CONF_COORDINATOR, DOMAIN as FRITZBOX_DOMAIN, ) +from .model import SensorExtraAttributes async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback ) -> None: """Set up the FRITZ!SmartHome sensor from ConfigEntry.""" - entities = [] + entities: list[FritzBoxEntity] = [] coordinator = hass.data[FRITZBOX_DOMAIN][entry.entry_id][CONF_COORDINATOR] for ain, device in coordinator.data.items(): @@ -69,23 +72,23 @@ class FritzBoxBatterySensor(FritzBoxEntity, SensorEntity): """The entity class for FRITZ!SmartHome sensors.""" @property - def state(self): + def state(self) -> int | None: """Return the state of the sensor.""" - return self.device.battery_level + return self.device.battery_level # type: ignore [no-any-return] class FritzBoxTempSensor(FritzBoxEntity, SensorEntity): """The entity class for FRITZ!SmartHome temperature sensors.""" @property - def state(self): + def state(self) -> float | None: """Return the state of the sensor.""" - return self.device.temperature + return self.device.temperature # type: ignore [no-any-return] @property - def extra_state_attributes(self): + def extra_state_attributes(self) -> SensorExtraAttributes: """Return the state attributes of the device.""" - attrs = { + attrs: SensorExtraAttributes = { ATTR_STATE_DEVICE_LOCKED: self.device.device_lock, ATTR_STATE_LOCKED: self.device.lock, } diff --git a/homeassistant/components/fritzbox/switch.py b/homeassistant/components/fritzbox/switch.py index 39d8c4e8ec2..82581473714 100644 --- a/homeassistant/components/fritzbox/switch.py +++ b/homeassistant/components/fritzbox/switch.py @@ -1,4 +1,8 @@ """Support for AVM FRITZ!SmartHome switch devices.""" +from __future__ import annotations + +from typing import Any + from homeassistant.components.switch import SwitchEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( @@ -23,6 +27,7 @@ from .const import ( CONF_COORDINATOR, DOMAIN as FRITZBOX_DOMAIN, ) +from .model import SwitchExtraAttributes ATTR_TOTAL_CONSUMPTION_UNIT_VALUE = ENERGY_KILO_WATT_HOUR @@ -31,7 +36,7 @@ async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback ) -> None: """Set up the FRITZ!SmartHome switch from ConfigEntry.""" - entities = [] + entities: list[FritzboxSwitch] = [] coordinator = hass.data[FRITZBOX_DOMAIN][entry.entry_id][CONF_COORDINATOR] for ain, device in coordinator.data.items(): @@ -58,31 +63,32 @@ class FritzboxSwitch(FritzBoxEntity, SwitchEntity): """The switch class for FRITZ!SmartHome switches.""" @property - def available(self): + def available(self) -> bool: """Return if switch is available.""" - return self.device.present + return self.device.present # type: ignore [no-any-return] @property - def is_on(self): + def is_on(self) -> bool: """Return true if the switch is on.""" - return self.device.switch_state + return self.device.switch_state # type: ignore [no-any-return] - async def async_turn_on(self, **kwargs): + async def async_turn_on(self, **kwargs: Any) -> None: """Turn the switch on.""" await self.hass.async_add_executor_job(self.device.set_switch_state_on) await self.coordinator.async_refresh() - async def async_turn_off(self, **kwargs): + async def async_turn_off(self, **kwargs: Any) -> None: """Turn the switch off.""" await self.hass.async_add_executor_job(self.device.set_switch_state_off) await self.coordinator.async_refresh() @property - def extra_state_attributes(self): + def extra_state_attributes(self) -> SwitchExtraAttributes: """Return the state attributes of the device.""" - attrs = {} - attrs[ATTR_STATE_DEVICE_LOCKED] = self.device.device_lock - attrs[ATTR_STATE_LOCKED] = self.device.lock + attrs: SwitchExtraAttributes = { + ATTR_STATE_DEVICE_LOCKED: self.device.device_lock, + ATTR_STATE_LOCKED: self.device.lock, + } if self.device.has_powermeter: attrs[ @@ -99,6 +105,6 @@ class FritzboxSwitch(FritzBoxEntity, SwitchEntity): return attrs @property - def current_power_w(self): + def current_power_w(self) -> float: """Return the current power usage in W.""" - return self.device.power / 1000 + return self.device.power / 1000 # type: ignore [no-any-return] diff --git a/homeassistant/const.py b/homeassistant/const.py index 489652b3c12..8b965995754 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -294,7 +294,7 @@ ATTR_ID = "id" ATTR_NAME: Final = "name" # Contains one string or a list of strings, each being an entity id -ATTR_ENTITY_ID = "entity_id" +ATTR_ENTITY_ID: Final = "entity_id" # Contains one string or a list of strings, each being an area id ATTR_AREA_ID = "area_id" @@ -314,7 +314,7 @@ ATTR_IDENTIFIERS: Final = "identifiers" ATTR_ICON = "icon" # The unit of measurement if applicable -ATTR_UNIT_OF_MEASUREMENT = "unit_of_measurement" +ATTR_UNIT_OF_MEASUREMENT: Final = "unit_of_measurement" CONF_UNIT_SYSTEM_METRIC: str = "metric" CONF_UNIT_SYSTEM_IMPERIAL: str = "imperial" @@ -332,7 +332,7 @@ ATTR_MODEL: Final = "model" ATTR_SW_VERSION: Final = "sw_version" ATTR_BATTERY_CHARGING = "battery_charging" -ATTR_BATTERY_LEVEL = "battery_level" +ATTR_BATTERY_LEVEL: Final = "battery_level" ATTR_WAKEUP = "wake_up_interval" # For devices which support a code attribute @@ -379,10 +379,10 @@ ATTR_RESTORED = "restored" ATTR_SUPPORTED_FEATURES = "supported_features" # Class of device within its domain -ATTR_DEVICE_CLASS = "device_class" +ATTR_DEVICE_CLASS: Final = "device_class" # Temperature attribute -ATTR_TEMPERATURE = "temperature" +ATTR_TEMPERATURE: Final = "temperature" # #### UNITS OF MEASUREMENT #### # Power units diff --git a/mypy.ini b/mypy.ini index 8bc55b0d82e..3fc323dab44 100644 --- a/mypy.ini +++ b/mypy.ini @@ -154,6 +154,17 @@ no_implicit_optional = true warn_return_any = true warn_unreachable = true +[mypy-homeassistant.components.fritzbox.*] +check_untyped_defs = true +disallow_incomplete_defs = true +disallow_subclassing_any = true +disallow_untyped_calls = true +disallow_untyped_decorators = true +disallow_untyped_defs = true +no_implicit_optional = true +warn_return_any = true +warn_unreachable = true + [mypy-homeassistant.components.frontend.*] check_untyped_defs = true disallow_incomplete_defs = true @@ -781,9 +792,6 @@ ignore_errors = true [mypy-homeassistant.components.freebox.*] ignore_errors = true -[mypy-homeassistant.components.fritzbox.*] -ignore_errors = true - [mypy-homeassistant.components.garmin_connect.*] ignore_errors = true diff --git a/script/hassfest/mypy_config.py b/script/hassfest/mypy_config.py index 72bb8879312..5e412517628 100644 --- a/script/hassfest/mypy_config.py +++ b/script/hassfest/mypy_config.py @@ -69,7 +69,6 @@ IGNORED_MODULES: Final[list[str]] = [ "homeassistant.components.fortios.*", "homeassistant.components.foscam.*", "homeassistant.components.freebox.*", - "homeassistant.components.fritzbox.*", "homeassistant.components.garmin_connect.*", "homeassistant.components.geniushub.*", "homeassistant.components.glances.*",