Enforce strict typing for Tile (#53410)

This commit is contained in:
Aaron Bach 2021-07-27 03:51:57 -06:00 committed by GitHub
parent 7103835d15
commit f4a7292f08
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 79 additions and 30 deletions

View File

@ -95,6 +95,7 @@ homeassistant.components.synology_dsm.*
homeassistant.components.systemmonitor.* homeassistant.components.systemmonitor.*
homeassistant.components.tag.* homeassistant.components.tag.*
homeassistant.components.tcp.* homeassistant.components.tcp.*
homeassistant.components.tile.*
homeassistant.components.tts.* homeassistant.components.tts.*
homeassistant.components.upcloud.* homeassistant.components.upcloud.*
homeassistant.components.uptime.* homeassistant.components.uptime.*

View File

@ -1,15 +1,19 @@
"""The Tile component.""" """The Tile component."""
from __future__ import annotations
from datetime import timedelta from datetime import timedelta
from functools import partial from functools import partial
from pytile import async_login from pytile import async_login
from pytile.errors import InvalidAuthError, SessionExpiredError, TileError 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.const import CONF_PASSWORD, CONF_USERNAME
from homeassistant.core import callback from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers import aiohttp_client 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.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from homeassistant.util.async_ import gather_with_concurrency from homeassistant.util.async_ import gather_with_concurrency
@ -24,14 +28,14 @@ DEFAULT_UPDATE_INTERVAL = timedelta(minutes=2)
CONF_SHOW_INACTIVE = "show_inactive" 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.""" """Set up Tile as config entry."""
hass.data.setdefault(DOMAIN, {DATA_COORDINATOR: {}, DATA_TILE: {}}) hass.data.setdefault(DOMAIN, {DATA_COORDINATOR: {}, DATA_TILE: {}})
hass.data[DOMAIN][DATA_COORDINATOR][entry.entry_id] = {} hass.data[DOMAIN][DATA_COORDINATOR][entry.entry_id] = {}
hass.data[DOMAIN][DATA_TILE][entry.entry_id] = {} hass.data[DOMAIN][DATA_TILE][entry.entry_id] = {}
@callback @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. 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} New: {username}_{uuid}
""" """
if entity_entry.unique_id.startswith(entry.data[CONF_USERNAME]): if entity_entry.unique_id.startswith(entry.data[CONF_USERNAME]):
return return None
new_unique_id = f"{entry.data[CONF_USERNAME]}_".join( new_unique_id = f"{entry.data[CONF_USERNAME]}_".join(
entity_entry.unique_id.split(f"{DOMAIN}_") entity_entry.unique_id.split(f"{DOMAIN}_")
@ -71,10 +75,10 @@ async def async_setup_entry(hass, entry):
except TileError as err: except TileError as err:
raise ConfigEntryNotReady("Error during integration setup") from 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.""" """Update the Tile."""
try: try:
return await tile.async_update() await tile.async_update()
except SessionExpiredError: except SessionExpiredError:
LOGGER.info("Tile session expired; creating a new one") LOGGER.info("Tile session expired; creating a new one")
await client.async_init() await client.async_init()
@ -101,7 +105,7 @@ async def async_setup_entry(hass, entry):
return True 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 a Tile config entry."""
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
if unload_ok: if unload_ok:

View File

@ -1,10 +1,15 @@
"""Config flow to configure the Tile integration.""" """Config flow to configure the Tile integration."""
from __future__ import annotations
from typing import Any
from pytile import async_login from pytile import async_login
from pytile.errors import TileError from pytile.errors import TileError
import voluptuous as vol import voluptuous as vol
from homeassistant import config_entries from homeassistant import config_entries
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
from homeassistant.data_entry_flow import FlowResult
from homeassistant.helpers import aiohttp_client from homeassistant.helpers import aiohttp_client
from .const import DOMAIN from .const import DOMAIN
@ -15,23 +20,25 @@ class TileFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
VERSION = 1 VERSION = 1
def __init__(self): def __init__(self) -> None:
"""Initialize the config flow.""" """Initialize the config flow."""
self.data_schema = vol.Schema( self.data_schema = vol.Schema(
{vol.Required(CONF_USERNAME): str, vol.Required(CONF_PASSWORD): str} {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.""" """Show the form to the user."""
return self.async_show_form( return self.async_show_form(
step_id="user", data_schema=self.data_schema, errors=errors or {} 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.""" """Import a config entry from configuration.yaml."""
return await self.async_step_user(import_config) 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.""" """Handle the start of the config flow."""
if not user_input: if not user_input:
return await self._show_form() return await self._show_form()

View File

@ -1,12 +1,23 @@
"""Support for Tile device trackers.""" """Support for Tile device trackers."""
from __future__ import annotations
from collections.abc import Awaitable
import logging 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.config_entry import TrackerEntity
from homeassistant.components.device_tracker.const import SOURCE_TYPE_GPS 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.const import ATTR_ATTRIBUTION, CONF_PASSWORD, CONF_USERNAME
from homeassistant.core import callback from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.update_coordinator import CoordinatorEntity 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 from . import DATA_COORDINATOR, DATA_TILE, DOMAIN
@ -25,7 +36,9 @@ DEFAULT_ATTRIBUTION = "Data provided by Tile"
DEFAULT_ICON = "mdi:view-grid" 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.""" """Set up Tile device trackers."""
async_add_entities( 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.""" """Detect a legacy configuration and import it."""
hass.async_create_task( hass.async_create_task(
hass.config_entries.flow.async_init( hass.config_entries.flow.async_init(
@ -65,7 +83,9 @@ class TileDeviceTracker(CoordinatorEntity, TrackerEntity):
_attr_icon = DEFAULT_ICON _attr_icon = DEFAULT_ICON
def __init__(self, entry, coordinator, tile): def __init__(
self, entry: ConfigEntry, coordinator: DataUpdateCoordinator, tile: Tile
) -> None:
"""Initialize.""" """Initialize."""
super().__init__(coordinator) super().__init__(coordinator)
@ -76,41 +96,47 @@ class TileDeviceTracker(CoordinatorEntity, TrackerEntity):
self._tile = tile self._tile = tile
@property @property
def available(self): def available(self) -> bool:
"""Return if entity is available.""" """Return if entity is available."""
return super().available and not self._tile.dead return super().available and not self._tile.dead
@property @property
def location_accuracy(self): def location_accuracy(self) -> int:
"""Return the location accuracy of the device. """Return the location accuracy of the device.
Value in meters. Value in meters.
""" """
return self._tile.accuracy if not self._tile.accuracy:
return super().location_accuracy
return int(self._tile.accuracy)
@property @property
def latitude(self) -> float: def latitude(self) -> float | None:
"""Return latitude value of the device.""" """Return latitude value of the device."""
if not self._tile.latitude:
return None
return self._tile.latitude return self._tile.latitude
@property @property
def longitude(self) -> float: def longitude(self) -> float | None:
"""Return longitude value of the device.""" """Return longitude value of the device."""
if not self._tile.longitude:
return None
return self._tile.longitude return self._tile.longitude
@property @property
def source_type(self): def source_type(self) -> str:
"""Return the source type, eg gps or router, of the device.""" """Return the source type, eg gps or router, of the device."""
return SOURCE_TYPE_GPS return SOURCE_TYPE_GPS
@callback @callback
def _handle_coordinator_update(self): def _handle_coordinator_update(self) -> None:
"""Respond to a DataUpdateCoordinator update.""" """Respond to a DataUpdateCoordinator update."""
self._update_from_latest_data() self._update_from_latest_data()
self.async_write_ha_state() self.async_write_ha_state()
@callback @callback
def _update_from_latest_data(self): def _update_from_latest_data(self) -> None:
"""Update the entity from the latest data.""" """Update the entity from the latest data."""
self._attr_extra_state_attributes.update( 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.""" """Handle entity which will be added."""
await super().async_added_to_hass() await super().async_added_to_hass()
self._update_from_latest_data() self._update_from_latest_data()

View File

@ -3,7 +3,7 @@
"name": "Tile", "name": "Tile",
"config_flow": true, "config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/tile", "documentation": "https://www.home-assistant.io/integrations/tile",
"requirements": ["pytile==5.2.2"], "requirements": ["pytile==5.2.3"],
"codeowners": ["@bachya"], "codeowners": ["@bachya"],
"iot_class": "cloud_polling" "iot_class": "cloud_polling"
} }

View File

@ -1056,6 +1056,17 @@ no_implicit_optional = true
warn_return_any = true warn_return_any = true
warn_unreachable = 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.*] [mypy-homeassistant.components.tts.*]
check_untyped_defs = true check_untyped_defs = true
disallow_incomplete_defs = true disallow_incomplete_defs = true

View File

@ -1928,7 +1928,7 @@ python_opendata_transport==0.2.1
pythonegardia==1.0.40 pythonegardia==1.0.40
# homeassistant.components.tile # homeassistant.components.tile
pytile==5.2.2 pytile==5.2.3
# homeassistant.components.touchline # homeassistant.components.touchline
pytouchline==0.7 pytouchline==0.7

View File

@ -1071,7 +1071,7 @@ python-velbus==2.1.2
python_awair==0.2.1 python_awair==0.2.1
# homeassistant.components.tile # homeassistant.components.tile
pytile==5.2.2 pytile==5.2.3
# homeassistant.components.traccar # homeassistant.components.traccar
pytraccar==0.9.0 pytraccar==0.9.0