mirror of
https://github.com/home-assistant/core.git
synced 2025-07-29 16:17:20 +00:00
Add "Albums" sensor to Lidarr (#125631)
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
This commit is contained in:
parent
c1f612dce1
commit
50161670ce
@ -16,6 +16,7 @@ from homeassistant.helpers.device_registry import DeviceEntryType
|
|||||||
|
|
||||||
from .const import DEFAULT_NAME, DOMAIN
|
from .const import DEFAULT_NAME, DOMAIN
|
||||||
from .coordinator import (
|
from .coordinator import (
|
||||||
|
AlbumsDataUpdateCoordinator,
|
||||||
DiskSpaceDataUpdateCoordinator,
|
DiskSpaceDataUpdateCoordinator,
|
||||||
QueueDataUpdateCoordinator,
|
QueueDataUpdateCoordinator,
|
||||||
StatusDataUpdateCoordinator,
|
StatusDataUpdateCoordinator,
|
||||||
@ -35,6 +36,7 @@ class LidarrData:
|
|||||||
queue: QueueDataUpdateCoordinator
|
queue: QueueDataUpdateCoordinator
|
||||||
status: StatusDataUpdateCoordinator
|
status: StatusDataUpdateCoordinator
|
||||||
wanted: WantedDataUpdateCoordinator
|
wanted: WantedDataUpdateCoordinator
|
||||||
|
albums: AlbumsDataUpdateCoordinator
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(hass: HomeAssistant, entry: LidarrConfigEntry) -> bool:
|
async def async_setup_entry(hass: HomeAssistant, entry: LidarrConfigEntry) -> bool:
|
||||||
@ -54,6 +56,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: LidarrConfigEntry) -> bo
|
|||||||
queue=QueueDataUpdateCoordinator(hass, host_configuration, lidarr),
|
queue=QueueDataUpdateCoordinator(hass, host_configuration, lidarr),
|
||||||
status=StatusDataUpdateCoordinator(hass, host_configuration, lidarr),
|
status=StatusDataUpdateCoordinator(hass, host_configuration, lidarr),
|
||||||
wanted=WantedDataUpdateCoordinator(hass, host_configuration, lidarr),
|
wanted=WantedDataUpdateCoordinator(hass, host_configuration, lidarr),
|
||||||
|
albums=AlbumsDataUpdateCoordinator(hass, host_configuration, lidarr),
|
||||||
)
|
)
|
||||||
for field in fields(data):
|
for field in fields(data):
|
||||||
coordinator = getattr(data, field.name)
|
coordinator = getattr(data, field.name)
|
||||||
|
@ -17,7 +17,7 @@ from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, Upda
|
|||||||
|
|
||||||
from .const import DEFAULT_MAX_RECORDS, DOMAIN, LOGGER
|
from .const import DEFAULT_MAX_RECORDS, DOMAIN, LOGGER
|
||||||
|
|
||||||
T = TypeVar("T", bound=list[LidarrRootFolder] | LidarrQueue | str | LidarrAlbum)
|
T = TypeVar("T", bound=list[LidarrRootFolder] | LidarrQueue | str | LidarrAlbum | int)
|
||||||
|
|
||||||
|
|
||||||
class LidarrDataUpdateCoordinator(DataUpdateCoordinator[T], Generic[T], ABC):
|
class LidarrDataUpdateCoordinator(DataUpdateCoordinator[T], Generic[T], ABC):
|
||||||
@ -96,3 +96,11 @@ class WantedDataUpdateCoordinator(LidarrDataUpdateCoordinator[LidarrAlbum]):
|
|||||||
LidarrAlbum,
|
LidarrAlbum,
|
||||||
await self.api_client.async_get_wanted(page_size=DEFAULT_MAX_RECORDS),
|
await self.api_client.async_get_wanted(page_size=DEFAULT_MAX_RECORDS),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class AlbumsDataUpdateCoordinator(LidarrDataUpdateCoordinator[int]):
|
||||||
|
"""Albums update coordinator."""
|
||||||
|
|
||||||
|
async def _fetch_data(self) -> int:
|
||||||
|
"""Fetch the album data."""
|
||||||
|
return len(cast(list[LidarrAlbum], await self.api_client.async_get_albums()))
|
||||||
|
@ -85,7 +85,7 @@ SENSOR_TYPES: dict[str, LidarrSensorEntityDescription[Any]] = {
|
|||||||
"queue": LidarrSensorEntityDescription[LidarrQueue](
|
"queue": LidarrSensorEntityDescription[LidarrQueue](
|
||||||
key="queue",
|
key="queue",
|
||||||
translation_key="queue",
|
translation_key="queue",
|
||||||
native_unit_of_measurement="Albums",
|
native_unit_of_measurement="albums",
|
||||||
value_fn=lambda data, _: data.totalRecords,
|
value_fn=lambda data, _: data.totalRecords,
|
||||||
state_class=SensorStateClass.TOTAL,
|
state_class=SensorStateClass.TOTAL,
|
||||||
attributes_fn=lambda data: {i.title: queue_str(i) for i in data.records},
|
attributes_fn=lambda data: {i.title: queue_str(i) for i in data.records},
|
||||||
@ -93,7 +93,7 @@ SENSOR_TYPES: dict[str, LidarrSensorEntityDescription[Any]] = {
|
|||||||
"wanted": LidarrSensorEntityDescription[LidarrQueue](
|
"wanted": LidarrSensorEntityDescription[LidarrQueue](
|
||||||
key="wanted",
|
key="wanted",
|
||||||
translation_key="wanted",
|
translation_key="wanted",
|
||||||
native_unit_of_measurement="Albums",
|
native_unit_of_measurement="albums",
|
||||||
value_fn=lambda data, _: data.totalRecords,
|
value_fn=lambda data, _: data.totalRecords,
|
||||||
state_class=SensorStateClass.TOTAL,
|
state_class=SensorStateClass.TOTAL,
|
||||||
entity_registry_enabled_default=False,
|
entity_registry_enabled_default=False,
|
||||||
@ -101,6 +101,14 @@ SENSOR_TYPES: dict[str, LidarrSensorEntityDescription[Any]] = {
|
|||||||
album.title: album.artist.artistName for album in data.records
|
album.title: album.artist.artistName for album in data.records
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
"albums": LidarrSensorEntityDescription[int](
|
||||||
|
key="albums",
|
||||||
|
translation_key="albums",
|
||||||
|
native_unit_of_measurement="albums",
|
||||||
|
value_fn=lambda data, _: data,
|
||||||
|
state_class=SensorStateClass.TOTAL,
|
||||||
|
entity_registry_enabled_default=False,
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -39,6 +39,9 @@
|
|||||||
},
|
},
|
||||||
"wanted": {
|
"wanted": {
|
||||||
"name": "Wanted"
|
"name": "Wanted"
|
||||||
|
},
|
||||||
|
"albums": {
|
||||||
|
"name": "Albums"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -44,10 +44,12 @@ def mock_error(
|
|||||||
aioclient_mock.get(f"{API_URL}/rootfolder", status=status)
|
aioclient_mock.get(f"{API_URL}/rootfolder", status=status)
|
||||||
aioclient_mock.get(f"{API_URL}/system/status", status=status)
|
aioclient_mock.get(f"{API_URL}/system/status", status=status)
|
||||||
aioclient_mock.get(f"{API_URL}/wanted/missing", status=status)
|
aioclient_mock.get(f"{API_URL}/wanted/missing", status=status)
|
||||||
|
aioclient_mock.get(f"{API_URL}/album", status=status)
|
||||||
aioclient_mock.get(f"{API_URL}/queue", exc=ClientError)
|
aioclient_mock.get(f"{API_URL}/queue", exc=ClientError)
|
||||||
aioclient_mock.get(f"{API_URL}/rootfolder", exc=ClientError)
|
aioclient_mock.get(f"{API_URL}/rootfolder", exc=ClientError)
|
||||||
aioclient_mock.get(f"{API_URL}/system/status", exc=ClientError)
|
aioclient_mock.get(f"{API_URL}/system/status", exc=ClientError)
|
||||||
aioclient_mock.get(f"{API_URL}/wanted/missing", exc=ClientError)
|
aioclient_mock.get(f"{API_URL}/wanted/missing", exc=ClientError)
|
||||||
|
aioclient_mock.get(f"{API_URL}/album", exc=ClientError)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
@ -115,6 +117,11 @@ def mock_connection(aioclient_mock: AiohttpClientMocker) -> None:
|
|||||||
text=load_fixture("lidarr/wanted-missing.json"),
|
text=load_fixture("lidarr/wanted-missing.json"),
|
||||||
headers={"Content-Type": CONTENT_TYPE_JSON},
|
headers={"Content-Type": CONTENT_TYPE_JSON},
|
||||||
)
|
)
|
||||||
|
aioclient_mock.get(
|
||||||
|
f"{API_URL}/album",
|
||||||
|
text=load_fixture("lidarr/album.json"),
|
||||||
|
headers={"Content-Type": CONTENT_TYPE_JSON},
|
||||||
|
)
|
||||||
aioclient_mock.get(
|
aioclient_mock.get(
|
||||||
f"{API_URL}/rootfolder",
|
f"{API_URL}/rootfolder",
|
||||||
text=load_fixture("lidarr/rootfolder-linux.json"),
|
text=load_fixture("lidarr/rootfolder-linux.json"),
|
||||||
|
155
tests/components/lidarr/fixtures/album.json
Normal file
155
tests/components/lidarr/fixtures/album.json
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"id": 0,
|
||||||
|
"title": "string",
|
||||||
|
"disambiguation": "string",
|
||||||
|
"overview": "string",
|
||||||
|
"artistId": 0,
|
||||||
|
"foreignAlbumId": "string",
|
||||||
|
"monitored": true,
|
||||||
|
"anyReleaseOk": true,
|
||||||
|
"profileId": 0,
|
||||||
|
"duration": 0,
|
||||||
|
"albumType": "string",
|
||||||
|
"secondaryTypes": ["string"],
|
||||||
|
"mediumCount": 0,
|
||||||
|
"ratings": {
|
||||||
|
"votes": 0,
|
||||||
|
"value": 0
|
||||||
|
},
|
||||||
|
"releaseDate": "2024-09-09T20:16:28.493Z",
|
||||||
|
"releases": [
|
||||||
|
{
|
||||||
|
"id": 0,
|
||||||
|
"albumId": 0,
|
||||||
|
"foreignReleaseId": "string",
|
||||||
|
"title": "string",
|
||||||
|
"status": "string",
|
||||||
|
"duration": 0,
|
||||||
|
"trackCount": 0,
|
||||||
|
"media": [
|
||||||
|
{
|
||||||
|
"mediumNumber": 0,
|
||||||
|
"mediumName": "string",
|
||||||
|
"mediumFormat": "string"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"mediumCount": 0,
|
||||||
|
"disambiguation": "string",
|
||||||
|
"country": ["string"],
|
||||||
|
"label": ["string"],
|
||||||
|
"format": "string",
|
||||||
|
"monitored": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"genres": ["string"],
|
||||||
|
"media": [
|
||||||
|
{
|
||||||
|
"mediumNumber": 0,
|
||||||
|
"mediumName": "string",
|
||||||
|
"mediumFormat": "string"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"artist": {
|
||||||
|
"id": 0,
|
||||||
|
"status": "continuing",
|
||||||
|
"ended": true,
|
||||||
|
"artistName": "string",
|
||||||
|
"foreignArtistId": "string",
|
||||||
|
"mbId": "string",
|
||||||
|
"tadbId": 0,
|
||||||
|
"discogsId": 0,
|
||||||
|
"allMusicId": "string",
|
||||||
|
"overview": "string",
|
||||||
|
"artistType": "string",
|
||||||
|
"disambiguation": "string",
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"url": "string",
|
||||||
|
"name": "string"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"nextAlbum": "string",
|
||||||
|
"lastAlbum": "string",
|
||||||
|
"images": [
|
||||||
|
{
|
||||||
|
"url": "string",
|
||||||
|
"coverType": "unknown",
|
||||||
|
"extension": "string",
|
||||||
|
"remoteUrl": "string"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"members": [
|
||||||
|
{
|
||||||
|
"name": "string",
|
||||||
|
"instrument": "string",
|
||||||
|
"images": [
|
||||||
|
{
|
||||||
|
"url": "string",
|
||||||
|
"coverType": "unknown",
|
||||||
|
"extension": "string",
|
||||||
|
"remoteUrl": "string"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"remotePoster": "string",
|
||||||
|
"path": "string",
|
||||||
|
"qualityProfileId": 0,
|
||||||
|
"metadataProfileId": 0,
|
||||||
|
"monitored": true,
|
||||||
|
"monitorNewItems": "all",
|
||||||
|
"rootFolderPath": "string",
|
||||||
|
"folder": "string",
|
||||||
|
"genres": ["string"],
|
||||||
|
"cleanName": "string",
|
||||||
|
"sortName": "string",
|
||||||
|
"tags": [0],
|
||||||
|
"added": "2024-09-09T20:16:28.493Z",
|
||||||
|
"addOptions": {
|
||||||
|
"monitor": "all",
|
||||||
|
"albumsToMonitor": ["string"],
|
||||||
|
"monitored": true,
|
||||||
|
"searchForMissingAlbums": true
|
||||||
|
},
|
||||||
|
"ratings": {
|
||||||
|
"votes": 0,
|
||||||
|
"value": 0
|
||||||
|
},
|
||||||
|
"statistics": {
|
||||||
|
"albumCount": 0,
|
||||||
|
"trackFileCount": 0,
|
||||||
|
"trackCount": 0,
|
||||||
|
"totalTrackCount": 0,
|
||||||
|
"sizeOnDisk": 0,
|
||||||
|
"percentOfTracks": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"images": [
|
||||||
|
{
|
||||||
|
"url": "string",
|
||||||
|
"coverType": "unknown",
|
||||||
|
"extension": "string",
|
||||||
|
"remoteUrl": "string"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"url": "string",
|
||||||
|
"name": "string"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"statistics": {
|
||||||
|
"trackFileCount": 0,
|
||||||
|
"trackCount": 0,
|
||||||
|
"totalTrackCount": 0,
|
||||||
|
"sizeOnDisk": 0,
|
||||||
|
"percentOfTracks": 0
|
||||||
|
},
|
||||||
|
"addOptions": {
|
||||||
|
"addType": "automatic",
|
||||||
|
"searchForNewAlbum": true
|
||||||
|
},
|
||||||
|
"remoteCover": "string"
|
||||||
|
}
|
||||||
|
]
|
@ -25,10 +25,14 @@ async def test_sensors(
|
|||||||
assert state.state == "2"
|
assert state.state == "2"
|
||||||
assert state.attributes.get("string") == "stopped"
|
assert state.attributes.get("string") == "stopped"
|
||||||
assert state.attributes.get("string2") == "downloading"
|
assert state.attributes.get("string2") == "downloading"
|
||||||
assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == "Albums"
|
assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == "albums"
|
||||||
assert state.attributes.get(CONF_STATE_CLASS) == SensorStateClass.TOTAL
|
assert state.attributes.get(CONF_STATE_CLASS) == SensorStateClass.TOTAL
|
||||||
state = hass.states.get("sensor.mock_title_wanted")
|
state = hass.states.get("sensor.mock_title_wanted")
|
||||||
assert state.state == "1"
|
assert state.state == "1"
|
||||||
assert state.attributes.get("test") == "test"
|
assert state.attributes.get("test") == "test"
|
||||||
assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == "Albums"
|
assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == "albums"
|
||||||
|
assert state.attributes.get(CONF_STATE_CLASS) == SensorStateClass.TOTAL
|
||||||
|
state = hass.states.get("sensor.mock_title_albums")
|
||||||
|
assert state.state == "1"
|
||||||
|
assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == "albums"
|
||||||
assert state.attributes.get(CONF_STATE_CLASS) == SensorStateClass.TOTAL
|
assert state.attributes.get(CONF_STATE_CLASS) == SensorStateClass.TOTAL
|
||||||
|
Loading…
x
Reference in New Issue
Block a user