Files
core/homeassistant/components/monzo/__init__.py
T

154 lines
5.5 KiB
Python

"""The Monzo integration."""
import contextlib
import logging
from aiohttp import ClientError
from monzopy import AuthorisationExpiredError, InvalidMonzoAPIResponseError
from homeassistant.components import cloud
from homeassistant.components.webhook import async_generate_id
from homeassistant.const import CONF_ACCESS_TOKEN, CONF_WEBHOOK_ID, Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady, OAuth2TokenRequestError
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.config_entry_oauth2_flow import (
ImplementationUnavailableError,
OAuth2Session,
async_get_config_entry_implementation,
)
from homeassistant.helpers.typing import ConfigType
from .api import AuthenticatedMonzoAPI, MonzoAPI
from .const import CONF_CLOUDHOOK_URL, CONF_WEBHOOK_URL, DOMAIN
from .coordinator import MonzoConfigEntry, MonzoCoordinator, MonzoRuntimeData
from .helpers import get_authenticated_owner_name
from .services import async_setup_services
from .webhook import MonzoWebhookManager, async_delete_remote_webhooks
_LOGGER = logging.getLogger(__name__)
PLATFORMS: list[Platform] = [Platform.EVENT, Platform.SENSOR]
async def _async_create_api(
hass: HomeAssistant, entry: MonzoConfigEntry
) -> AuthenticatedMonzoAPI:
"""Create an authenticated Monzo API client."""
implementation = await async_get_config_entry_implementation(hass, entry)
session = OAuth2Session(hass, entry, implementation)
return AuthenticatedMonzoAPI(async_get_clientsession(hass), session)
async def _async_create_removal_api(
hass: HomeAssistant, entry: MonzoConfigEntry
) -> MonzoAPI:
"""Create a Monzo API client without updating a deleted config entry."""
implementation = await async_get_config_entry_implementation(hass, entry)
session = OAuth2Session(hass, entry, implementation)
token = session.token
if not session.valid_token:
token = await implementation.async_refresh_token(token)
return MonzoAPI(async_get_clientsession(hass), token[CONF_ACCESS_TOKEN])
CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up the Monzo integration."""
async_setup_services(hass)
return True
async def async_migrate_entry(hass: HomeAssistant, entry: MonzoConfigEntry) -> bool:
"""Migrate entry."""
_LOGGER.debug("Migrating from version %s.%s", entry.version, entry.minor_version)
if entry.version == 1:
data = dict(entry.data)
minor_version = entry.minor_version
unique_id = entry.unique_id
# 1 -> 1.2: Unique ID from integer to string
if minor_version == 1:
unique_id = str(unique_id)
minor_version = 2
# 1.2 -> 1.3: Add a stable webhook ID
if minor_version < 3:
data[CONF_WEBHOOK_ID] = async_generate_id()
minor_version = 3
hass.config_entries.async_update_entry(
entry,
data=data,
unique_id=unique_id,
minor_version=minor_version,
)
_LOGGER.debug("Migration successful")
return True
async def async_setup_entry(hass: HomeAssistant, entry: MonzoConfigEntry) -> bool:
"""Set up Monzo from a config entry."""
try:
external_api = await _async_create_api(hass, entry)
except ImplementationUnavailableError as err:
raise ConfigEntryNotReady(
translation_domain=DOMAIN,
translation_key="oauth2_implementation_unavailable",
) from err
coordinator = MonzoCoordinator(hass, entry, external_api)
await coordinator.async_config_entry_first_refresh()
if entry.title == DOMAIN and (
owner_name := get_authenticated_owner_name(
coordinator.data.accounts.values(), entry.unique_id
)
):
hass.config_entries.async_update_entry(entry, title=owner_name)
webhook_manager = MonzoWebhookManager(hass, entry, coordinator)
entry.runtime_data = MonzoRuntimeData(coordinator, webhook_manager)
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
await webhook_manager.async_setup()
return True
async def async_unload_entry(hass: HomeAssistant, entry: MonzoConfigEntry) -> bool:
"""Unload a config entry."""
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
if unload_ok:
await entry.runtime_data.webhook_manager.async_unload()
return unload_ok
async def async_remove_entry(hass: HomeAssistant, entry: MonzoConfigEntry) -> None:
"""Remove webhooks for a deleted config entry."""
if webhook_url := entry.data.get(CONF_WEBHOOK_URL):
try:
api = await _async_create_removal_api(hass, entry)
accounts = await api.user_account.accounts()
await async_delete_remote_webhooks(
api, (account["id"] for account in accounts), webhook_url
)
except (
AuthorisationExpiredError,
ClientError,
ImplementationUnavailableError,
InvalidMonzoAPIResponseError,
OAuth2TokenRequestError,
TimeoutError,
) as err:
_LOGGER.warning("Unable to remove Monzo webhooks: %s", err)
if CONF_CLOUDHOOK_URL in entry.data:
with contextlib.suppress(cloud.CloudNotAvailable):
await cloud.async_delete_cloudhook(hass, entry.data[CONF_WEBHOOK_ID])