diff --git a/.strict-typing b/.strict-typing index 24d35370ca5..9cfe0b5a9a7 100644 --- a/.strict-typing +++ b/.strict-typing @@ -84,6 +84,7 @@ homeassistant.components.scene.* homeassistant.components.select.* homeassistant.components.sensor.* homeassistant.components.shelly.* +homeassistant.components.simplisafe.* homeassistant.components.slack.* homeassistant.components.sonos.media_player homeassistant.components.ssdp.* diff --git a/homeassistant/components/simplisafe/__init__.py b/homeassistant/components/simplisafe/__init__.py index baf1bcd7b2a..0853aa3974c 100644 --- a/homeassistant/components/simplisafe/__init__.py +++ b/homeassistant/components/simplisafe/__init__.py @@ -1,17 +1,28 @@ """Support for SimpliSafe alarm systems.""" +from __future__ import annotations + import asyncio +from collections.abc import Awaitable +from typing import Callable, cast from uuid import UUID from simplipy import get_api +from simplipy.api import API from simplipy.errors import ( EndpointUnavailableError, InvalidCredentialsError, SimplipyError, ) +from simplipy.sensor.v2 import SensorV2 +from simplipy.sensor.v3 import SensorV3 +from simplipy.system import SystemNotification +from simplipy.system.v2 import SystemV2 +from simplipy.system.v3 import SystemV3 import voluptuous as vol +from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_CODE, CONF_CODE, CONF_PASSWORD, CONF_USERNAME -from homeassistant.core import CoreState, callback +from homeassistant.core import CoreState, HomeAssistant, ServiceCall, callback from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady from homeassistant.helpers import ( aiohttp_client, @@ -109,7 +120,7 @@ SERVICE_SET_SYSTEM_PROPERTIES_SCHEMA = SERVICE_BASE_SCHEMA.extend( CONFIG_SCHEMA = cv.deprecated(DOMAIN) -async def async_get_client_id(hass): +async def async_get_client_id(hass: HomeAssistant) -> str: """Get a client ID (based on the HASS unique ID) for the SimpliSafe API. Note that SimpliSafe requires full, "dashed" versions of UUIDs. @@ -118,7 +129,9 @@ async def async_get_client_id(hass): return str(UUID(hass_id)) -async def async_register_base_station(hass, system, config_entry_id): +async def async_register_base_station( + hass: HomeAssistant, system: SystemV2 | SystemV3, config_entry_id: str +) -> None: """Register a new bridge.""" device_registry = await dr.async_get_registry(hass) device_registry.async_get_or_create( @@ -130,11 +143,11 @@ async def async_register_base_station(hass, system, config_entry_id): ) -async def async_setup_entry(hass, config_entry): # noqa: C901 - """Set up SimpliSafe as config entry.""" - hass.data.setdefault(DOMAIN, {DATA_CLIENT: {}}) - hass.data[DOMAIN][DATA_CLIENT][config_entry.entry_id] = [] - +@callback +def _async_standardize_config_entry( + hass: HomeAssistant, config_entry: ConfigEntry +) -> None: + """Bring a config entry up to current standards.""" if CONF_PASSWORD not in config_entry.data: raise ConfigEntryAuthFailed("Config schema change requires re-authentication") @@ -154,6 +167,14 @@ async def async_setup_entry(hass, config_entry): # noqa: C901 if entry_updates: hass.config_entries.async_update_entry(config_entry, **entry_updates) + +async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool: + """Set up SimpliSafe as config entry.""" + hass.data.setdefault(DOMAIN, {DATA_CLIENT: {}}) + hass.data[DOMAIN][DATA_CLIENT][config_entry.entry_id] = [] + + _async_standardize_config_entry(hass, config_entry) + _verify_domain_control = verify_domain_control(hass, DOMAIN) client_id = await async_get_client_id(hass) @@ -183,10 +204,12 @@ async def async_setup_entry(hass, config_entry): # noqa: C901 hass.config_entries.async_setup_platforms(config_entry, PLATFORMS) @callback - def verify_system_exists(coro): + def verify_system_exists( + coro: Callable[..., Awaitable] + ) -> Callable[..., Awaitable]: """Log an error if a service call uses an invalid system ID.""" - async def decorator(call): + async def decorator(call: ServiceCall) -> None: """Decorate.""" system_id = int(call.data[ATTR_SYSTEM_ID]) if system_id not in simplisafe.systems: @@ -197,10 +220,10 @@ async def async_setup_entry(hass, config_entry): # noqa: C901 return decorator @callback - def v3_only(coro): + def v3_only(coro: Callable[..., Awaitable]) -> Callable[..., Awaitable]: """Log an error if the decorated coroutine is called with a v2 system.""" - async def decorator(call): + async def decorator(call: ServiceCall) -> None: """Decorate.""" system = simplisafe.systems[int(call.data[ATTR_SYSTEM_ID])] if system.version != 3: @@ -212,43 +235,40 @@ async def async_setup_entry(hass, config_entry): # noqa: C901 @verify_system_exists @_verify_domain_control - async def clear_notifications(call): + async def clear_notifications(call: ServiceCall) -> None: """Clear all active notifications.""" system = simplisafe.systems[call.data[ATTR_SYSTEM_ID]] try: await system.clear_notifications() except SimplipyError as err: LOGGER.error("Error during service call: %s", err) - return @verify_system_exists @_verify_domain_control - async def remove_pin(call): + async def remove_pin(call: ServiceCall) -> None: """Remove a PIN.""" system = simplisafe.systems[call.data[ATTR_SYSTEM_ID]] try: await system.remove_pin(call.data[ATTR_PIN_LABEL_OR_VALUE]) except SimplipyError as err: LOGGER.error("Error during service call: %s", err) - return @verify_system_exists @_verify_domain_control - async def set_pin(call): + async def set_pin(call: ServiceCall) -> None: """Set a PIN.""" system = simplisafe.systems[call.data[ATTR_SYSTEM_ID]] try: await system.set_pin(call.data[ATTR_PIN_LABEL], call.data[ATTR_PIN_VALUE]) except SimplipyError as err: LOGGER.error("Error during service call: %s", err) - return @verify_system_exists @v3_only @_verify_domain_control - async def set_system_properties(call): + async def set_system_properties(call: ServiceCall) -> None: """Set one or more system parameters.""" - system = simplisafe.systems[call.data[ATTR_SYSTEM_ID]] + system = cast(SystemV3, simplisafe.systems[call.data[ATTR_SYSTEM_ID]]) try: await system.set_properties( { @@ -259,7 +279,6 @@ async def async_setup_entry(hass, config_entry): # noqa: C901 ) except SimplipyError as err: LOGGER.error("Error during service call: %s", err) - return for service, method, schema in ( ("clear_notifications", clear_notifications, None), @@ -278,7 +297,7 @@ async def async_setup_entry(hass, config_entry): # noqa: C901 return True -async def async_unload_entry(hass, entry): +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Unload a SimpliSafe config entry.""" unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) if unload_ok: @@ -287,7 +306,7 @@ async def async_unload_entry(hass, entry): return unload_ok -async def async_reload_entry(hass, config_entry): +async def async_reload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> None: """Handle an options update.""" await hass.config_entries.async_reload(config_entry.entry_id) @@ -295,17 +314,19 @@ async def async_reload_entry(hass, config_entry): class SimpliSafe: """Define a SimpliSafe data object.""" - def __init__(self, hass, config_entry, api): + def __init__( + self, hass: HomeAssistant, config_entry: ConfigEntry, api: API + ) -> None: """Initialize.""" self._api = api self._hass = hass - self._system_notifications = {} + self._system_notifications: dict[int, set[SystemNotification]] = {} self.config_entry = config_entry - self.coordinator = None - self.systems = {} + self.coordinator: DataUpdateCoordinator | None = None + self.systems: dict[int, SystemV2 | SystemV3] = {} @callback - def _async_process_new_notifications(self, system): + def _async_process_new_notifications(self, system: SystemV2 | SystemV3) -> None: """Act on any new system notifications.""" if self._hass.state != CoreState.running: # If HASS isn't fully running yet, it may cause the SIMPLISAFE_NOTIFICATION @@ -324,8 +345,6 @@ class SimpliSafe: LOGGER.debug("New system notifications: %s", to_add) - self._system_notifications[system.system_id].update(to_add) - for notification in to_add: text = notification.text if notification.link: @@ -341,7 +360,9 @@ class SimpliSafe: }, ) - async def async_init(self): + self._system_notifications[system.system_id] = latest_notifications + + async def async_init(self) -> None: """Initialize the data class.""" self.systems = await self._api.get_systems() for system in self.systems.values(): @@ -361,10 +382,10 @@ class SimpliSafe: update_method=self.async_update, ) - async def async_update(self): + async def async_update(self) -> None: """Get updated data from SimpliSafe.""" - async def async_update_system(system): + async def async_update_system(system: SystemV2 | SystemV3) -> None: """Update a system.""" await system.update(cached=system.version != 3) self._async_process_new_notifications(system) @@ -389,8 +410,16 @@ class SimpliSafe: class SimpliSafeEntity(CoordinatorEntity): """Define a base SimpliSafe entity.""" - def __init__(self, simplisafe, system, name, *, serial=None): + def __init__( + self, + simplisafe: SimpliSafe, + system: SystemV2 | SystemV3, + name: str, + *, + serial: str | None = None, + ) -> None: """Initialize.""" + assert simplisafe.coordinator super().__init__(simplisafe.coordinator) if serial: @@ -413,32 +442,33 @@ class SimpliSafeEntity(CoordinatorEntity): self._system = system @property - def available(self): + def available(self) -> bool: """Return whether the entity is available.""" # We can easily detect if the V3 system is offline, but no simple check exists # for the V2 system. Therefore, assuming the coordinator hasn't failed, we mark # the entity as available if: # 1. We can verify that the system is online (assuming True if we can't) # 2. We can verify that the entity is online - return ( - super().available - and self._online - and not (self._system.version == 3 and self._system.offline) - ) + if isinstance(self._system, SystemV3): + system_offline = self._system.offline + else: + system_offline = False + + return super().available and self._online and not system_offline @callback - def _handle_coordinator_update(self): + def _handle_coordinator_update(self) -> None: """Update the entity with new REST API data.""" self.async_update_from_rest_api() self.async_write_ha_state() - async def async_added_to_hass(self): + async def async_added_to_hass(self) -> None: """Register callbacks.""" await super().async_added_to_hass() self.async_update_from_rest_api() @callback - def async_update_from_rest_api(self): + def async_update_from_rest_api(self) -> None: """Update the entity with the provided REST API data.""" raise NotImplementedError() @@ -446,13 +476,22 @@ class SimpliSafeEntity(CoordinatorEntity): class SimpliSafeBaseSensor(SimpliSafeEntity): """Define a SimpliSafe base (binary) sensor.""" - def __init__(self, simplisafe, system, sensor): + def __init__( + self, + simplisafe: SimpliSafe, + system: SystemV2 | SystemV3, + sensor: SensorV2 | SensorV3, + ) -> None: """Initialize.""" super().__init__(simplisafe, system, sensor.name, serial=sensor.serial) - self._attr_device_info["identifiers"] = {(DOMAIN, sensor.serial)} - self._attr_device_info["model"] = sensor.type.name - self._attr_device_info["name"] = sensor.name + self._attr_device_info = { + "identifiers": {(DOMAIN, sensor.serial)}, + "manufacturer": "SimpliSafe", + "model": sensor.type.name, + "name": sensor.name, + "via_device": (DOMAIN, system.serial), + } human_friendly_name = " ".join([w.title() for w in sensor.type.name.split("_")]) self._attr_name = f"{super().name} {human_friendly_name}" diff --git a/homeassistant/components/simplisafe/alarm_control_panel.py b/homeassistant/components/simplisafe/alarm_control_panel.py index 28013b69d55..5c50d6a343e 100644 --- a/homeassistant/components/simplisafe/alarm_control_panel.py +++ b/homeassistant/components/simplisafe/alarm_control_panel.py @@ -1,8 +1,12 @@ """Support for SimpliSafe alarm control panels.""" +from __future__ import annotations + import re from simplipy.errors import SimplipyError from simplipy.system import SystemStates +from simplipy.system.v2 import SystemV2 +from simplipy.system.v3 import SystemV3 from homeassistant.components.alarm_control_panel import ( FORMAT_NUMBER, @@ -13,6 +17,7 @@ from homeassistant.components.alarm_control_panel.const import ( SUPPORT_ALARM_ARM_AWAY, SUPPORT_ALARM_ARM_HOME, ) +from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( CONF_CODE, STATE_ALARM_ARMED_AWAY, @@ -21,9 +26,10 @@ from homeassistant.const import ( STATE_ALARM_DISARMED, STATE_ALARM_TRIGGERED, ) -from homeassistant.core import callback +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.entity_platform import AddEntitiesCallback -from . import SimpliSafeEntity +from . import SimpliSafe, SimpliSafeEntity from .const import ( ATTR_ALARM_DURATION, ATTR_ALARM_VOLUME, @@ -48,7 +54,9 @@ ATTR_WALL_POWER_LEVEL = "wall_power_level" ATTR_WIFI_STRENGTH = "wifi_strength" -async def async_setup_entry(hass, entry, async_add_entities): +async def async_setup_entry( + hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback +) -> None: """Set up a SimpliSafe alarm control panel based on a config entry.""" simplisafe = hass.data[DOMAIN][DATA_CLIENT][entry.entry_id] async_add_entities( @@ -60,7 +68,7 @@ async def async_setup_entry(hass, entry, async_add_entities): class SimpliSafeAlarm(SimpliSafeEntity, AlarmControlPanelEntity): """Representation of a SimpliSafe alarm.""" - def __init__(self, simplisafe, system): + def __init__(self, simplisafe: SimpliSafe, system: SystemV2 | SystemV3) -> None: """Initialize the SimpliSafe alarm.""" super().__init__(simplisafe, system, "Alarm Control Panel") @@ -91,7 +99,7 @@ class SimpliSafeAlarm(SimpliSafeEntity, AlarmControlPanelEntity): self._attr_state = None @callback - def _is_code_valid(self, code, state): + def _is_code_valid(self, code: str | None, state: str) -> bool: """Validate that a code matches the required one.""" if not self._simplisafe.config_entry.options.get(CONF_CODE): return True @@ -104,7 +112,7 @@ class SimpliSafeAlarm(SimpliSafeEntity, AlarmControlPanelEntity): return True - async def async_alarm_disarm(self, code=None): + async def async_alarm_disarm(self, code: str | None = None) -> None: """Send disarm command.""" if not self._is_code_valid(code, STATE_ALARM_DISARMED): return @@ -118,7 +126,7 @@ class SimpliSafeAlarm(SimpliSafeEntity, AlarmControlPanelEntity): self._attr_state = STATE_ALARM_DISARMED self.async_write_ha_state() - async def async_alarm_arm_home(self, code=None): + async def async_alarm_arm_home(self, code: str | None = None) -> None: """Send arm home command.""" if not self._is_code_valid(code, STATE_ALARM_ARMED_HOME): return @@ -134,7 +142,7 @@ class SimpliSafeAlarm(SimpliSafeEntity, AlarmControlPanelEntity): self._attr_state = STATE_ALARM_ARMED_HOME self.async_write_ha_state() - async def async_alarm_arm_away(self, code=None): + async def async_alarm_arm_away(self, code: str | None = None) -> None: """Send arm away command.""" if not self._is_code_valid(code, STATE_ALARM_ARMED_AWAY): return @@ -151,9 +159,9 @@ class SimpliSafeAlarm(SimpliSafeEntity, AlarmControlPanelEntity): self.async_write_ha_state() @callback - def async_update_from_rest_api(self): + def async_update_from_rest_api(self) -> None: """Update the entity with the provided REST API data.""" - if self._system.version == 3: + if isinstance(self._system, SystemV3): self._attr_extra_state_attributes.update( { ATTR_ALARM_DURATION: self._system.alarm_duration, @@ -175,9 +183,6 @@ class SimpliSafeAlarm(SimpliSafeEntity, AlarmControlPanelEntity): } ) - # Although system state updates are designed the come via the websocket, the - # SimpliSafe cloud can sporadically fail to send those updates as expected; so, - # just in case, we synchronize the state via the REST API, too: if self._system.state == SystemStates.alarm: self._attr_state = STATE_ALARM_TRIGGERED elif self._system.state == SystemStates.away: diff --git a/homeassistant/components/simplisafe/binary_sensor.py b/homeassistant/components/simplisafe/binary_sensor.py index 70fa0fb0e51..1c471d10ce8 100644 --- a/homeassistant/components/simplisafe/binary_sensor.py +++ b/homeassistant/components/simplisafe/binary_sensor.py @@ -1,5 +1,9 @@ """Support for SimpliSafe binary sensors.""" -from simplipy.entity import EntityTypes +from __future__ import annotations + +from simplipy.entity import Entity as SimplipyEntity, EntityTypes +from simplipy.system.v2 import SystemV2 +from simplipy.system.v3 import SystemV3 from homeassistant.components.binary_sensor import ( DEVICE_CLASS_BATTERY, @@ -11,9 +15,11 @@ from homeassistant.components.binary_sensor import ( DEVICE_CLASS_SMOKE, BinarySensorEntity, ) -from homeassistant.core import callback +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.entity_platform import AddEntitiesCallback -from . import SimpliSafeBaseSensor +from . import SimpliSafe, SimpliSafeBaseSensor from .const import DATA_CLIENT, DOMAIN, LOGGER SUPPORTED_BATTERY_SENSOR_TYPES = [ @@ -39,10 +45,13 @@ TRIGGERED_SENSOR_TYPES = { } -async def async_setup_entry(hass, entry, async_add_entities): +async def async_setup_entry( + hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback +) -> None: """Set up SimpliSafe binary sensors based on a config entry.""" simplisafe = hass.data[DOMAIN][DATA_CLIENT][entry.entry_id] - sensors = [] + + sensors: list[BatteryBinarySensor | TriggeredBinarySensor] = [] for system in simplisafe.systems.values(): if system.version == 2: @@ -68,14 +77,20 @@ async def async_setup_entry(hass, entry, async_add_entities): class TriggeredBinarySensor(SimpliSafeBaseSensor, BinarySensorEntity): """Define a binary sensor related to whether an entity has been triggered.""" - def __init__(self, simplisafe, system, sensor, device_class): + def __init__( + self, + simplisafe: SimpliSafe, + system: SystemV2 | SystemV3, + sensor: SimplipyEntity, + device_class: str, + ) -> None: """Initialize.""" super().__init__(simplisafe, system, sensor) self._attr_device_class = device_class @callback - def async_update_from_rest_api(self): + def async_update_from_rest_api(self) -> None: """Update the entity with the provided REST API data.""" self._attr_is_on = self._sensor.triggered @@ -85,13 +100,18 @@ class BatteryBinarySensor(SimpliSafeBaseSensor, BinarySensorEntity): _attr_device_class = DEVICE_CLASS_BATTERY - def __init__(self, simplisafe, system, sensor): + def __init__( + self, + simplisafe: SimpliSafe, + system: SystemV2 | SystemV3, + sensor: SimplipyEntity, + ) -> None: """Initialize.""" super().__init__(simplisafe, system, sensor) self._attr_unique_id = f"{super().unique_id}-battery" @callback - def async_update_from_rest_api(self): + def async_update_from_rest_api(self) -> None: """Update the entity with the provided REST API data.""" self._attr_is_on = self._sensor.low_battery diff --git a/homeassistant/components/simplisafe/config_flow.py b/homeassistant/components/simplisafe/config_flow.py index ac31779175f..31ae125046c 100644 --- a/homeassistant/components/simplisafe/config_flow.py +++ b/homeassistant/components/simplisafe/config_flow.py @@ -1,5 +1,10 @@ """Config flow to configure the SimpliSafe component.""" +from __future__ import annotations + +from typing import Any + from simplipy import get_api +from simplipy.api import API from simplipy.errors import ( InvalidCredentialsError, PendingAuthorizationError, @@ -8,9 +13,12 @@ from simplipy.errors import ( import voluptuous as vol from homeassistant import config_entries +from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_CODE, CONF_PASSWORD, CONF_USERNAME from homeassistant.core import callback +from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import aiohttp_client +from homeassistant.helpers.typing import ConfigType from . import async_get_client_id from .const import DOMAIN, LOGGER @@ -30,20 +38,25 @@ class SimpliSafeFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): VERSION = 1 - def __init__(self): + def __init__(self) -> None: """Initialize the config flow.""" - self._code = None - self._password = None - self._username = None + self._code: str | None = None + self._password: str | None = None + self._username: str | None = None @staticmethod @callback - def async_get_options_flow(config_entry): + def async_get_options_flow( + config_entry: ConfigEntry, + ) -> SimpliSafeOptionsFlowHandler: """Define the config flow to handle options.""" return SimpliSafeOptionsFlowHandler(config_entry) - async def _async_get_simplisafe_api(self): + async def _async_get_simplisafe_api(self) -> API: """Get an authenticated SimpliSafe API client.""" + assert self._username + assert self._password + client_id = await async_get_client_id(self.hass) websession = aiohttp_client.async_get_clientsession(self.hass) @@ -54,7 +67,9 @@ class SimpliSafeFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): session=websession, ) - async def _async_login_during_step(self, *, step_id, form_schema): + async def _async_login_during_step( + self, *, step_id: str, form_schema: vol.Schema + ) -> FlowResult: """Attempt to log into the API from within a config flow step.""" errors = {} @@ -84,8 +99,10 @@ class SimpliSafeFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): } ) - async def async_step_finish(self, user_input=None): + async def async_step_finish(self, user_input: dict[str, Any]) -> FlowResult: """Handle finish config entry setup.""" + assert self._username + existing_entry = await self.async_set_unique_id(self._username) if existing_entry: self.hass.config_entries.async_update_entry(existing_entry, data=user_input) @@ -95,7 +112,9 @@ class SimpliSafeFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): return self.async_abort(reason="reauth_successful") return self.async_create_entry(title=self._username, data=user_input) - async def async_step_mfa(self, user_input=None): + async def async_step_mfa( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: """Handle multi-factor auth confirmation.""" if user_input is None: return self.async_show_form(step_id="mfa") @@ -116,14 +135,16 @@ class SimpliSafeFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): } ) - async def async_step_reauth(self, config): + async def async_step_reauth(self, config: ConfigType) -> FlowResult: """Handle configuration by re-auth.""" self._code = config.get(CONF_CODE) self._username = config[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 re-auth completion.""" if not user_input: return self.async_show_form( @@ -136,7 +157,9 @@ class SimpliSafeFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): step_id="reauth_confirm", form_schema=PASSWORD_DATA_SCHEMA ) - async def async_step_user(self, user_input=None): + async def async_step_user( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: """Handle the start of the config flow.""" if not user_input: return self.async_show_form(step_id="user", data_schema=FULL_DATA_SCHEMA) @@ -156,11 +179,13 @@ class SimpliSafeFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): class SimpliSafeOptionsFlowHandler(config_entries.OptionsFlow): """Handle a SimpliSafe options flow.""" - def __init__(self, config_entry): + def __init__(self, config_entry: ConfigEntry) -> None: """Initialize.""" self.config_entry = config_entry - async def async_step_init(self, user_input=None): + async def async_step_init( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: """Manage the options.""" if user_input is not None: return self.async_create_entry(title="", data=user_input) diff --git a/homeassistant/components/simplisafe/lock.py b/homeassistant/components/simplisafe/lock.py index 982494d9b1c..e912eedb955 100644 --- a/homeassistant/components/simplisafe/lock.py +++ b/homeassistant/components/simplisafe/lock.py @@ -1,11 +1,18 @@ """Support for SimpliSafe locks.""" +from __future__ import annotations + +from typing import Any + from simplipy.errors import SimplipyError -from simplipy.lock import LockStates +from simplipy.lock import Lock, LockStates +from simplipy.system.v3 import SystemV3 from homeassistant.components.lock import LockEntity -from homeassistant.core import callback +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.entity_platform import AddEntitiesCallback -from . import SimpliSafeEntity +from . import SimpliSafe, SimpliSafeEntity from .const import DATA_CLIENT, DOMAIN, LOGGER ATTR_LOCK_LOW_BATTERY = "lock_low_battery" @@ -13,7 +20,9 @@ ATTR_JAMMED = "jammed" ATTR_PIN_PAD_LOW_BATTERY = "pin_pad_low_battery" -async def async_setup_entry(hass, entry, async_add_entities): +async def async_setup_entry( + hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback +) -> None: """Set up SimpliSafe locks based on a config entry.""" simplisafe = hass.data[DOMAIN][DATA_CLIENT][entry.entry_id] locks = [] @@ -32,13 +41,13 @@ async def async_setup_entry(hass, entry, async_add_entities): class SimpliSafeLock(SimpliSafeEntity, LockEntity): """Define a SimpliSafe lock.""" - def __init__(self, simplisafe, system, lock): + def __init__(self, simplisafe: SimpliSafe, system: SystemV3, lock: Lock) -> None: """Initialize.""" super().__init__(simplisafe, system, lock.name, serial=lock.serial) self._lock = lock - async def async_lock(self, **kwargs): + async def async_lock(self, **kwargs: dict[str, Any]) -> None: """Lock the lock.""" try: await self._lock.lock() @@ -49,7 +58,7 @@ class SimpliSafeLock(SimpliSafeEntity, LockEntity): self._attr_is_locked = True self.async_write_ha_state() - async def async_unlock(self, **kwargs): + async def async_unlock(self, **kwargs: dict[str, Any]) -> None: """Unlock the lock.""" try: await self._lock.unlock() @@ -61,7 +70,7 @@ class SimpliSafeLock(SimpliSafeEntity, LockEntity): self.async_write_ha_state() @callback - def async_update_from_rest_api(self): + def async_update_from_rest_api(self) -> None: """Update the entity with the provided REST API data.""" self._attr_extra_state_attributes.update( { diff --git a/homeassistant/components/simplisafe/manifest.json b/homeassistant/components/simplisafe/manifest.json index 3ec1e38ad4d..8c23e575cc3 100644 --- a/homeassistant/components/simplisafe/manifest.json +++ b/homeassistant/components/simplisafe/manifest.json @@ -3,7 +3,7 @@ "name": "SimpliSafe", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/simplisafe", - "requirements": ["simplisafe-python==11.0.2"], + "requirements": ["simplisafe-python==11.0.3"], "codeowners": ["@bachya"], "iot_class": "cloud_polling" } diff --git a/homeassistant/components/simplisafe/sensor.py b/homeassistant/components/simplisafe/sensor.py index 76be7b6e4f0..149319cd5bd 100644 --- a/homeassistant/components/simplisafe/sensor.py +++ b/homeassistant/components/simplisafe/sensor.py @@ -2,14 +2,18 @@ from simplipy.entity import EntityTypes from homeassistant.components.sensor import SensorEntity +from homeassistant.config_entries import ConfigEntry from homeassistant.const import DEVICE_CLASS_TEMPERATURE, TEMP_FAHRENHEIT -from homeassistant.core import callback +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.entity_platform import AddEntitiesCallback from . import SimpliSafeBaseSensor from .const import DATA_CLIENT, DOMAIN, LOGGER -async def async_setup_entry(hass, entry, async_add_entities): +async def async_setup_entry( + hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback +) -> None: """Set up SimpliSafe freeze sensors based on a config entry.""" simplisafe = hass.data[DOMAIN][DATA_CLIENT][entry.entry_id] sensors = [] @@ -33,6 +37,6 @@ class SimplisafeFreezeSensor(SimpliSafeBaseSensor, SensorEntity): _attr_unit_of_measurement = TEMP_FAHRENHEIT @callback - def async_update_from_rest_api(self): + def async_update_from_rest_api(self) -> None: """Update the entity with the provided REST API data.""" self._attr_state = self._sensor.temperature diff --git a/mypy.ini b/mypy.ini index 409dbbdab76..613a4e3335d 100644 --- a/mypy.ini +++ b/mypy.ini @@ -935,6 +935,17 @@ no_implicit_optional = true warn_return_any = true warn_unreachable = true +[mypy-homeassistant.components.simplisafe.*] +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.slack.*] check_untyped_defs = true disallow_incomplete_defs = true diff --git a/requirements_all.txt b/requirements_all.txt index b23f28caafd..7cf7390d0ca 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2107,7 +2107,7 @@ simplehound==0.3 simplepush==1.1.4 # homeassistant.components.simplisafe -simplisafe-python==11.0.2 +simplisafe-python==11.0.3 # homeassistant.components.sisyphus sisyphus-control==3.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index fd290d3bfe8..14bf97ad2da 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1156,7 +1156,7 @@ sharkiqpy==0.1.8 simplehound==0.3 # homeassistant.components.simplisafe -simplisafe-python==11.0.2 +simplisafe-python==11.0.3 # homeassistant.components.slack slackclient==2.5.0