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 partpull/94158/head
parent
2a99fea1de
commit
4f00cc9faa
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -36,6 +36,12 @@
|
|||
"totalItemCount": 6178,
|
||||
"newItemCount": 0,
|
||||
"activityType": "all"
|
||||
},
|
||||
"statistics": {
|
||||
"viewCount": "214141263",
|
||||
"subscriberCount": "2290000",
|
||||
"hiddenSubscriberCount": false,
|
||||
"videoCount": "5798"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
@ -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 what’s 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
|
||||
}
|
||||
}
|
|
@ -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:
|
||||
|
|
Loading…
Reference in New Issue