Add some typing to doorbird (#98483)

This commit is contained in:
J. Nick Koston 2023-08-16 04:33:25 -05:00 committed by GitHub
parent b680bca5e9
commit b083f5bf89
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 166 additions and 130 deletions

View File

@ -212,8 +212,9 @@ omit =
homeassistant/components/dominos/* homeassistant/components/dominos/*
homeassistant/components/doods/* homeassistant/components/doods/*
homeassistant/components/doorbird/__init__.py homeassistant/components/doorbird/__init__.py
homeassistant/components/doorbird/button.py
homeassistant/components/doorbird/camera.py homeassistant/components/doorbird/camera.py
homeassistant/components/doorbird/button.py
homeassistant/components/doorbird/device.py
homeassistant/components/doorbird/entity.py homeassistant/components/doorbird/entity.py
homeassistant/components/doorbird/util.py homeassistant/components/doorbird/util.py
homeassistant/components/dormakaba_dkey/__init__.py homeassistant/components/dormakaba_dkey/__init__.py

View File

@ -24,11 +24,10 @@ from homeassistant.const import (
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.exceptions import ConfigEntryNotReady
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.network import get_url
from homeassistant.helpers.typing import ConfigType from homeassistant.helpers.typing import ConfigType
from homeassistant.util import dt as dt_util, slugify
from .const import ( from .const import (
API_URL,
CONF_EVENTS, CONF_EVENTS,
DOMAIN, DOMAIN,
DOOR_STATION, DOOR_STATION,
@ -37,12 +36,11 @@ from .const import (
PLATFORMS, PLATFORMS,
UNDO_UPDATE_LISTENER, UNDO_UPDATE_LISTENER,
) )
from .device import ConfiguredDoorBird
from .util import get_doorstation_by_token from .util import get_doorstation_by_token
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
API_URL = f"/api/{DOMAIN}"
CONF_CUSTOM_URL = "hass_url_override" CONF_CUSTOM_URL = "hass_url_override"
RESET_DEVICE_FAVORITES = "doorbird_reset_favorites" RESET_DEVICE_FAVORITES = "doorbird_reset_favorites"
@ -128,9 +126,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
) )
raise ConfigEntryNotReady raise ConfigEntryNotReady
token = doorstation_config.get(CONF_TOKEN, config_entry_id) token: str = doorstation_config.get(CONF_TOKEN, config_entry_id)
custom_url = doorstation_config.get(CONF_CUSTOM_URL) custom_url: str | None = doorstation_config.get(CONF_CUSTOM_URL)
name = doorstation_config.get(CONF_NAME) name: str | None = doorstation_config.get(CONF_NAME)
events = doorstation_options.get(CONF_EVENTS, []) events = doorstation_options.get(CONF_EVENTS, [])
doorstation = ConfiguredDoorBird(device, name, custom_url, token) doorstation = ConfiguredDoorBird(device, name, custom_url, token)
doorstation.update_events(events) doorstation.update_events(events)
@ -151,7 +149,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
return True return True
def _init_doorbird_device(device): def _init_doorbird_device(device: DoorBird) -> tuple[tuple[bool, int], dict[str, Any]]:
return device.ready(), device.info() return device.ready(), device.info()
@ -211,122 +209,6 @@ def _async_import_options_from_data_if_missing(hass: HomeAssistant, entry: Confi
hass.config_entries.async_update_entry(entry, options=options) hass.config_entries.async_update_entry(entry, options=options)
class ConfiguredDoorBird:
"""Attach additional information to pass along with configured device."""
def __init__(self, device, name, custom_url, token):
"""Initialize configured device."""
self._name = name
self._device = device
self._custom_url = custom_url
self.events = None
self.doorstation_events = None
self._token = token
def update_events(self, events):
"""Update the doorbird events."""
self.events = events
self.doorstation_events = [self._get_event_name(event) for event in self.events]
@property
def name(self):
"""Get custom device name."""
return self._name
@property
def device(self):
"""Get the configured device."""
return self._device
@property
def custom_url(self):
"""Get custom url for device."""
return self._custom_url
@property
def token(self):
"""Get token for device."""
return self._token
def register_events(self, hass: HomeAssistant) -> None:
"""Register events on device."""
# Get the URL of this server
hass_url = get_url(hass, prefer_external=False)
# Override url if another is specified in the configuration
if self.custom_url is not None:
hass_url = self.custom_url
if not self.doorstation_events:
# User may not have permission to get the favorites
return
favorites = self.device.favorites()
for event in self.doorstation_events:
if self._register_event(hass_url, event, favs=favorites):
_LOGGER.info(
"Successfully registered URL for %s on %s", event, self.name
)
@property
def slug(self):
"""Get device slug."""
return slugify(self._name)
def _get_event_name(self, event):
return f"{self.slug}_{event}"
def _register_event(
self, hass_url: str, event: str, favs: dict[str, Any] | None = None
) -> bool:
"""Add a schedule entry in the device for a sensor."""
url = f"{hass_url}{API_URL}/{event}?token={self._token}"
# Register HA URL as webhook if not already, then get the ID
if self.webhook_is_registered(url, favs=favs):
return True
self.device.change_favorite("http", f"Home Assistant ({event})", url)
if not self.webhook_is_registered(url):
_LOGGER.warning(
'Unable to set favorite URL "%s". Event "%s" will not fire',
url,
event,
)
return False
return True
def webhook_is_registered(self, url, favs=None) -> bool:
"""Return whether the given URL is registered as a device favorite."""
return self.get_webhook_id(url, favs) is not None
def get_webhook_id(self, url, favs=None) -> str | None:
"""Return the device favorite ID for the given URL.
The favorite must exist or there will be problems.
"""
favs = favs if favs else self.device.favorites()
if "http" not in favs:
return None
for fav_id in favs["http"]:
if favs["http"][fav_id]["value"] == url:
return fav_id
return None
def get_event_data(self):
"""Get data to pass along with HA event."""
return {
"timestamp": dt_util.utcnow().isoformat(),
"live_video_url": self._device.live_video_url,
"live_image_url": self._device.live_image_url,
"rtsp_live_video_url": self._device.rtsp_live_video_url,
"html5_viewer_url": self._device.html5_viewer_url,
}
class DoorBirdRequestView(HomeAssistantView): class DoorBirdRequestView(HomeAssistantView):
"""Provide a page for the device to call.""" """Provide a page for the device to call."""

View File

@ -19,3 +19,5 @@ DOORBIRD_INFO_KEY_PRIMARY_MAC_ADDR = "PRIMARY_MAC_ADDR"
DOORBIRD_INFO_KEY_WIFI_MAC_ADDR = "WIFI_MAC_ADDR" DOORBIRD_INFO_KEY_WIFI_MAC_ADDR = "WIFI_MAC_ADDR"
UNDO_UPDATE_LISTENER = "undo_update_listener" UNDO_UPDATE_LISTENER = "undo_update_listener"
API_URL = f"/api/{DOMAIN}"

View File

@ -0,0 +1,137 @@
"""Support for DoorBird devices."""
from __future__ import annotations
import logging
from typing import Any
from doorbirdpy import DoorBird
from homeassistant.core import HomeAssistant
from homeassistant.helpers.network import get_url
from homeassistant.util import dt as dt_util, slugify
from .const import API_URL
_LOGGER = logging.getLogger(__name__)
class ConfiguredDoorBird:
"""Attach additional information to pass along with configured device."""
def __init__(
self, device: DoorBird, name: str | None, custom_url: str | None, token: str
) -> None:
"""Initialize configured device."""
self._name = name
self._device = device
self._custom_url = custom_url
self.events = None
self.doorstation_events = None
self._token = token
def update_events(self, events):
"""Update the doorbird events."""
self.events = events
self.doorstation_events = [self._get_event_name(event) for event in self.events]
@property
def name(self) -> str | None:
"""Get custom device name."""
return self._name
@property
def device(self) -> DoorBird:
"""Get the configured device."""
return self._device
@property
def custom_url(self) -> str | None:
"""Get custom url for device."""
return self._custom_url
@property
def token(self) -> str:
"""Get token for device."""
return self._token
def register_events(self, hass: HomeAssistant) -> None:
"""Register events on device."""
# Get the URL of this server
hass_url = get_url(hass, prefer_external=False)
# Override url if another is specified in the configuration
if self.custom_url is not None:
hass_url = self.custom_url
if not self.doorstation_events:
# User may not have permission to get the favorites
return
favorites = self.device.favorites()
for event in self.doorstation_events:
if self._register_event(hass_url, event, favs=favorites):
_LOGGER.info(
"Successfully registered URL for %s on %s", event, self.name
)
@property
def slug(self) -> str:
"""Get device slug."""
return slugify(self._name)
def _get_event_name(self, event: str) -> str:
return f"{self.slug}_{event}"
def _register_event(
self, hass_url: str, event: str, favs: dict[str, Any] | None = None
) -> bool:
"""Add a schedule entry in the device for a sensor."""
url = f"{hass_url}{API_URL}/{event}?token={self._token}"
# Register HA URL as webhook if not already, then get the ID
if self.webhook_is_registered(url, favs=favs):
return True
self.device.change_favorite("http", f"Home Assistant ({event})", url)
if not self.webhook_is_registered(url):
_LOGGER.warning(
'Unable to set favorite URL "%s". Event "%s" will not fire',
url,
event,
)
return False
return True
def webhook_is_registered(
self, url: str, favs: dict[str, Any] | None = None
) -> bool:
"""Return whether the given URL is registered as a device favorite."""
return self.get_webhook_id(url, favs) is not None
def get_webhook_id(
self, url: str, favs: dict[str, Any] | None = None
) -> str | None:
"""Return the device favorite ID for the given URL.
The favorite must exist or there will be problems.
"""
favs = favs if favs else self.device.favorites()
if "http" not in favs:
return None
for fav_id in favs["http"]:
if favs["http"][fav_id]["value"] == url:
return fav_id
return None
def get_event_data(self) -> dict[str, str]:
"""Get data to pass along with HA event."""
return {
"timestamp": dt_util.utcnow().isoformat(),
"live_video_url": self._device.live_video_url,
"live_image_url": self._device.live_image_url,
"rtsp_live_video_url": self._device.rtsp_live_video_url,
"html5_viewer_url": self._device.html5_viewer_url,
}

View File

@ -1,5 +1,7 @@
"""The DoorBird integration base entity.""" """The DoorBird integration base entity."""
from typing import Any
from homeassistant.helpers import device_registry as dr from homeassistant.helpers import device_registry as dr
from homeassistant.helpers.device_registry import DeviceInfo from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
@ -10,6 +12,7 @@ from .const import (
DOORBIRD_INFO_KEY_FIRMWARE, DOORBIRD_INFO_KEY_FIRMWARE,
MANUFACTURER, MANUFACTURER,
) )
from .device import ConfiguredDoorBird
from .util import get_mac_address_from_doorstation_info from .util import get_mac_address_from_doorstation_info
@ -18,7 +21,9 @@ class DoorBirdEntity(Entity):
_attr_has_entity_name = True _attr_has_entity_name = True
def __init__(self, doorstation, doorstation_info): def __init__(
self, doorstation: ConfiguredDoorBird, doorstation_info: dict[str, Any]
) -> None:
"""Initialize the entity.""" """Initialize the entity."""
super().__init__() super().__init__()
self._doorstation = doorstation self._doorstation = doorstation

View File

@ -1,6 +1,9 @@
"""DoorBird integration utils.""" """DoorBird integration utils."""
from homeassistant.core import HomeAssistant
from .const import DOMAIN, DOOR_STATION from .const import DOMAIN, DOOR_STATION
from .device import ConfiguredDoorBird
def get_mac_address_from_doorstation_info(doorstation_info): def get_mac_address_from_doorstation_info(doorstation_info):
@ -10,17 +13,23 @@ def get_mac_address_from_doorstation_info(doorstation_info):
return doorstation_info["WIFI_MAC_ADDR"] return doorstation_info["WIFI_MAC_ADDR"]
def get_doorstation_by_token(hass, token): def get_doorstation_by_token(
hass: HomeAssistant, token: str
) -> ConfiguredDoorBird | None:
"""Get doorstation by token.""" """Get doorstation by token."""
return _get_doorstation_by_attr(hass, "token", token) return _get_doorstation_by_attr(hass, "token", token)
def get_doorstation_by_slug(hass, slug): def get_doorstation_by_slug(
hass: HomeAssistant, slug: str
) -> ConfiguredDoorBird | None:
"""Get doorstation by slug.""" """Get doorstation by slug."""
return _get_doorstation_by_attr(hass, "slug", slug) return _get_doorstation_by_attr(hass, "slug", slug)
def _get_doorstation_by_attr(hass, attr, val): def _get_doorstation_by_attr(
hass: HomeAssistant, attr: str, val: str
) -> ConfiguredDoorBird | None:
for entry in hass.data[DOMAIN].values(): for entry in hass.data[DOMAIN].values():
if DOOR_STATION not in entry: if DOOR_STATION not in entry:
continue continue
@ -33,7 +42,7 @@ def _get_doorstation_by_attr(hass, attr, val):
return None return None
def get_all_doorstations(hass): def get_all_doorstations(hass: HomeAssistant) -> list[ConfiguredDoorBird]:
"""Get all doorstations.""" """Get all doorstations."""
return [ return [
entry[DOOR_STATION] entry[DOOR_STATION]