Update pypoint to use async http requests (#41546)

* Remove domain after entities

* Add support for async http-requests

* Fix, handle network issues (update _is_available status)

* Fix missing await for alarm_arm

* Fix alarm status

* Update tests to async

* Bump pypoint version

* Fix doc string

* Apply suggestions from code review, remove pylint disable

* Fix black
This commit is contained in:
Fredrik Erlandsson 2020-10-13 08:48:26 +02:00 committed by GitHub
parent 32204821c1
commit 8bdc824b6c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 49 additions and 46 deletions

View file

@ -75,22 +75,22 @@ async def async_setup(hass, config):
async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry):
"""Set up Point from a config entry."""
def token_saver(token):
_LOGGER.debug("Saving updated token")
async def token_saver(token, **kwargs):
_LOGGER.debug("Saving updated token %s", token)
hass.config_entries.async_update_entry(
entry, data={**entry.data, CONF_TOKEN: token}
)
# Force token update.
entry.data[CONF_TOKEN]["expires_in"] = -1
session = PointSession(
hass.helpers.aiohttp_client.async_get_clientsession(),
entry.data["refresh_args"][CONF_CLIENT_ID],
entry.data["refresh_args"][CONF_CLIENT_SECRET],
token=entry.data[CONF_TOKEN],
auto_refresh_kwargs=entry.data["refresh_args"],
token_saver=token_saver,
)
if not session.is_authorized:
try:
await session.ensure_active_token()
except Exception: # pylint: disable=broad-except
_LOGGER.error("Authentication Error")
return False
@ -120,8 +120,7 @@ async def async_setup_webhook(hass: HomeAssistantType, entry: ConfigEntry, sessi
CONF_WEBHOOK_URL: webhook_url,
},
)
await hass.async_add_executor_job(
session.update_webhook,
await session.update_webhook(
entry.data[CONF_WEBHOOK_URL],
entry.data[CONF_WEBHOOK_ID],
["*"],
@ -136,14 +135,14 @@ async def async_unload_entry(hass: HomeAssistantType, entry: ConfigEntry):
"""Unload a config entry."""
hass.components.webhook.async_unregister(entry.data[CONF_WEBHOOK_ID])
session = hass.data[DOMAIN].pop(entry.entry_id)
await hass.async_add_executor_job(session.remove_webhook)
if not hass.data[DOMAIN]:
hass.data.pop(DOMAIN)
await session.remove_webhook()
for component in ("binary_sensor", "sensor"):
await hass.config_entries.async_forward_entry_unload(entry, component)
if not hass.data[DOMAIN]:
hass.data.pop(DOMAIN)
return True
@ -181,12 +180,10 @@ class MinutPointClient:
async def _sync(self):
"""Update local list of devices."""
if (
not await self._hass.async_add_executor_job(self._client.update)
and self._is_available
):
if not await self._client.update() and self._is_available:
self._is_available = False
_LOGGER.warning("Device is unavailable")
async_dispatcher_send(self._hass, SIGNAL_UPDATE_ENTITY)
return
async def new_device(device_id, component):
@ -221,24 +218,26 @@ class MinutPointClient:
def is_available(self, device_id):
"""Return device availability."""
if not self._is_available:
return False
return device_id in self._client.device_ids
def remove_webhook(self):
async def remove_webhook(self):
"""Remove the session webhook."""
return self._client.remove_webhook()
return await self._client.remove_webhook()
@property
def homes(self):
"""Return known homes."""
return self._client.homes
def alarm_disarm(self, home_id):
async def async_alarm_disarm(self, home_id):
"""Send alarm disarm command."""
return self._client.alarm_disarm(home_id)
return await self._client.alarm_disarm(home_id)
def alarm_arm(self, home_id):
async def async_alarm_arm(self, home_id):
"""Send alarm arm command."""
return self._client.alarm_arm(home_id)
return await self._client.alarm_arm(home_id)
class MinutPointEntity(Entity):

View file

@ -99,15 +99,15 @@ class MinutPointAlarmControl(AlarmControlPanelEntity):
"""Return the user the last change was triggered by."""
return self._changed_by
def alarm_disarm(self, code=None):
async def async_alarm_disarm(self, code=None):
"""Send disarm command."""
status = self._client.alarm_disarm(self._home_id)
status = await self._client.async_alarm_disarm(self._home_id)
if status:
self._home["alarm_status"] = "off"
def alarm_arm_away(self, code=None):
async def async_alarm_arm_away(self, code=None):
"""Send arm away command."""
status = self._client.alarm_arm(self._home_id)
status = await self._client.async_alarm_arm(self._home_id)
if status:
self._home["alarm_status"] = "on"

View file

@ -102,7 +102,6 @@ class PointFlowHandler(config_entries.ConfigFlow):
except Exception: # pylint: disable=broad-except
_LOGGER.exception("Unexpected error generating auth url")
return self.async_abort(reason="authorize_url_fail")
return self.async_show_form(
step_id="auth",
description_placeholders={"authorization_url": url},
@ -111,11 +110,14 @@ class PointFlowHandler(config_entries.ConfigFlow):
async def _get_authorization_url(self):
"""Create Minut Point session and get authorization url."""
flow = self.hass.data[DATA_FLOW_IMPL][self.flow_impl]
client_id = flow[CONF_CLIENT_ID]
client_secret = flow[CONF_CLIENT_SECRET]
point_session = PointSession(client_id, client_secret=client_secret)
point_session = PointSession(
self.hass.helpers.aiohttp_client.async_get_clientsession(),
client_id,
client_secret,
)
self.hass.http.register_view(MinutAuthCallbackView())
@ -143,17 +145,19 @@ class PointFlowHandler(config_entries.ConfigFlow):
flow = self.hass.data[DATA_FLOW_IMPL][DOMAIN]
client_id = flow[CONF_CLIENT_ID]
client_secret = flow[CONF_CLIENT_SECRET]
point_session = PointSession(client_id, client_secret=client_secret)
token = await self.hass.async_add_executor_job(
point_session.get_access_token, code
point_session = PointSession(
self.hass.helpers.aiohttp_client.async_get_clientsession(),
client_id,
client_secret,
)
token = await point_session.get_access_token(code)
_LOGGER.debug("Got new token")
if not point_session.is_authorized:
_LOGGER.error("Authentication Error")
return self.async_abort(reason="auth_error")
_LOGGER.info("Successfully authenticated Point")
user_email = point_session.user().get("email") or ""
user_email = (await point_session.user()).get("email") or ""
return self.async_create_entry(
title=user_email,

View file

@ -3,7 +3,7 @@
"name": "Minut Point",
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/point",
"requirements": ["pypoint==1.1.2"],
"requirements": ["pypoint==2.0.0"],
"dependencies": ["webhook", "http"],
"codeowners": ["@fredrike"],
"quality_scale": "gold"

View file

@ -57,13 +57,11 @@ class MinutPointSensor(MinutPointEntity):
async def _update_callback(self):
"""Update the value of the sensor."""
_LOGGER.debug("Update sensor value for %s", self)
if self.is_updated:
_LOGGER.debug("Update sensor value for %s", self)
self._value = await self.hass.async_add_executor_job(
self.device.sensor, self.device_class
)
self._value = await self.device.sensor(self.device_class)
self._updated = parse_datetime(self.device.last_update)
self.async_write_ha_state()
self.async_write_ha_state()
@property
def icon(self):

View file

@ -1600,7 +1600,7 @@ pypck==0.7.2
pypjlink2==1.2.1
# homeassistant.components.point
pypoint==1.1.2
pypoint==2.0.0
# homeassistant.components.profiler
pyprof2calltree==1.4.5

View file

@ -783,7 +783,7 @@ pyowm==2.10.0
pyownet==0.10.0.post1
# homeassistant.components.point
pypoint==1.1.2
pypoint==2.0.0
# homeassistant.components.profiler
pyprof2calltree==1.4.5

View file

@ -33,11 +33,13 @@ def mock_pypoint(is_authorized): # pylint: disable=redefined-outer-name
with patch(
"homeassistant.components.point.config_flow.PointSession"
) as PointSession:
PointSession.return_value.get_access_token.return_value = {
"access_token": "boo"
}
PointSession.return_value.get_access_token = AsyncMock(
return_value={"access_token": "boo"}
)
PointSession.return_value.is_authorized = is_authorized
PointSession.return_value.user.return_value = {"email": "john.doe@example.com"}
PointSession.return_value.user = AsyncMock(
return_value={"email": "john.doe@example.com"}
)
yield PointSession