Bump yalexs to 2.0.0 (#111706)

Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
A Björck 2024-03-10 09:11:10 +01:00 committed by GitHub
parent ffcbab1c20
commit e631224372
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 133 additions and 22 deletions

View file

@ -306,6 +306,13 @@ class AugustData(AugustSubscriberMixin):
exc_info=err,
)
async def refresh_camera_by_id(self, device_id: str) -> None:
"""Re-fetch doorbell/camera data from API."""
await self._async_update_device_detail(
self._doorbells_by_id[device_id],
self._api.async_get_doorbell_detail,
)
async def _async_refresh_device_detail_by_id(self, device_id: str) -> None:
if device_id in self._locks_by_id:
if self.activity_stream and self.activity_stream.pubnub.connected:

View file

@ -2,9 +2,12 @@
from __future__ import annotations
import logging
from aiohttp import ClientSession
from yalexs.activity import ActivityType
from yalexs.doorbell import Doorbell
from yalexs.const import Brand
from yalexs.doorbell import ContentTokenExpired, Doorbell
from yalexs.util import update_doorbell_image_from_activity
from homeassistant.components.camera import Camera
@ -17,6 +20,8 @@ from . import AugustData
from .const import DEFAULT_NAME, DEFAULT_TIMEOUT, DOMAIN
from .entity import AugustEntityMixin
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry(
hass: HomeAssistant,
@ -48,6 +53,7 @@ class AugustCamera(AugustEntityMixin, Camera):
self._timeout = timeout
self._session = session
self._image_url = None
self._content_token = None
self._image_content = None
self._attr_unique_id = f"{self._device_id:s}_camera"
self._attr_motion_detection_enabled = True
@ -63,6 +69,12 @@ class AugustCamera(AugustEntityMixin, Camera):
"""Return the camera model."""
return self._detail.model
async def _async_update(self):
"""Update device."""
_LOGGER.debug("async_update called %s", self._detail.device_name)
await self._data.refresh_camera_by_id(self._device_id)
self._update_from_data()
@callback
def _update_from_data(self) -> None:
"""Get the latest state of the sensor."""
@ -70,7 +82,6 @@ class AugustCamera(AugustEntityMixin, Camera):
self._device_id,
{ActivityType.DOORBELL_MOTION, ActivityType.DOORBELL_IMAGE_CAPTURE},
)
if doorbell_activity is not None:
update_doorbell_image_from_activity(self._detail, doorbell_activity)
@ -82,7 +93,23 @@ class AugustCamera(AugustEntityMixin, Camera):
if self._image_url is not self._detail.image_url:
self._image_url = self._detail.image_url
self._image_content = await self._detail.async_get_doorbell_image(
self._session, timeout=self._timeout
self._content_token = self._detail.content_token or self._content_token
_LOGGER.debug(
"calling doorbell async_get_doorbell_image, %s",
self._detail.device_name,
)
try:
self._image_content = await self._detail.async_get_doorbell_image(
self._session, timeout=self._timeout
)
except ContentTokenExpired:
if self._data.brand == Brand.YALE_HOME:
_LOGGER.debug(
"Error fetching camera image, updating content-token from api to retry"
)
await self._async_update()
self._image_content = await self._detail.async_get_doorbell_image(
self._session, timeout=self._timeout
)
return self._image_content

View file

@ -28,5 +28,5 @@
"documentation": "https://www.home-assistant.io/integrations/august",
"iot_class": "cloud_push",
"loggers": ["pubnub", "yalexs"],
"requirements": ["yalexs==1.11.4", "yalexs-ble==2.4.2"]
"requirements": ["yalexs==2.0.0", "yalexs-ble==2.4.2"]
}

View file

@ -2893,7 +2893,7 @@ yalesmartalarmclient==0.3.9
yalexs-ble==2.4.2
# homeassistant.components.august
yalexs==1.11.4
yalexs==2.0.0
# homeassistant.components.yeelight
yeelight==0.7.14

View file

@ -2228,7 +2228,7 @@ yalesmartalarmclient==0.3.9
yalexs-ble==2.4.2
# homeassistant.components.august
yalexs==1.11.4
yalexs==2.0.0
# homeassistant.components.yeelight
yeelight==0.7.14

View file

@ -26,11 +26,12 @@ from yalexs.activity import (
LockOperationActivity,
)
from yalexs.authenticator import AuthenticationState
from yalexs.const import Brand
from yalexs.doorbell import Doorbell, DoorbellDetail
from yalexs.lock import Lock, LockDetail
from yalexs.pubnub_async import AugustPubNub
from homeassistant.components.august.const import CONF_LOGIN_METHOD, DOMAIN
from homeassistant.components.august.const import CONF_BRAND, CONF_LOGIN_METHOD, DOMAIN
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
from homeassistant.core import HomeAssistant
@ -38,13 +39,14 @@ from homeassistant.core import HomeAssistant
from tests.common import MockConfigEntry, load_fixture
def _mock_get_config():
def _mock_get_config(brand: Brand = Brand.AUGUST):
"""Return a default august config."""
return {
DOMAIN: {
CONF_LOGIN_METHOD: "email",
CONF_USERNAME: "mocked_username",
CONF_PASSWORD: "mocked_password",
CONF_BRAND: brand,
}
}
@ -59,7 +61,7 @@ def _mock_authenticator(auth_state):
@patch("homeassistant.components.august.gateway.ApiAsync")
@patch("homeassistant.components.august.gateway.AuthenticatorAsync.async_authenticate")
async def _mock_setup_august(
hass, api_instance, pubnub_mock, authenticate_mock, api_mock
hass, api_instance, pubnub_mock, authenticate_mock, api_mock, brand
):
"""Set up august integration."""
authenticate_mock.side_effect = MagicMock(
@ -70,7 +72,7 @@ async def _mock_setup_august(
api_mock.return_value = api_instance
entry = MockConfigEntry(
domain=DOMAIN,
data=_mock_get_config()[DOMAIN],
data=_mock_get_config(brand)[DOMAIN],
options={},
)
entry.add_to_hass(hass)
@ -88,21 +90,26 @@ async def _create_august_with_devices(
api_call_side_effects: dict[str, Any] | None = None,
activities: list[Any] | None = None,
pubnub: AugustPubNub | None = None,
brand: Brand = Brand.AUGUST,
) -> ConfigEntry:
entry, _ = await _create_august_api_with_devices(
hass, devices, api_call_side_effects, activities, pubnub
hass, devices, api_call_side_effects, activities, pubnub, brand
)
return entry
async def _create_august_api_with_devices( # noqa: C901
hass, devices, api_call_side_effects=None, activities=None, pubnub=None
hass,
devices,
api_call_side_effects=None,
activities=None,
pubnub=None,
brand=Brand.AUGUST,
):
if api_call_side_effects is None:
api_call_side_effects = {}
if pubnub is None:
pubnub = AugustPubNub()
device_data = {"doorbells": [], "locks": []}
for device in devices:
if isinstance(device, LockDetail):
@ -111,7 +118,13 @@ async def _create_august_api_with_devices( # noqa: C901
)
elif isinstance(device, DoorbellDetail):
device_data["doorbells"].append(
{"base": _mock_august_doorbell(device.device_id), "detail": device}
{
"base": _mock_august_doorbell(
deviceid=device.device_id,
brand=device._data.get("brand", Brand.AUGUST),
),
"detail": device,
}
)
else:
raise ValueError # noqa: TRY004
@ -182,7 +195,7 @@ async def _create_august_api_with_devices( # noqa: C901
)
api_instance, entry = await _mock_setup_august_with_api_side_effects(
hass, api_call_side_effects, pubnub
hass, api_call_side_effects, pubnub, brand
)
if device_data["locks"]:
@ -193,7 +206,9 @@ async def _create_august_api_with_devices( # noqa: C901
return entry, api_instance
async def _mock_setup_august_with_api_side_effects(hass, api_call_side_effects, pubnub):
async def _mock_setup_august_with_api_side_effects(
hass, api_call_side_effects, pubnub, brand=Brand.AUGUST
):
api_instance = MagicMock(name="Api")
if api_call_side_effects["get_lock_detail"]:
@ -236,7 +251,9 @@ async def _mock_setup_august_with_api_side_effects(hass, api_call_side_effects,
api_instance.async_status_async = AsyncMock()
api_instance.async_get_user = AsyncMock(return_value={"UserID": "abc"})
return api_instance, await _mock_setup_august(hass, api_instance, pubnub)
return api_instance, await _mock_setup_august(
hass, api_instance, pubnub, brand=brand
)
def _mock_august_authentication(token_text, token_timestamp, state):
@ -253,13 +270,18 @@ def _mock_august_lock(lockid="mocklockid1", houseid="mockhouseid1"):
return Lock(lockid, _mock_august_lock_data(lockid=lockid, houseid=houseid))
def _mock_august_doorbell(deviceid="mockdeviceid1", houseid="mockhouseid1"):
def _mock_august_doorbell(
deviceid="mockdeviceid1", houseid="mockhouseid1", brand=Brand.AUGUST
):
return Doorbell(
deviceid, _mock_august_doorbell_data(deviceid=deviceid, houseid=houseid)
deviceid,
_mock_august_doorbell_data(deviceid=deviceid, houseid=houseid, brand=brand),
)
def _mock_august_doorbell_data(deviceid="mockdeviceid1", houseid="mockhouseid1"):
def _mock_august_doorbell_data(
deviceid="mockdeviceid1", houseid="mockhouseid1", brand=Brand.AUGUST
):
return {
"_id": deviceid,
"DeviceID": deviceid,

View file

@ -3,6 +3,9 @@
from http import HTTPStatus
from unittest.mock import patch
from yalexs.const import Brand
from yalexs.doorbell import ContentTokenExpired
from homeassistant.const import STATE_IDLE
from homeassistant.core import HomeAssistant
@ -20,7 +23,7 @@ async def test_create_doorbell(
with patch.object(
doorbell_one, "async_get_doorbell_image", create=False, return_value="image"
):
await _create_august_with_devices(hass, [doorbell_one])
await _create_august_with_devices(hass, [doorbell_one], brand=Brand.AUGUST)
camera_k98gidt45gul_name_camera = hass.states.get(
"camera.k98gidt45gul_name_camera"
@ -36,3 +39,55 @@ async def test_create_doorbell(
assert resp.status == HTTPStatus.OK
body = await resp.text()
assert body == "image"
async def test_doorbell_refresh_content_token_recover(
hass: HomeAssistant, hass_client_no_auth: ClientSessionGenerator
) -> None:
"""Test camera image content token expired."""
doorbell_two = await _mock_doorbell_from_fixture(hass, "get_doorbell.json")
with patch.object(
doorbell_two,
"async_get_doorbell_image",
create=False,
side_effect=[ContentTokenExpired, "image"],
):
await _create_august_with_devices(
hass,
[doorbell_two],
brand=Brand.YALE_HOME,
)
url = hass.states.get("camera.k98gidt45gul_name_camera").attributes[
"entity_picture"
]
client = await hass_client_no_auth()
resp = await client.get(url)
assert resp.status == HTTPStatus.OK
body = await resp.text()
assert body == "image"
async def test_doorbell_refresh_content_token_fail(
hass: HomeAssistant, hass_client_no_auth: ClientSessionGenerator
) -> None:
"""Test camera image content token expired."""
doorbell_two = await _mock_doorbell_from_fixture(hass, "get_doorbell.json")
with patch.object(
doorbell_two,
"async_get_doorbell_image",
create=False,
side_effect=ContentTokenExpired,
):
await _create_august_with_devices(
hass,
[doorbell_two],
brand=Brand.YALE_HOME,
)
url = hass.states.get("camera.k98gidt45gul_name_camera").attributes[
"entity_picture"
]
client = await hass_client_no_auth()
resp = await client.get(url)
assert resp.status == HTTPStatus.INTERNAL_SERVER_ERROR