From 2c0033254bd23993efad5d995169d13074df3580 Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Fri, 14 Jan 2022 16:35:35 +0100 Subject: [PATCH] Import cloud (#64116) * Add type hints to cloud * Import cloud * Adjust smartthings tests Co-authored-by: epenet --- homeassistant/components/cloud/__init__.py | 10 +++++----- .../components/mobile_app/http_api.py | 11 ++++++----- homeassistant/components/mobile_app/webhook.py | 4 ++-- homeassistant/components/netatmo/__init__.py | 11 ++++------- homeassistant/components/owntracks/__init__.py | 4 ++-- .../components/owntracks/config_flow.py | 8 +++----- homeassistant/components/plaato/config_flow.py | 8 +++----- homeassistant/components/rachio/__init__.py | 3 ++- homeassistant/components/rachio/webhooks.py | 8 +++----- .../components/smartthings/smartapp.py | 18 ++++++++---------- homeassistant/components/toon/coordinator.py | 7 ++++--- .../components/smartthings/test_config_flow.py | 6 ++++-- 12 files changed, 46 insertions(+), 52 deletions(-) diff --git a/homeassistant/components/cloud/__init__.py b/homeassistant/components/cloud/__init__.py index 272080d63fd..215f2a8d1e8 100644 --- a/homeassistant/components/cloud/__init__.py +++ b/homeassistant/components/cloud/__init__.py @@ -116,20 +116,20 @@ class CloudNotAvailable(HomeAssistantError): @bind_hass @callback -def async_is_logged_in(hass) -> bool: +def async_is_logged_in(hass: HomeAssistant) -> bool: """Test if user is logged in.""" return DOMAIN in hass.data and hass.data[DOMAIN].is_logged_in @bind_hass @callback -def async_active_subscription(hass) -> bool: +def async_active_subscription(hass: HomeAssistant) -> bool: """Test if user has an active subscription.""" return async_is_logged_in(hass) and not hass.data[DOMAIN].subscription_expired @bind_hass -async def async_create_cloudhook(hass, webhook_id: str) -> str: +async def async_create_cloudhook(hass: HomeAssistant, webhook_id: str) -> str: """Create a cloudhook.""" if not async_is_logged_in(hass): raise CloudNotAvailable @@ -139,7 +139,7 @@ async def async_create_cloudhook(hass, webhook_id: str) -> str: @bind_hass -async def async_delete_cloudhook(hass, webhook_id: str) -> None: +async def async_delete_cloudhook(hass: HomeAssistant, webhook_id: str) -> None: """Delete a cloudhook.""" if DOMAIN not in hass.data: raise CloudNotAvailable @@ -149,7 +149,7 @@ async def async_delete_cloudhook(hass, webhook_id: str) -> None: @bind_hass @callback -def async_remote_ui_url(hass) -> str: +def async_remote_ui_url(hass: HomeAssistant) -> str: """Get the remote UI URL.""" if not async_is_logged_in(hass): raise CloudNotAvailable diff --git a/homeassistant/components/mobile_app/http_api.py b/homeassistant/components/mobile_app/http_api.py index b817e99493f..a80e7db204e 100644 --- a/homeassistant/components/mobile_app/http_api.py +++ b/homeassistant/components/mobile_app/http_api.py @@ -10,6 +10,7 @@ import emoji from nacl.secret import SecretBox import voluptuous as vol +from homeassistant.components import cloud from homeassistant.components.http import HomeAssistantView from homeassistant.components.http.data_validator import RequestDataValidator from homeassistant.const import ATTR_DEVICE_ID, CONF_WEBHOOK_ID @@ -68,10 +69,10 @@ class RegistrationsView(HomeAssistantView): webhook_id = secrets.token_hex() - if hass.components.cloud.async_active_subscription(): - data[ - CONF_CLOUDHOOK_URL - ] = await hass.components.cloud.async_create_cloudhook(webhook_id) + if cloud.async_active_subscription(hass): + data[CONF_CLOUDHOOK_URL] = await cloud.async_create_cloudhook( + hass, webhook_id + ) data[CONF_WEBHOOK_ID] = webhook_id @@ -102,7 +103,7 @@ class RegistrationsView(HomeAssistantView): remote_ui_url = None with suppress(hass.components.cloud.CloudNotAvailable): - remote_ui_url = hass.components.cloud.async_remote_ui_url() + remote_ui_url = cloud.async_remote_ui_url(hass) return self.json( { diff --git a/homeassistant/components/mobile_app/webhook.py b/homeassistant/components/mobile_app/webhook.py index 961f6f54c88..c2bde7d2312 100644 --- a/homeassistant/components/mobile_app/webhook.py +++ b/homeassistant/components/mobile_app/webhook.py @@ -10,7 +10,7 @@ from aiohttp.web import HTTPBadRequest, Request, Response, json_response from nacl.secret import SecretBox import voluptuous as vol -from homeassistant.components import notify as hass_notify, tag +from homeassistant.components import cloud, notify as hass_notify, tag from homeassistant.components.binary_sensor import ( DEVICE_CLASSES as BINARY_SENSOR_CLASSES, ) @@ -593,7 +593,7 @@ async def webhook_get_config(hass, config_entry, data): resp[CONF_CLOUDHOOK_URL] = config_entry.data[CONF_CLOUDHOOK_URL] with suppress(hass.components.cloud.CloudNotAvailable): - resp[CONF_REMOTE_UI_URL] = hass.components.cloud.async_remote_ui_url() + resp[CONF_REMOTE_UI_URL] = cloud.async_remote_ui_url(hass) return webhook_response(resp, registration=config_entry.data) diff --git a/homeassistant/components/netatmo/__init__.py b/homeassistant/components/netatmo/__init__.py index 8c8c848f4e3..ad27e18340d 100644 --- a/homeassistant/components/netatmo/__init__.py +++ b/homeassistant/components/netatmo/__init__.py @@ -183,10 +183,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: data = {**entry.data, CONF_WEBHOOK_ID: secrets.token_hex()} hass.config_entries.async_update_entry(entry, data=data) - if hass.components.cloud.async_active_subscription(): + if cloud.async_active_subscription(hass): if CONF_CLOUDHOOK_URL not in entry.data: - webhook_url = await hass.components.cloud.async_create_cloudhook( - entry.data[CONF_WEBHOOK_ID] + webhook_url = await cloud.async_create_cloudhook( + hass, entry.data[CONF_WEBHOOK_ID] ) data = {**entry.data, CONF_CLOUDHOOK_URL: webhook_url} hass.config_entries.async_update_entry(entry, data=data) @@ -276,10 +276,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_remove_entry(hass: HomeAssistant, entry: ConfigEntry) -> None: """Cleanup when entry is removed.""" - if ( - CONF_WEBHOOK_ID in entry.data - and hass.components.cloud.async_active_subscription() - ): + if CONF_WEBHOOK_ID in entry.data and cloud.async_active_subscription(hass): try: _LOGGER.debug( "Removing Netatmo cloudhook (%s)", entry.data[CONF_WEBHOOK_ID] diff --git a/homeassistant/components/owntracks/__init__.py b/homeassistant/components/owntracks/__init__.py index e61acc83a70..9fad7c2abf5 100644 --- a/homeassistant/components/owntracks/__init__.py +++ b/homeassistant/components/owntracks/__init__.py @@ -8,7 +8,7 @@ from aiohttp.web import json_response import voluptuous as vol from homeassistant import config_entries -from homeassistant.components import mqtt, webhook +from homeassistant.components import cloud, mqtt, webhook from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( ATTR_GPS_ACCURACY, @@ -126,7 +126,7 @@ async def async_remove_entry(hass: HomeAssistant, entry: ConfigEntry) -> None: if not entry.data.get("cloudhook"): return - await hass.components.cloud.async_delete_cloudhook(entry.data[CONF_WEBHOOK_ID]) + await cloud.async_delete_cloudhook(hass, entry.data[CONF_WEBHOOK_ID]) async def async_connect_mqtt(hass, component): diff --git a/homeassistant/components/owntracks/config_flow.py b/homeassistant/components/owntracks/config_flow.py index 5f6462e7628..e0a959ae108 100644 --- a/homeassistant/components/owntracks/config_flow.py +++ b/homeassistant/components/owntracks/config_flow.py @@ -2,7 +2,7 @@ import secrets from homeassistant import config_entries -from homeassistant.components import webhook +from homeassistant.components import cloud, webhook from homeassistant.const import CONF_WEBHOOK_ID from .const import DOMAIN @@ -68,10 +68,8 @@ class OwnTracksFlow(config_entries.ConfigFlow, domain=DOMAIN): async def _get_webhook_id(self): """Generate webhook ID.""" webhook_id = webhook.async_generate_id() - if self.hass.components.cloud.async_active_subscription(): - webhook_url = await self.hass.components.cloud.async_create_cloudhook( - webhook_id - ) + if cloud.async_active_subscription(self.hass): + webhook_url = await cloud.async_create_cloudhook(self.hass, webhook_id) cloudhook = True else: webhook_url = webhook.async_generate_url(self.hass, webhook_id) diff --git a/homeassistant/components/plaato/config_flow.py b/homeassistant/components/plaato/config_flow.py index 729c106119d..10bd96aa72c 100644 --- a/homeassistant/components/plaato/config_flow.py +++ b/homeassistant/components/plaato/config_flow.py @@ -3,7 +3,7 @@ from pyplaato.plaato import PlaatoDeviceType import voluptuous as vol from homeassistant import config_entries -from homeassistant.components import webhook +from homeassistant.components import cloud, webhook from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_SCAN_INTERVAL, CONF_TOKEN, CONF_WEBHOOK_ID from homeassistant.core import callback @@ -141,10 +141,8 @@ class PlaatoConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def _get_webhook_id(self): """Generate webhook ID.""" webhook_id = webhook.async_generate_id() - if self.hass.components.cloud.async_active_subscription(): - webhook_url = await self.hass.components.cloud.async_create_cloudhook( - webhook_id - ) + if cloud.async_active_subscription(self.hass): + webhook_url = await cloud.async_create_cloudhook(self.hass, webhook_id) cloudhook = True else: webhook_url = webhook.async_generate_url(self.hass, webhook_id) diff --git a/homeassistant/components/rachio/__init__.py b/homeassistant/components/rachio/__init__.py index 028f02ad292..eafb8ee25ba 100644 --- a/homeassistant/components/rachio/__init__.py +++ b/homeassistant/components/rachio/__init__.py @@ -5,6 +5,7 @@ import secrets from rachiopy import Rachio from requests.exceptions import ConnectTimeout +from homeassistant.components import cloud from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_API_KEY, Platform from homeassistant.core import HomeAssistant @@ -36,7 +37,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_remove_entry(hass: HomeAssistant, entry: ConfigEntry) -> None: """Remove a rachio config entry.""" if CONF_CLOUDHOOK_URL in entry.data: - await hass.components.cloud.async_delete_cloudhook(entry.data[CONF_WEBHOOK_ID]) + await cloud.async_delete_cloudhook(hass, entry.data[CONF_WEBHOOK_ID]) async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: diff --git a/homeassistant/components/rachio/webhooks.py b/homeassistant/components/rachio/webhooks.py index d04773170f6..6ad396b76a1 100644 --- a/homeassistant/components/rachio/webhooks.py +++ b/homeassistant/components/rachio/webhooks.py @@ -1,7 +1,7 @@ """Webhooks used by rachio.""" from aiohttp import web -from homeassistant.components import webhook +from homeassistant.components import cloud, webhook from homeassistant.const import URL_API from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_send @@ -115,11 +115,9 @@ async def async_get_or_create_registered_webhook_id_and_url(hass, entry): config[CONF_WEBHOOK_ID] = webhook_id updated_config = True - if hass.components.cloud.async_active_subscription(): + if cloud.async_active_subscription(hass): if not (cloudhook_url := config.get(CONF_CLOUDHOOK_URL)): - cloudhook_url = await hass.components.cloud.async_create_cloudhook( - webhook_id - ) + cloudhook_url = await cloud.async_create_cloudhook(hass, webhook_id) config[CONF_CLOUDHOOK_URL] = cloudhook_url updated_config = True webhook_url = cloudhook_url diff --git a/homeassistant/components/smartthings/smartapp.py b/homeassistant/components/smartthings/smartapp.py index 8feb5f512d6..a6d35c40335 100644 --- a/homeassistant/components/smartthings/smartapp.py +++ b/homeassistant/components/smartthings/smartapp.py @@ -23,7 +23,7 @@ from pysmartthings import ( SubscriptionEntity, ) -from homeassistant.components import webhook +from homeassistant.components import cloud, webhook from homeassistant.const import CONF_WEBHOOK_ID from homeassistant.core import HomeAssistant from homeassistant.helpers.aiohttp_client import async_get_clientsession @@ -94,7 +94,7 @@ async def validate_installed_app(api, installed_app_id: str): def validate_webhook_requirements(hass: HomeAssistant) -> bool: """Ensure Home Assistant is setup properly to receive webhooks.""" - if hass.components.cloud.async_active_subscription(): + if cloud.async_active_subscription(hass): return True if hass.data[DOMAIN][CONF_CLOUDHOOK_URL] is not None: return True @@ -108,7 +108,7 @@ def get_webhook_url(hass: HomeAssistant) -> str: Return the cloudhook if available, otherwise local webhook. """ cloudhook_url = hass.data[DOMAIN][CONF_CLOUDHOOK_URL] - if hass.components.cloud.async_active_subscription() and cloudhook_url is not None: + if cloud.async_active_subscription(hass) and cloudhook_url is not None: return cloudhook_url return webhook.async_generate_url(hass, hass.data[DOMAIN][CONF_WEBHOOK_ID]) @@ -229,11 +229,11 @@ async def setup_smartapp_endpoint(hass: HomeAssistant): cloudhook_url = config.get(CONF_CLOUDHOOK_URL) if ( cloudhook_url is None - and hass.components.cloud.async_active_subscription() + and cloud.async_active_subscription(hass) and not hass.config_entries.async_entries(DOMAIN) ): - cloudhook_url = await hass.components.cloud.async_create_cloudhook( - config[CONF_WEBHOOK_ID] + cloudhook_url = await cloud.async_create_cloudhook( + hass, config[CONF_WEBHOOK_ID] ) config[CONF_CLOUDHOOK_URL] = cloudhook_url await store.async_save(config) @@ -279,10 +279,8 @@ async def unload_smartapp_endpoint(hass: HomeAssistant): return # Remove the cloudhook if it was created cloudhook_url = hass.data[DOMAIN][CONF_CLOUDHOOK_URL] - if cloudhook_url and hass.components.cloud.async_is_logged_in(): - await hass.components.cloud.async_delete_cloudhook( - hass.data[DOMAIN][CONF_WEBHOOK_ID] - ) + if cloudhook_url and cloud.async_is_logged_in(hass): + await cloud.async_delete_cloudhook(hass, hass.data[DOMAIN][CONF_WEBHOOK_ID]) # Remove cloudhook from storage store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY) await store.async_save( diff --git a/homeassistant/components/toon/coordinator.py b/homeassistant/components/toon/coordinator.py index 9405de22bf8..712edd9dfda 100644 --- a/homeassistant/components/toon/coordinator.py +++ b/homeassistant/components/toon/coordinator.py @@ -6,6 +6,7 @@ import secrets from toonapi import Status, Toon, ToonError +from homeassistant.components import cloud from homeassistant.components.webhook import ( async_register as webhook_register, async_unregister as webhook_unregister, @@ -57,11 +58,11 @@ class ToonDataUpdateCoordinator(DataUpdateCoordinator[Status]): data = {**self.entry.data, CONF_WEBHOOK_ID: secrets.token_hex()} self.hass.config_entries.async_update_entry(self.entry, data=data) - if self.hass.components.cloud.async_active_subscription(): + if cloud.async_active_subscription(self.hass): if CONF_CLOUDHOOK_URL not in self.entry.data: - webhook_url = await self.hass.components.cloud.async_create_cloudhook( - self.entry.data[CONF_WEBHOOK_ID] + webhook_url = await cloud.async_create_cloudhook( + self.hass, self.entry.data[CONF_WEBHOOK_ID] ) data = {**self.entry.data, CONF_CLOUDHOOK_URL: webhook_url} self.hass.config_entries.async_update_entry(self.entry, data=data) diff --git a/tests/components/smartthings/test_config_flow.py b/tests/components/smartthings/test_config_flow.py index 7f8950f18b5..420c07d2a04 100644 --- a/tests/components/smartthings/test_config_flow.py +++ b/tests/components/smartthings/test_config_flow.py @@ -355,9 +355,11 @@ async def test_entry_created_with_cloudhook( request.refresh_token = refresh_token with patch.object( - hass.components.cloud, "async_active_subscription", Mock(return_value=True) + smartapp.cloud, + "async_active_subscription", + Mock(return_value=True), ), patch.object( - hass.components.cloud, + smartapp.cloud, "async_create_cloudhook", AsyncMock(return_value="http://cloud.test"), ) as mock_create_cloudhook: