Move prusalink migration to async_migrate_entry and use a minor version bump (#106109)

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
pull/105552/head
Robert Resch 2023-12-20 19:10:12 +01:00 committed by GitHub
parent ea28b74fe9
commit 803e77bebd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 102 additions and 77 deletions

View File

@ -20,6 +20,7 @@ from homeassistant.const import (
Platform,
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import ConfigEntryError
from homeassistant.helpers import issue_registry as ir
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.device_registry import DeviceInfo
@ -29,73 +30,24 @@ from homeassistant.helpers.update_coordinator import (
UpdateFailed,
)
from .config_flow import ConfigFlow
from .const import DOMAIN
PLATFORMS: list[Platform] = [Platform.BUTTON, Platform.CAMERA, Platform.SENSOR]
_LOGGER = logging.getLogger(__name__)
async def _migrate_to_version_2(
hass: HomeAssistant, entry: ConfigEntry
) -> PrusaLink | None:
"""Migrate to Version 2."""
_LOGGER.debug("Migrating entry to version 2")
data = dict(entry.data)
# "maker" is currently hardcoded in the firmware
# https://github.com/prusa3d/Prusa-Firmware-Buddy/blob/bfb0ffc745ee6546e7efdba618d0e7c0f4c909cd/lib/WUI/wui_api.h#L19
data = {
**entry.data,
CONF_USERNAME: "maker",
CONF_PASSWORD: entry.data[CONF_API_KEY],
}
data.pop(CONF_API_KEY)
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up PrusaLink from a config entry."""
if entry.version == 1 and entry.minor_version < 2:
raise ConfigEntryError("Please upgrade your printer's firmware.")
api = PrusaLink(
async_get_clientsession(hass),
data[CONF_HOST],
data[CONF_USERNAME],
data[CONF_PASSWORD],
entry.data[CONF_HOST],
entry.data[CONF_USERNAME],
entry.data[CONF_PASSWORD],
)
try:
await api.get_info()
except InvalidAuth:
# We are unable to reach the new API which usually means
# that the user is running an outdated firmware version
ir.async_create_issue(
hass,
DOMAIN,
"firmware_5_1_required",
is_fixable=False,
severity=ir.IssueSeverity.ERROR,
translation_key="firmware_5_1_required",
translation_placeholders={
"entry_title": entry.title,
"prusa_mini_firmware_update": "https://help.prusa3d.com/article/firmware-updating-mini-mini_124784",
"prusa_mk4_xl_firmware_update": "https://help.prusa3d.com/article/how-to-update-firmware-mk4-xl_453086",
},
)
return None
entry.version = 2
hass.config_entries.async_update_entry(entry, data=data)
_LOGGER.info("Migrated config entry to version %d", entry.version)
return api
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up PrusaLink from a config entry."""
if entry.version == 1:
if (api := await _migrate_to_version_2(hass, entry)) is None:
return False
ir.async_delete_issue(hass, DOMAIN, "firmware_5_1_required")
else:
api = PrusaLink(
async_get_clientsession(hass),
entry.data[CONF_HOST],
entry.data[CONF_USERNAME],
entry.data[CONF_PASSWORD],
)
coordinators = {
"legacy_status": LegacyStatusCoordinator(hass, api),
@ -112,9 +64,59 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
return True
async def async_migrate_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
"""Migrate old entry."""
# Version 1->2 migration are handled in async_setup_entry.
if config_entry.version > ConfigFlow.VERSION:
# This means the user has downgraded from a future version
return False
new_data = dict(config_entry.data)
if config_entry.version == 1:
if config_entry.minor_version < 2:
# Add username and password
# "maker" is currently hardcoded in the firmware
# https://github.com/prusa3d/Prusa-Firmware-Buddy/blob/bfb0ffc745ee6546e7efdba618d0e7c0f4c909cd/lib/WUI/wui_api.h#L19
username = "maker"
password = config_entry.data[CONF_API_KEY]
api = PrusaLink(
async_get_clientsession(hass),
config_entry.data[CONF_HOST],
username,
password,
)
try:
await api.get_info()
except InvalidAuth:
# We are unable to reach the new API which usually means
# that the user is running an outdated firmware version
ir.async_create_issue(
hass,
DOMAIN,
"firmware_5_1_required",
is_fixable=False,
severity=ir.IssueSeverity.ERROR,
translation_key="firmware_5_1_required",
translation_placeholders={
"entry_title": config_entry.title,
"prusa_mini_firmware_update": "https://help.prusa3d.com/article/firmware-updating-mini-mini_124784",
"prusa_mk4_xl_firmware_update": "https://help.prusa3d.com/article/how-to-update-firmware-mk4-xl_453086",
},
)
# There is a check in the async_setup_entry to prevent the setup if minor_version < 2
# Currently we can't reload the config entry
# if the migration returns False.
# Return True here to workaround that.
return True
new_data[CONF_USERNAME] = username
new_data[CONF_PASSWORD] = password
ir.async_delete_issue(hass, DOMAIN, "firmware_5_1_required")
config_entry.minor_version = 2
hass.config_entries.async_update_entry(config_entry, data=new_data)
return True

View File

@ -66,7 +66,8 @@ async def validate_input(hass: HomeAssistant, data: dict[str, str]) -> dict[str,
class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle a config flow for PrusaLink."""
VERSION = 2
VERSION = 1
MINOR_VERSION = 2
async def async_step_user(
self, user_input: dict[str, Any] | None = None

View File

@ -14,7 +14,8 @@ def mock_config_entry(hass):
entry = MockConfigEntry(
domain=DOMAIN,
data={"host": "http://example.com", "username": "dummy", "password": "dummypw"},
version=2,
version=1,
minor_version=2,
)
entry.add_to_hass(hass)
return entry

View File

@ -6,6 +6,7 @@ from pyprusalink.types import InvalidAuth, PrusaLinkError
import pytest
from homeassistant.components.prusalink import DOMAIN
from homeassistant.components.prusalink.config_flow import ConfigFlow
from homeassistant.config_entries import ConfigEntry, ConfigEntryState
from homeassistant.const import CONF_API_KEY, CONF_HOST, CONF_PASSWORD, CONF_USERNAME
from homeassistant.core import HomeAssistant
@ -14,11 +15,12 @@ from homeassistant.util.dt import utcnow
from tests.common import MockConfigEntry, async_fire_time_changed
pytestmark = pytest.mark.usefixtures("mock_api")
async def test_unloading(
hass: HomeAssistant,
mock_config_entry: ConfigEntry,
mock_api,
) -> None:
"""Test unloading prusalink."""
assert await hass.config_entries.async_setup(mock_config_entry.entry_id)
@ -35,7 +37,7 @@ async def test_unloading(
@pytest.mark.parametrize("exception", [InvalidAuth, PrusaLinkError])
async def test_failed_update(
hass: HomeAssistant, mock_config_entry: ConfigEntry, mock_api, exception
hass: HomeAssistant, mock_config_entry: ConfigEntry, exception
) -> None:
"""Test failed update marks prusalink unavailable."""
assert await hass.config_entries.async_setup(mock_config_entry.entry_id)
@ -61,16 +63,17 @@ async def test_failed_update(
assert state.state == "unavailable"
async def test_migration_1_2(
hass: HomeAssistant, issue_registry: ir.IssueRegistry, mock_api
async def test_migration_from_1_1_to_1_2(
hass: HomeAssistant, issue_registry: ir.IssueRegistry
) -> None:
"""Test migrating from version 1 to 2."""
data = {
CONF_HOST: "http://prusaxl.local",
CONF_API_KEY: "api-key",
}
entry = MockConfigEntry(
domain=DOMAIN,
data={
CONF_HOST: "http://prusaxl.local",
CONF_API_KEY: "api-key",
},
data=data,
version=1,
)
entry.add_to_hass(hass)
@ -83,7 +86,7 @@ async def test_migration_1_2(
# Ensure that we have username, password after migration
assert len(config_entries) == 1
assert config_entries[0].data == {
CONF_HOST: "http://prusaxl.local",
**data,
CONF_USERNAME: "maker",
CONF_PASSWORD: "api-key",
}
@ -91,10 +94,10 @@ async def test_migration_1_2(
assert len(issue_registry.issues) == 0
async def test_outdated_firmware_migration_1_2(
hass: HomeAssistant, issue_registry: ir.IssueRegistry, mock_api
async def test_migration_from_1_1_to_1_2_outdated_firmware(
hass: HomeAssistant, issue_registry: ir.IssueRegistry
) -> None:
"""Test migrating from version 1 to 2."""
"""Test migrating from version 1.1 to 1.2."""
entry = MockConfigEntry(
domain=DOMAIN,
data={
@ -107,14 +110,14 @@ async def test_outdated_firmware_migration_1_2(
with patch(
"pyprusalink.PrusaLink.get_info",
side_effect=InvalidAuth,
side_effect=InvalidAuth, # Simulate firmware update required
):
await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
assert entry.state == ConfigEntryState.SETUP_ERROR
# Make sure that we don't have thrown the issues
assert len(issue_registry.issues) == 1
assert entry.minor_version == 1
assert (DOMAIN, "firmware_5_1_required") in issue_registry.issues
# Reloading the integration with a working API (e.g. User updated firmware)
await hass.config_entries.async_reload(entry.entry_id)
@ -122,4 +125,22 @@ async def test_outdated_firmware_migration_1_2(
# Integration should be running now, the issue should be gone
assert entry.state == ConfigEntryState.LOADED
assert len(issue_registry.issues) == 0
assert entry.minor_version == 2
assert (DOMAIN, "firmware_5_1_required") not in issue_registry.issues
async def test_migration_fails_on_future_version(
hass: HomeAssistant, issue_registry: ir.IssueRegistry
) -> None:
"""Test migrating fails on a version higher than the current one."""
entry = MockConfigEntry(
domain=DOMAIN,
data={},
version=ConfigFlow.VERSION + 1,
)
entry.add_to_hass(hass)
await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
assert entry.state == ConfigEntryState.MIGRATION_ERROR