Add update entity to immich integration (#147273)

* add update entity

* remove unneccessary entity description

* rename update entity to version

* simplify test

* define static attribute outside of the constructor

* move min version check into coordinator
This commit is contained in:
Michael 2025-06-22 16:54:48 +02:00 committed by GitHub
parent d4e7667ea0
commit 41e53297c2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 209 additions and 20 deletions

View File

@ -20,7 +20,7 @@ from homeassistant.helpers.aiohttp_client import async_get_clientsession
from .coordinator import ImmichConfigEntry, ImmichDataUpdateCoordinator
PLATFORMS: list[Platform] = [Platform.SENSOR]
PLATFORMS: list[Platform] = [Platform.SENSOR, Platform.UPDATE]
async def async_setup_entry(hass: HomeAssistant, entry: ImmichConfigEntry) -> bool:

View File

@ -13,7 +13,9 @@ from aioimmich.server.models import (
ImmichServerAbout,
ImmichServerStatistics,
ImmichServerStorage,
ImmichServerVersionCheck,
)
from awesomeversion import AwesomeVersion
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_HOST, CONF_PORT, CONF_SSL
@ -33,6 +35,7 @@ class ImmichData:
server_about: ImmichServerAbout
server_storage: ImmichServerStorage
server_usage: ImmichServerStatistics | None
server_version_check: ImmichServerVersionCheck | None
type ImmichConfigEntry = ConfigEntry[ImmichDataUpdateCoordinator]
@ -71,9 +74,16 @@ class ImmichDataUpdateCoordinator(DataUpdateCoordinator[ImmichData]):
if self.is_admin
else None
)
server_version_check = (
await self.api.server.async_get_version_check()
if AwesomeVersion(server_about.version) >= AwesomeVersion("v1.134.0")
else None
)
except ImmichUnauthorizedError as err:
raise ConfigEntryAuthFailed from err
except CONNECT_ERRORS as err:
raise UpdateFailed from err
return ImmichData(server_about, server_storage, server_usage)
return ImmichData(
server_about, server_storage, server_usage, server_version_check
)

View File

@ -68,6 +68,11 @@
"usage_by_videos": {
"name": "Disk used by videos"
}
},
"update": {
"update": {
"name": "Version"
}
}
}
}

View File

@ -0,0 +1,57 @@
"""Update platform for the Immich integration."""
from __future__ import annotations
from homeassistant.components.update import UpdateEntity
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .coordinator import ImmichConfigEntry, ImmichDataUpdateCoordinator
from .entity import ImmichEntity
# Coordinator is used to centralize the data updates
PARALLEL_UPDATES = 0
async def async_setup_entry(
hass: HomeAssistant,
entry: ImmichConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Add immich server update entity."""
coordinator = entry.runtime_data
if coordinator.data.server_version_check is not None:
async_add_entities([ImmichUpdateEntity(coordinator)])
class ImmichUpdateEntity(ImmichEntity, UpdateEntity):
"""Define Immich update entity."""
_attr_translation_key = "update"
def __init__(
self,
coordinator: ImmichDataUpdateCoordinator,
) -> None:
"""Initialize."""
super().__init__(coordinator)
self._attr_unique_id = f"{coordinator.config_entry.unique_id}_update"
@property
def installed_version(self) -> str:
"""Current installed immich server version."""
return self.coordinator.data.server_about.version
@property
def latest_version(self) -> str:
"""Available new immich server version."""
assert self.coordinator.data.server_version_check
return self.coordinator.data.server_version_check.release_version
@property
def release_url(self) -> str | None:
"""URL to the full release notes of the new immich server version."""
return (
f"https://github.com/immich-app/immich/releases/tag/{self.latest_version}"
)

View File

@ -8,6 +8,7 @@ from aioimmich.server.models import (
ImmichServerAbout,
ImmichServerStatistics,
ImmichServerStorage,
ImmichServerVersionCheck,
)
from aioimmich.users.models import ImmichUserObject
import pytest
@ -79,21 +80,21 @@ def mock_immich_server() -> AsyncMock:
mock = AsyncMock(spec=ImmichServer)
mock.async_get_about_info.return_value = ImmichServerAbout.from_dict(
{
"version": "v1.132.3",
"versionUrl": "https://github.com/immich-app/immich/releases/tag/v1.132.3",
"version": "v1.134.0",
"versionUrl": "https://github.com/immich-app/immich/releases/tag/v1.134.0",
"licensed": False,
"build": "14709928600",
"buildUrl": "https://github.com/immich-app/immich/actions/runs/14709928600",
"buildImage": "v1.132.3",
"build": "15281783550",
"buildUrl": "https://github.com/immich-app/immich/actions/runs/15281783550",
"buildImage": "v1.134.0",
"buildImageUrl": "https://github.com/immich-app/immich/pkgs/container/immich-server",
"repository": "immich-app/immich",
"repositoryUrl": "https://github.com/immich-app/immich",
"sourceRef": "v1.132.3",
"sourceCommit": "02994883fe3f3972323bb6759d0170a4062f5236",
"sourceUrl": "https://github.com/immich-app/immich/commit/02994883fe3f3972323bb6759d0170a4062f5236",
"sourceRef": "v1.134.0",
"sourceCommit": "58ae77ec9204a2e43a8cb2f1fd27482af40d0891",
"sourceUrl": "https://github.com/immich-app/immich/commit/58ae77ec9204a2e43a8cb2f1fd27482af40d0891",
"nodejs": "v22.14.0",
"exiftool": "13.00",
"ffmpeg": "7.0.2-7",
"ffmpeg": "7.0.2-9",
"libvips": "8.16.1",
"imagemagick": "7.1.1-47",
}
@ -130,6 +131,12 @@ def mock_immich_server() -> AsyncMock:
],
}
)
mock.async_get_version_check.return_value = ImmichServerVersionCheck.from_dict(
{
"checkedAt": "2025-06-21T16:35:10.352Z",
"releaseVersion": "v1.135.3",
}
)
return mock

View File

@ -3,23 +3,23 @@
dict({
'data': dict({
'server_about': dict({
'build': '14709928600',
'build_image': 'v1.132.3',
'build': '15281783550',
'build_image': 'v1.134.0',
'build_image_url': 'https://github.com/immich-app/immich/pkgs/container/immich-server',
'build_url': 'https://github.com/immich-app/immich/actions/runs/14709928600',
'build_url': 'https://github.com/immich-app/immich/actions/runs/15281783550',
'exiftool': '13.00',
'ffmpeg': '7.0.2-7',
'ffmpeg': '7.0.2-9',
'imagemagick': '7.1.1-47',
'libvips': '8.16.1',
'licensed': False,
'nodejs': 'v22.14.0',
'repository': 'immich-app/immich',
'repository_url': 'https://github.com/immich-app/immich',
'source_commit': '02994883fe3f3972323bb6759d0170a4062f5236',
'source_ref': 'v1.132.3',
'source_url': 'https://github.com/immich-app/immich/commit/02994883fe3f3972323bb6759d0170a4062f5236',
'version': 'v1.132.3',
'version_url': 'https://github.com/immich-app/immich/releases/tag/v1.132.3',
'source_commit': '58ae77ec9204a2e43a8cb2f1fd27482af40d0891',
'source_ref': 'v1.134.0',
'source_url': 'https://github.com/immich-app/immich/commit/58ae77ec9204a2e43a8cb2f1fd27482af40d0891',
'version': 'v1.134.0',
'version_url': 'https://github.com/immich-app/immich/releases/tag/v1.134.0',
}),
'server_storage': dict({
'disk_available': '136.3 GiB',
@ -49,6 +49,10 @@
'usage_videos': 65234281361,
'videos': 1836,
}),
'server_version_check': dict({
'checked_at': '2025-06-21T16:35:10.352000+00:00',
'release_version': 'v1.135.3',
}),
}),
'entry': dict({
'data': dict({

View File

@ -0,0 +1,61 @@
# serializer version: 1
# name: test_update[update.someone_version-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'update',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'update.someone_version',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Version',
'platform': 'immich',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'update',
'unique_id': 'e7ef5713-9dab-4bd4-b899-715b0ca4379e_update',
'unit_of_measurement': None,
})
# ---
# name: test_update[update.someone_version-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'auto_update': False,
'display_precision': 0,
'entity_picture': 'https://brands.home-assistant.io/_/immich/icon.png',
'friendly_name': 'Someone Version',
'in_progress': False,
'installed_version': 'v1.134.0',
'latest_version': 'v1.135.3',
'release_summary': None,
'release_url': 'https://github.com/immich-app/immich/releases/tag/v1.135.3',
'skipped_version': None,
'supported_features': <UpdateEntityFeature: 0>,
'title': None,
'update_percentage': None,
}),
'context': <ANY>,
'entity_id': 'update.someone_version',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'on',
})
# ---

View File

@ -0,0 +1,45 @@
"""Test the Immich update platform."""
from unittest.mock import Mock, patch
from syrupy.assertion import SnapshotAssertion
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from . import setup_integration
from tests.common import MockConfigEntry, snapshot_platform
async def test_update(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
snapshot: SnapshotAssertion,
mock_immich: Mock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test the Immich update platform."""
with patch("homeassistant.components.immich.PLATFORMS", [Platform.UPDATE]):
await setup_integration(hass, mock_config_entry)
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
async def test_update_min_version(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
snapshot: SnapshotAssertion,
mock_immich: Mock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test the Immich update platform with min version not installed."""
mock_immich.server.async_get_about_info.return_value.version = "v1.132.3"
with patch("homeassistant.components.immich.PLATFORMS", [Platform.UPDATE]):
await setup_integration(hass, mock_config_entry)
assert not hass.states.async_all()