Show the sensor state using the coordinatordata instead of initial data (#94008)

* Show the sensor state using the coordinatordata instead of initial data

* Add test

* Remove part
pull/94158/head
Joost Lekkerkerker 2023-06-03 20:35:57 +02:00 committed by Paulus Schoutsen
parent 2a99fea1de
commit 4f00cc9faa
6 changed files with 298 additions and 23 deletions

View File

@ -1,9 +1,6 @@
"""Entity representing a YouTube account."""
from __future__ import annotations
from typing import Any
from homeassistant.const import ATTR_ID
from homeassistant.helpers.device_registry import DeviceEntryType
from homeassistant.helpers.entity import DeviceInfo, EntityDescription
from homeassistant.helpers.update_coordinator import CoordinatorEntity
@ -21,20 +18,18 @@ class YouTubeChannelEntity(CoordinatorEntity):
self,
coordinator: YouTubeDataUpdateCoordinator,
description: EntityDescription,
channel: dict[str, Any],
channel_id: str,
) -> None:
"""Initialize a Google Mail entity."""
"""Initialize a YouTube entity."""
super().__init__(coordinator)
self.entity_description = description
self._attr_unique_id = (
f"{coordinator.config_entry.entry_id}_{channel[ATTR_ID]}_{description.key}"
f"{coordinator.config_entry.entry_id}_{channel_id}_{description.key}"
)
self._channel_id = channel_id
self._attr_device_info = DeviceInfo(
entry_type=DeviceEntryType.SERVICE,
identifiers={
(DOMAIN, f"{coordinator.config_entry.entry_id}_{channel[ATTR_ID]}")
},
identifiers={(DOMAIN, f"{coordinator.config_entry.entry_id}_{channel_id}")},
manufacturer=MANUFACTURER,
name=channel[ATTR_TITLE],
name=coordinator.data[channel_id][ATTR_TITLE],
)
self._channel = channel

View File

@ -70,8 +70,8 @@ async def async_setup_entry(
COORDINATOR
]
async_add_entities(
YouTubeSensor(coordinator, sensor_type, channel)
for channel in coordinator.data.values()
YouTubeSensor(coordinator, sensor_type, channel_id)
for channel_id in coordinator.data
for sensor_type in SENSOR_TYPES
)
@ -84,16 +84,20 @@ class YouTubeSensor(YouTubeChannelEntity, SensorEntity):
@property
def native_value(self) -> StateType:
"""Return the value reported by the sensor."""
return self.entity_description.value_fn(self._channel)
return self.entity_description.value_fn(self.coordinator.data[self._channel_id])
@property
def entity_picture(self) -> str:
"""Return the value reported by the sensor."""
return self.entity_description.entity_picture_fn(self._channel)
return self.entity_description.entity_picture_fn(
self.coordinator.data[self._channel_id]
)
@property
def extra_state_attributes(self) -> dict[str, Any] | None:
"""Return the extra state attributes."""
if self.entity_description.attributes_fn:
return self.entity_description.attributes_fn(self._channel)
return self.entity_description.attributes_fn(
self.coordinator.data[self._channel_id]
)
return None

View File

@ -20,6 +20,10 @@ class MockRequest:
class MockChannels:
"""Mock object for channels."""
def __init__(self, fixture: str):
"""Initialize mock channels."""
self._fixture = fixture
def list(
self,
part: str,
@ -28,12 +32,16 @@ class MockChannels:
maxResults: int | None = None,
) -> MockRequest:
"""Return a fixture."""
return MockRequest(fixture="youtube/get_channel.json")
return MockRequest(fixture=self._fixture)
class MockPlaylistItems:
"""Mock object for playlist items."""
def __init__(self, fixture: str):
"""Initialize mock playlist items."""
self._fixture = fixture
def list(
self,
part: str,
@ -41,28 +49,43 @@ class MockPlaylistItems:
maxResults: int | None = None,
) -> MockRequest:
"""Return a fixture."""
return MockRequest(fixture="youtube/get_playlist_items.json")
return MockRequest(fixture=self._fixture)
class MockSubscriptions:
"""Mock object for subscriptions."""
def __init__(self, fixture: str):
"""Initialize mock subscriptions."""
self._fixture = fixture
def list(self, part: str, mine: bool, maxResults: int | None = None) -> MockRequest:
"""Return a fixture."""
return MockRequest(fixture="youtube/get_subscriptions.json")
return MockRequest(fixture=self._fixture)
class MockService:
"""Service which returns mock objects."""
def __init__(
self,
channel_fixture: str = "youtube/get_channel.json",
playlist_items_fixture: str = "youtube/get_playlist_items.json",
subscriptions_fixture: str = "youtube/get_subscriptions.json",
):
"""Initialize mock service."""
self._channel_fixture = channel_fixture
self._playlist_items_fixture = playlist_items_fixture
self._subscriptions_fixture = subscriptions_fixture
def channels(self) -> MockChannels:
"""Return a mock object."""
return MockChannels()
return MockChannels(self._channel_fixture)
def playlistItems(self) -> MockPlaylistItems:
"""Return a mock object."""
return MockPlaylistItems()
return MockPlaylistItems(self._playlist_items_fixture)
def subscriptions(self) -> MockSubscriptions:
"""Return a mock object."""
return MockSubscriptions()
return MockSubscriptions(self._subscriptions_fixture)

View File

@ -36,6 +36,12 @@
"totalItemCount": 6178,
"newItemCount": 0,
"activityType": "all"
},
"statistics": {
"viewCount": "214141263",
"subscriberCount": "2290000",
"hiddenSubscriberCount": false,
"videoCount": "5798"
}
}
]

View File

@ -0,0 +1,215 @@
{
"kind": "youtube#playlistItemListResponse",
"etag": "O0Ah8Wd5pUD2Gsv-n0A42RDRcX8",
"nextPageToken": "EAAaBlBUOkNBVQ",
"items": [
{
"kind": "youtube#playlistItem",
"etag": "pU0v49jXONlQfIJEX7ldINttRYM",
"id": "VVVfeDVYRzFPVjJQNnVaWjVGU005VHR3LmhsZUxsY0h3UUxN",
"snippet": {
"publishedAt": "2023-05-10T22:30:48Z",
"channelId": "UC_x5XG1OV2P6uZZ5FSM9Ttw",
"title": "Google I/O 2023 Developer Keynote in 5 minutes",
"description": "Discover whats new from Google, including top takeaways and highlights announced at Google I/O 2023. From deep investments in the largest mobile platform, to breakthroughs in AI, learn about the latest capabilities in mobile, web, Cloud, AI, and more. \n\nCatch the full Developer Keynote →https://goo.gle/dev-keynote-23 \nWatch all the Keynotes from Google I/O 2023→ https://goo.gle/IO23_keynotes\nWatch all the Google I/O 2023 Sessions → https://goo.gle/IO23_all \n\n0:00 - Welcome\n0:25 - MakerSuite\n0:49 - Android Studio Bot\n1:38 - Large screens\n2:04 - Wear OS\n2:34 - WebGPU\n2:58 - Baseline\n3:27 - MediaPipe\n3:57 - Duet AI for Google Cloud\n4:59 - Closing\n\nSubscribe to Google Developers → https://goo.gle/developers\n\n#GoogleIO #developers",
"thumbnails": {
"default": {
"url": "https://i.ytimg.com/vi/hleLlcHwQLM/default.jpg",
"width": 120,
"height": 90
},
"medium": {
"url": "https://i.ytimg.com/vi/hleLlcHwQLM/mqdefault.jpg",
"width": 320,
"height": 180
},
"high": {
"url": "https://i.ytimg.com/vi/hleLlcHwQLM/hqdefault.jpg",
"width": 480,
"height": 360
},
"standard": {
"url": "https://i.ytimg.com/vi/hleLlcHwQLM/sddefault.jpg",
"width": 640,
"height": 480
},
"maxres": {
"url": "https://i.ytimg.com/vi/hleLlcHwQLM/maxresdefault.jpg",
"width": 1280,
"height": 720
}
},
"channelTitle": "Google for Developers",
"playlistId": "UU_x5XG1OV2P6uZZ5FSM9Ttw",
"position": 1,
"resourceId": {
"kind": "youtube#video",
"videoId": "hleLlcHwQLM"
},
"videoOwnerChannelTitle": "Google for Developers",
"videoOwnerChannelId": "UC_x5XG1OV2P6uZZ5FSM9Ttw"
},
"contentDetails": {
"videoId": "hleLlcHwQLM",
"videoPublishedAt": "2023-05-10T22:30:48Z"
}
},
{
"kind": "youtube#playlistItem",
"etag": "fht9mKDuIBXcO75k21ZB_gC_4vM",
"id": "VVVfeDVYRzFPVjJQNnVaWjVGU005VHR3LmxNS2p0U0Z1amN3",
"snippet": {
"publishedAt": "2023-05-10T21:25:47Z",
"channelId": "UC_x5XG1OV2P6uZZ5FSM9Ttw",
"title": "What's new in Google Pay and Wallet in less than 1 minute",
"description": "A quick recap on the latest updates to Google Pay and Wallet from Google I/O 2023.\n\nTo learn more about what's new in Google Pay and Wallet, check out the keynote → https://goo.gle/IO23_paywallet\n\nSubscribe to Google Developers → https://goo.gle/developers\n\n#GoogleIO",
"thumbnails": {
"default": {
"url": "https://i.ytimg.com/vi/lMKjtSFujcw/default.jpg",
"width": 120,
"height": 90
},
"medium": {
"url": "https://i.ytimg.com/vi/lMKjtSFujcw/mqdefault.jpg",
"width": 320,
"height": 180
},
"high": {
"url": "https://i.ytimg.com/vi/lMKjtSFujcw/hqdefault.jpg",
"width": 480,
"height": 360
},
"standard": {
"url": "https://i.ytimg.com/vi/lMKjtSFujcw/sddefault.jpg",
"width": 640,
"height": 480
},
"maxres": {
"url": "https://i.ytimg.com/vi/lMKjtSFujcw/maxresdefault.jpg",
"width": 1280,
"height": 720
}
},
"channelTitle": "Google for Developers",
"playlistId": "UU_x5XG1OV2P6uZZ5FSM9Ttw",
"position": 2,
"resourceId": {
"kind": "youtube#video",
"videoId": "lMKjtSFujcw"
},
"videoOwnerChannelTitle": "Google for Developers",
"videoOwnerChannelId": "UC_x5XG1OV2P6uZZ5FSM9Ttw"
},
"contentDetails": {
"videoId": "lMKjtSFujcw",
"videoPublishedAt": "2023-05-10T21:25:47Z"
}
},
{
"kind": "youtube#playlistItem",
"etag": "nYKXoKd8eePAZ_xFa3dL5ZmvM5c",
"id": "VVVfeDVYRzFPVjJQNnVaWjVGU005VHR3LmMwbXFCdVhQcnBB",
"snippet": {
"publishedAt": "2023-05-10T20:47:57Z",
"channelId": "UC_x5XG1OV2P6uZZ5FSM9Ttw",
"title": "Developers guide to BigQuery export for Google Analytics 4",
"description": "With Google Analytics 4 (GA4), anyone can set up export of granular measurement data to BigQuery.\n\nIn this session, you will learn how to use the BigQuery export for solving business problems, doing complex reporting, implementing advanced use cases with ML models, and creating custom audiences by joining with first-party data. You can use this framework for detailed or large-scale data analysis. We will also share some best practices to get you started.\n\nResources:\nDevelopers guide to BigQuery export for Google Analytics 4 → https://goo.gle/ga-io23\n\nSpeaker: Minhaz Kazi\n\nWatch more:\nWatch all the Technical Sessions from Google I/O 2023 → https://goo.gle/IO23_sessions\nWatch more Mobile Sessions → https://goo.gle/IO23_mobile\nWatch more Web Sessions → https://goo.gle/IO23_web\nAll Google I/O 2023 Sessions → https://goo.gle/IO23_all\n\nSubscribe to Google Developers → https://goo.gle/developers\n\n#GoogleIO",
"thumbnails": {
"default": {
"url": "https://i.ytimg.com/vi/c0mqBuXPrpA/default.jpg",
"width": 120,
"height": 90
},
"medium": {
"url": "https://i.ytimg.com/vi/c0mqBuXPrpA/mqdefault.jpg",
"width": 320,
"height": 180
},
"high": {
"url": "https://i.ytimg.com/vi/c0mqBuXPrpA/hqdefault.jpg",
"width": 480,
"height": 360
},
"standard": {
"url": "https://i.ytimg.com/vi/c0mqBuXPrpA/sddefault.jpg",
"width": 640,
"height": 480
},
"maxres": {
"url": "https://i.ytimg.com/vi/c0mqBuXPrpA/maxresdefault.jpg",
"width": 1280,
"height": 720
}
},
"channelTitle": "Google for Developers",
"playlistId": "UU_x5XG1OV2P6uZZ5FSM9Ttw",
"position": 3,
"resourceId": {
"kind": "youtube#video",
"videoId": "c0mqBuXPrpA"
},
"videoOwnerChannelTitle": "Google for Developers",
"videoOwnerChannelId": "UC_x5XG1OV2P6uZZ5FSM9Ttw"
},
"contentDetails": {
"videoId": "c0mqBuXPrpA",
"videoPublishedAt": "2023-05-10T20:47:57Z"
}
},
{
"kind": "youtube#playlistItem",
"etag": "--gb8pSHDwp9c-fyjhZ0K2DklLE",
"id": "VVVfeDVYRzFPVjJQNnVaWjVGU005VHR3Ll9uOXh3dVRPUmFz",
"snippet": {
"publishedAt": "2023-05-10T20:46:29Z",
"channelId": "UC_x5XG1OV2P6uZZ5FSM9Ttw",
"title": "What's new in Google Home - American Sign Language",
"description": "To watch this Session without American Sign Language (ASL) interpretation, please click here → https://goo.gle/IO23_homekey\n\nDiscover how your connected devices can do more with Google Home using Matter and Automations.\n\nResources:\nGoogle Home Developer Center → https://goo.gle/3KcD5xr\n\nDiscover how your connected devices can do more with Google Home using Matter and Automations\nGoogle Home APIs Developer Preview → https://goo.gle/3UakRl0\nAutomations Developer Preview → https://goo.gle/3KgEcMy\n\nSpeakers: Taylor Lehman, Indu Ramamurthi\n\nWatch more:\nWatch more Mobile Sessions → https://goo.gle/IO23_mobile\nAll Google I/O 2023 Sessions → https://goo.gle/IO23_all\n\nSubscribe to Google Developers → https://goo.gle/developers\n\n#GoogleIO",
"thumbnails": {
"default": {
"url": "https://i.ytimg.com/vi/_n9xwuTORas/default.jpg",
"width": 120,
"height": 90
},
"medium": {
"url": "https://i.ytimg.com/vi/_n9xwuTORas/mqdefault.jpg",
"width": 320,
"height": 180
},
"high": {
"url": "https://i.ytimg.com/vi/_n9xwuTORas/hqdefault.jpg",
"width": 480,
"height": 360
},
"standard": {
"url": "https://i.ytimg.com/vi/_n9xwuTORas/sddefault.jpg",
"width": 640,
"height": 480
},
"maxres": {
"url": "https://i.ytimg.com/vi/_n9xwuTORas/maxresdefault.jpg",
"width": 1280,
"height": 720
}
},
"channelTitle": "Google for Developers",
"playlistId": "UU_x5XG1OV2P6uZZ5FSM9Ttw",
"position": 4,
"resourceId": {
"kind": "youtube#video",
"videoId": "_n9xwuTORas"
},
"videoOwnerChannelTitle": "Google for Developers",
"videoOwnerChannelId": "UC_x5XG1OV2P6uZZ5FSM9Ttw"
},
"contentDetails": {
"videoId": "_n9xwuTORas",
"videoPublishedAt": "2023-05-10T20:46:29Z"
}
}
],
"pageInfo": {
"totalResults": 5798,
"resultsPerPage": 5
}
}

View File

@ -9,9 +9,11 @@ from homeassistant.components.youtube import DOMAIN
from homeassistant.core import HomeAssistant
from homeassistant.util import dt as dt_util
from ...common import async_fire_time_changed
from . import MockService
from .conftest import TOKEN, ComponentSetup
from tests.common import async_fire_time_changed
async def test_sensor(hass: HomeAssistant, setup_integration: ComponentSetup) -> None:
"""Test sensor."""
@ -37,6 +39,36 @@ async def test_sensor(hass: HomeAssistant, setup_integration: ComponentSetup) ->
)
async def test_sensor_updating(
hass: HomeAssistant, setup_integration: ComponentSetup
) -> None:
"""Test updating sensor."""
await setup_integration()
state = hass.states.get("sensor.google_for_developers_latest_upload")
assert state
assert state.attributes["video_id"] == "wysukDrMdqU"
with patch(
"homeassistant.components.youtube.api.build",
return_value=MockService(
playlist_items_fixture="youtube/get_playlist_items_2.json"
),
):
future = dt_util.utcnow() + timedelta(minutes=15)
async_fire_time_changed(hass, future)
await hass.async_block_till_done()
state = hass.states.get("sensor.google_for_developers_latest_upload")
assert state
assert state.name == "Google for Developers Latest upload"
assert state.state == "Google I/O 2023 Developer Keynote in 5 minutes"
assert (
state.attributes["entity_picture"]
== "https://i.ytimg.com/vi/hleLlcHwQLM/sddefault.jpg"
)
assert state.attributes["video_id"] == "hleLlcHwQLM"
async def test_sensor_reauth_trigger(
hass: HomeAssistant, setup_integration: ComponentSetup
) -> None: