Add "Albums" sensor to Lidarr (#125631)

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
This commit is contained in:
Isaac 2024-10-25 17:13:03 +01:00 committed by GitHub
parent c1f612dce1
commit 50161670ce
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 193 additions and 5 deletions

View File

@ -16,6 +16,7 @@ from homeassistant.helpers.device_registry import DeviceEntryType
from .const import DEFAULT_NAME, DOMAIN
from .coordinator import (
AlbumsDataUpdateCoordinator,
DiskSpaceDataUpdateCoordinator,
QueueDataUpdateCoordinator,
StatusDataUpdateCoordinator,
@ -35,6 +36,7 @@ class LidarrData:
queue: QueueDataUpdateCoordinator
status: StatusDataUpdateCoordinator
wanted: WantedDataUpdateCoordinator
albums: AlbumsDataUpdateCoordinator
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),
status=StatusDataUpdateCoordinator(hass, host_configuration, lidarr),
wanted=WantedDataUpdateCoordinator(hass, host_configuration, lidarr),
albums=AlbumsDataUpdateCoordinator(hass, host_configuration, lidarr),
)
for field in fields(data):
coordinator = getattr(data, field.name)

View File

@ -17,7 +17,7 @@ from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, Upda
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):
@ -96,3 +96,11 @@ class WantedDataUpdateCoordinator(LidarrDataUpdateCoordinator[LidarrAlbum]):
LidarrAlbum,
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()))

View File

@ -85,7 +85,7 @@ SENSOR_TYPES: dict[str, LidarrSensorEntityDescription[Any]] = {
"queue": LidarrSensorEntityDescription[LidarrQueue](
key="queue",
translation_key="queue",
native_unit_of_measurement="Albums",
native_unit_of_measurement="albums",
value_fn=lambda data, _: data.totalRecords,
state_class=SensorStateClass.TOTAL,
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](
key="wanted",
translation_key="wanted",
native_unit_of_measurement="Albums",
native_unit_of_measurement="albums",
value_fn=lambda data, _: data.totalRecords,
state_class=SensorStateClass.TOTAL,
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
},
),
"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,
),
}

View File

@ -39,6 +39,9 @@
},
"wanted": {
"name": "Wanted"
},
"albums": {
"name": "Albums"
}
}
}

View File

@ -44,10 +44,12 @@ def mock_error(
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}/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}/rootfolder", 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}/album", exc=ClientError)
@pytest.fixture
@ -115,6 +117,11 @@ def mock_connection(aioclient_mock: AiohttpClientMocker) -> None:
text=load_fixture("lidarr/wanted-missing.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(
f"{API_URL}/rootfolder",
text=load_fixture("lidarr/rootfolder-linux.json"),

View 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"
}
]

View File

@ -25,10 +25,14 @@ async def test_sensors(
assert state.state == "2"
assert state.attributes.get("string") == "stopped"
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
state = hass.states.get("sensor.mock_title_wanted")
assert state.state == "1"
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