From f4a7292f08c89c41b4f6c335dbc909bb9fe13499 Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Tue, 27 Jul 2021 03:51:57 -0600 Subject: [PATCH] Enforce strict typing for Tile (#53410) --- .strict-typing | 1 + homeassistant/components/tile/__init__.py | 20 ++++--- homeassistant/components/tile/config_flow.py | 15 +++-- .../components/tile/device_tracker.py | 56 ++++++++++++++----- homeassistant/components/tile/manifest.json | 2 +- mypy.ini | 11 ++++ requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 8 files changed, 79 insertions(+), 30 deletions(-) diff --git a/.strict-typing b/.strict-typing index 2a1f3bc8cc7..24d35370ca5 100644 --- a/.strict-typing +++ b/.strict-typing @@ -95,6 +95,7 @@ homeassistant.components.synology_dsm.* homeassistant.components.systemmonitor.* homeassistant.components.tag.* homeassistant.components.tcp.* +homeassistant.components.tile.* homeassistant.components.tts.* homeassistant.components.upcloud.* homeassistant.components.uptime.* diff --git a/homeassistant/components/tile/__init__.py b/homeassistant/components/tile/__init__.py index d8a592a2f09..5b52e637c64 100644 --- a/homeassistant/components/tile/__init__.py +++ b/homeassistant/components/tile/__init__.py @@ -1,15 +1,19 @@ """The Tile component.""" +from __future__ import annotations + from datetime import timedelta from functools import partial from pytile import async_login from pytile.errors import InvalidAuthError, SessionExpiredError, TileError +from pytile.tile import Tile +from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_PASSWORD, CONF_USERNAME -from homeassistant.core import callback +from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers import aiohttp_client -from homeassistant.helpers.entity_registry import async_migrate_entries +from homeassistant.helpers.entity_registry import RegistryEntry, async_migrate_entries from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from homeassistant.util.async_ import gather_with_concurrency @@ -24,14 +28,14 @@ DEFAULT_UPDATE_INTERVAL = timedelta(minutes=2) CONF_SHOW_INACTIVE = "show_inactive" -async def async_setup_entry(hass, entry): +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up Tile as config entry.""" hass.data.setdefault(DOMAIN, {DATA_COORDINATOR: {}, DATA_TILE: {}}) hass.data[DOMAIN][DATA_COORDINATOR][entry.entry_id] = {} hass.data[DOMAIN][DATA_TILE][entry.entry_id] = {} @callback - def async_migrate_callback(entity_entry): + def async_migrate_callback(entity_entry: RegistryEntry) -> dict | None: """ Define a callback to migrate appropriate Tile entities to new unique IDs. @@ -39,7 +43,7 @@ async def async_setup_entry(hass, entry): New: {username}_{uuid} """ if entity_entry.unique_id.startswith(entry.data[CONF_USERNAME]): - return + return None new_unique_id = f"{entry.data[CONF_USERNAME]}_".join( entity_entry.unique_id.split(f"{DOMAIN}_") @@ -71,10 +75,10 @@ async def async_setup_entry(hass, entry): except TileError as err: raise ConfigEntryNotReady("Error during integration setup") from err - async def async_update_tile(tile): + async def async_update_tile(tile: Tile) -> None: """Update the Tile.""" try: - return await tile.async_update() + await tile.async_update() except SessionExpiredError: LOGGER.info("Tile session expired; creating a new one") await client.async_init() @@ -101,7 +105,7 @@ async def async_setup_entry(hass, entry): return True -async def async_unload_entry(hass, entry): +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Unload a Tile config entry.""" unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) if unload_ok: diff --git a/homeassistant/components/tile/config_flow.py b/homeassistant/components/tile/config_flow.py index 23bf4ffa79b..3c78e5d2bca 100644 --- a/homeassistant/components/tile/config_flow.py +++ b/homeassistant/components/tile/config_flow.py @@ -1,10 +1,15 @@ """Config flow to configure the Tile integration.""" +from __future__ import annotations + +from typing import Any + from pytile import async_login from pytile.errors import TileError import voluptuous as vol from homeassistant import config_entries from homeassistant.const import CONF_PASSWORD, CONF_USERNAME +from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import aiohttp_client from .const import DOMAIN @@ -15,23 +20,25 @@ class TileFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): VERSION = 1 - def __init__(self): + def __init__(self) -> None: """Initialize the config flow.""" self.data_schema = vol.Schema( {vol.Required(CONF_USERNAME): str, vol.Required(CONF_PASSWORD): str} ) - async def _show_form(self, errors=None): + async def _show_form(self, errors: dict[str, Any] | None = None) -> FlowResult: """Show the form to the user.""" return self.async_show_form( step_id="user", data_schema=self.data_schema, errors=errors or {} ) - async def async_step_import(self, import_config): + async def async_step_import(self, import_config: dict[str, Any]) -> FlowResult: """Import a config entry from configuration.yaml.""" return await self.async_step_user(import_config) - 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 await self._show_form() diff --git a/homeassistant/components/tile/device_tracker.py b/homeassistant/components/tile/device_tracker.py index ea1b853d9ae..27446389f50 100644 --- a/homeassistant/components/tile/device_tracker.py +++ b/homeassistant/components/tile/device_tracker.py @@ -1,12 +1,23 @@ """Support for Tile device trackers.""" +from __future__ import annotations + +from collections.abc import Awaitable import logging +from typing import Any, Callable + +from pytile.tile import Tile from homeassistant.components.device_tracker.config_entry import TrackerEntity from homeassistant.components.device_tracker.const import SOURCE_TYPE_GPS -from homeassistant.config_entries import SOURCE_IMPORT +from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry from homeassistant.const import ATTR_ATTRIBUTION, CONF_PASSWORD, CONF_USERNAME -from homeassistant.core import callback -from homeassistant.helpers.update_coordinator import CoordinatorEntity +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.typing import ConfigType +from homeassistant.helpers.update_coordinator import ( + CoordinatorEntity, + DataUpdateCoordinator, +) from . import DATA_COORDINATOR, DATA_TILE, DOMAIN @@ -25,7 +36,9 @@ DEFAULT_ATTRIBUTION = "Data provided by Tile" DEFAULT_ICON = "mdi:view-grid" -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 Tile device trackers.""" async_add_entities( [ @@ -39,7 +52,12 @@ async def async_setup_entry(hass, entry, async_add_entities): ) -async def async_setup_scanner(hass, config, async_see, discovery_info=None): +async def async_setup_scanner( + hass: HomeAssistant, + config: ConfigType, + async_see: Callable[..., Awaitable[None]], + discovery_info: dict[str, Any] | None = None, +) -> bool: """Detect a legacy configuration and import it.""" hass.async_create_task( hass.config_entries.flow.async_init( @@ -65,7 +83,9 @@ class TileDeviceTracker(CoordinatorEntity, TrackerEntity): _attr_icon = DEFAULT_ICON - def __init__(self, entry, coordinator, tile): + def __init__( + self, entry: ConfigEntry, coordinator: DataUpdateCoordinator, tile: Tile + ) -> None: """Initialize.""" super().__init__(coordinator) @@ -76,41 +96,47 @@ class TileDeviceTracker(CoordinatorEntity, TrackerEntity): self._tile = tile @property - def available(self): + def available(self) -> bool: """Return if entity is available.""" return super().available and not self._tile.dead @property - def location_accuracy(self): + def location_accuracy(self) -> int: """Return the location accuracy of the device. Value in meters. """ - return self._tile.accuracy + if not self._tile.accuracy: + return super().location_accuracy + return int(self._tile.accuracy) @property - def latitude(self) -> float: + def latitude(self) -> float | None: """Return latitude value of the device.""" + if not self._tile.latitude: + return None return self._tile.latitude @property - def longitude(self) -> float: + def longitude(self) -> float | None: """Return longitude value of the device.""" + if not self._tile.longitude: + return None return self._tile.longitude @property - def source_type(self): + def source_type(self) -> str: """Return the source type, eg gps or router, of the device.""" return SOURCE_TYPE_GPS @callback - def _handle_coordinator_update(self): + def _handle_coordinator_update(self) -> None: """Respond to a DataUpdateCoordinator update.""" self._update_from_latest_data() self.async_write_ha_state() @callback - def _update_from_latest_data(self): + def _update_from_latest_data(self) -> None: """Update the entity from the latest data.""" self._attr_extra_state_attributes.update( { @@ -122,7 +148,7 @@ class TileDeviceTracker(CoordinatorEntity, TrackerEntity): } ) - async def async_added_to_hass(self): + async def async_added_to_hass(self) -> None: """Handle entity which will be added.""" await super().async_added_to_hass() self._update_from_latest_data() diff --git a/homeassistant/components/tile/manifest.json b/homeassistant/components/tile/manifest.json index e8d386f4a88..39295eed646 100644 --- a/homeassistant/components/tile/manifest.json +++ b/homeassistant/components/tile/manifest.json @@ -3,7 +3,7 @@ "name": "Tile", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/tile", - "requirements": ["pytile==5.2.2"], + "requirements": ["pytile==5.2.3"], "codeowners": ["@bachya"], "iot_class": "cloud_polling" } diff --git a/mypy.ini b/mypy.ini index 644a8dc22e6..2342bf3d966 100644 --- a/mypy.ini +++ b/mypy.ini @@ -1056,6 +1056,17 @@ no_implicit_optional = true warn_return_any = true warn_unreachable = true +[mypy-homeassistant.components.tile.*] +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.tts.*] check_untyped_defs = true disallow_incomplete_defs = true diff --git a/requirements_all.txt b/requirements_all.txt index 7ee67487da6..dfbd6090ffb 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1928,7 +1928,7 @@ python_opendata_transport==0.2.1 pythonegardia==1.0.40 # homeassistant.components.tile -pytile==5.2.2 +pytile==5.2.3 # homeassistant.components.touchline pytouchline==0.7 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 9569a63d0c3..63caf5e42d3 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1071,7 +1071,7 @@ python-velbus==2.1.2 python_awair==0.2.1 # homeassistant.components.tile -pytile==5.2.2 +pytile==5.2.3 # homeassistant.components.traccar pytraccar==0.9.0