Bump pylamarzocco to 2.1.0 (#152364)

This commit is contained in:
Josef Zweck
2025-09-19 20:00:37 +00:00
committed by Franck Nijhof
parent 757aec1c6b
commit 10b186a20d
10 changed files with 102 additions and 63 deletions
+18 -23
View File
@@ -2,6 +2,7 @@
import asyncio
import logging
import uuid
from packaging import version
from pylamarzocco import (
@@ -11,6 +12,7 @@ from pylamarzocco import (
)
from pylamarzocco.const import FirmwareType
from pylamarzocco.exceptions import AuthFail, RequestNotSuccessful
from pylamarzocco.util import InstallationKey, generate_installation_key
from homeassistant.components.bluetooth import async_discovered_service_info
from homeassistant.const import (
@@ -25,7 +27,7 @@ from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from homeassistant.helpers import issue_registry as ir
from homeassistant.helpers.aiohttp_client import async_create_clientsession
from .const import CONF_USE_BLUETOOTH, DOMAIN
from .const import CONF_INSTALLATION_KEY, CONF_USE_BLUETOOTH, DOMAIN
from .coordinator import (
LaMarzoccoConfigEntry,
LaMarzoccoConfigUpdateCoordinator,
@@ -60,6 +62,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: LaMarzoccoConfigEntry) -
cloud_client = LaMarzoccoCloudClient(
username=entry.data[CONF_USERNAME],
password=entry.data[CONF_PASSWORD],
installation_key=InstallationKey.from_json(entry.data[CONF_INSTALLATION_KEY]),
client=async_create_clientsession(hass),
)
@@ -166,45 +169,37 @@ async def async_migrate_entry(
hass: HomeAssistant, entry: LaMarzoccoConfigEntry
) -> bool:
"""Migrate config entry."""
if entry.version > 3:
if entry.version > 4:
# guard against downgrade from a future version
return False
if entry.version == 1:
if entry.version in (1, 2):
_LOGGER.error(
"Migration from version 1 is no longer supported, please remove and re-add the integration"
"Migration from version 1 or 2 is no longer supported, please remove and re-add the integration"
)
return False
if entry.version == 2:
if entry.version == 3:
installation_key = generate_installation_key(str(uuid.uuid4()).lower())
cloud_client = LaMarzoccoCloudClient(
username=entry.data[CONF_USERNAME],
password=entry.data[CONF_PASSWORD],
installation_key=installation_key,
)
try:
things = await cloud_client.list_things()
await cloud_client.async_register_client()
except (AuthFail, RequestNotSuccessful) as exc:
_LOGGER.error("Migration failed with error %s", exc)
return False
v3_data = {
CONF_USERNAME: entry.data[CONF_USERNAME],
CONF_PASSWORD: entry.data[CONF_PASSWORD],
CONF_TOKEN: next(
(
thing.ble_auth_token
for thing in things
if thing.serial_number == entry.unique_id
),
None,
),
}
if CONF_MAC in entry.data:
v3_data[CONF_MAC] = entry.data[CONF_MAC]
hass.config_entries.async_update_entry(
entry,
data=v3_data,
version=3,
data={
**entry.data,
CONF_INSTALLATION_KEY: installation_key.to_json(),
},
version=4,
)
_LOGGER.debug("Migrated La Marzocco config entry to version 2")
_LOGGER.debug("Migrated La Marzocco config entry to version 4")
return True
@@ -5,11 +5,13 @@ from __future__ import annotations
from collections.abc import Mapping
import logging
from typing import Any
import uuid
from aiohttp import ClientSession
from pylamarzocco import LaMarzoccoCloudClient
from pylamarzocco.exceptions import AuthFail, RequestNotSuccessful
from pylamarzocco.models import Thing
from pylamarzocco.util import InstallationKey, generate_installation_key
import voluptuous as vol
from homeassistant.components.bluetooth import (
@@ -45,7 +47,7 @@ from homeassistant.helpers.selector import (
)
from homeassistant.helpers.service_info.dhcp import DhcpServiceInfo
from .const import CONF_USE_BLUETOOTH, DOMAIN
from .const import CONF_INSTALLATION_KEY, CONF_USE_BLUETOOTH, DOMAIN
from .coordinator import LaMarzoccoConfigEntry
CONF_MACHINE = "machine"
@@ -57,9 +59,10 @@ _LOGGER = logging.getLogger(__name__)
class LmConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle a config flow for La Marzocco."""
VERSION = 3
VERSION = 4
_client: ClientSession
_installation_key: InstallationKey
def __init__(self) -> None:
"""Initialize the config flow."""
@@ -84,12 +87,17 @@ class LmConfigFlow(ConfigFlow, domain=DOMAIN):
}
self._client = async_create_clientsession(self.hass)
self._installation_key = generate_installation_key(
str(uuid.uuid4()).lower()
)
cloud_client = LaMarzoccoCloudClient(
username=data[CONF_USERNAME],
password=data[CONF_PASSWORD],
client=self._client,
installation_key=self._installation_key,
)
try:
await cloud_client.async_register_client()
things = await cloud_client.list_things()
except AuthFail:
_LOGGER.debug("Server rejected login credentials")
@@ -184,6 +192,7 @@ class LmConfigFlow(ConfigFlow, domain=DOMAIN):
title=selected_device.name,
data={
**self._config,
CONF_INSTALLATION_KEY: self._installation_key.to_json(),
CONF_TOKEN: self._things[serial_number].ble_auth_token,
},
)
@@ -5,3 +5,4 @@ from typing import Final
DOMAIN: Final = "lamarzocco"
CONF_USE_BLUETOOTH: Final = "use_bluetooth"
CONF_INSTALLATION_KEY: Final = "installation_key"
@@ -37,5 +37,5 @@
"iot_class": "cloud_push",
"loggers": ["pylamarzocco"],
"quality_scale": "platinum",
"requirements": ["pylamarzocco==2.0.11"]
"requirements": ["pylamarzocco==2.1.0"]
}
+1 -1
View File
@@ -2103,7 +2103,7 @@ pykwb==0.0.8
pylacrosse==0.4
# homeassistant.components.lamarzocco
pylamarzocco==2.0.11
pylamarzocco==2.1.0
# homeassistant.components.lastfm
pylast==5.1.0
+1 -1
View File
@@ -1748,7 +1748,7 @@ pykrakenapi==0.1.8
pykulersky==0.5.8
# homeassistant.components.lamarzocco
pylamarzocco==2.0.11
pylamarzocco==2.1.0
# homeassistant.components.lastfm
pylast==5.1.0
+3
View File
@@ -54,3 +54,6 @@ def get_bluetooth_service_info(model: ModelName, serial: str) -> BluetoothServic
service_uuids=[],
source="local",
)
MOCK_INSTALLATION_KEY = '{"secret": "K9ZW2vlMSb3QXmhySx4pxAbTHujWj3VZ01Jn3D/sO98=", "private_key": "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg8iotE8El786F6kHuEL8GyYhjDB7oo06vNhQwtewF37yhRANCAAQCLb9lHskiavvfkI4H2B+WsdkusfgBBFuFNRrGV8bqPMra1TK5myb/ecdZfHJBBJrcbdt90QMDmXQm5L3muXXe", "installation_id": "4e966f3f-2abc-49c4-a362-3cd3346f1a87"}'
+21 -3
View File
@@ -12,13 +12,14 @@ from pylamarzocco.models import (
ThingSettings,
ThingStatistics,
)
from pylamarzocco.util import InstallationKey
import pytest
from homeassistant.components.lamarzocco.const import DOMAIN
from homeassistant.components.lamarzocco.const import CONF_INSTALLATION_KEY, DOMAIN
from homeassistant.const import CONF_ADDRESS, CONF_TOKEN
from homeassistant.core import HomeAssistant
from . import SERIAL_DICT, USER_INPUT, async_init_integration
from . import MOCK_INSTALLATION_KEY, SERIAL_DICT, USER_INPUT, async_init_integration
from tests.common import MockConfigEntry, load_json_object_fixture
@@ -31,11 +32,12 @@ def mock_config_entry(
return MockConfigEntry(
title="My LaMarzocco",
domain=DOMAIN,
version=3,
version=4,
data=USER_INPUT
| {
CONF_ADDRESS: "000000000000",
CONF_TOKEN: "token",
CONF_INSTALLATION_KEY: MOCK_INSTALLATION_KEY,
},
unique_id=mock_lamarzocco.serial_number,
)
@@ -51,6 +53,22 @@ async def init_integration(
return mock_config_entry
@pytest.fixture(autouse=True)
def mock_generate_installation_key() -> Generator[MagicMock]:
"""Return a mocked generate_installation_key."""
with (
patch(
"homeassistant.components.lamarzocco.generate_installation_key",
return_value=InstallationKey.from_json(MOCK_INSTALLATION_KEY),
) as mock_generate,
patch(
"homeassistant.components.lamarzocco.config_flow.generate_installation_key",
new=mock_generate,
),
):
yield mock_generate
@pytest.fixture
def device_fixture() -> ModelName:
"""Return the device fixture for a specific device."""
@@ -9,7 +9,11 @@ from pylamarzocco.exceptions import AuthFail, RequestNotSuccessful
import pytest
from homeassistant.components.lamarzocco.config_flow import CONF_MACHINE
from homeassistant.components.lamarzocco.const import CONF_USE_BLUETOOTH, DOMAIN
from homeassistant.components.lamarzocco.const import (
CONF_INSTALLATION_KEY,
CONF_USE_BLUETOOTH,
DOMAIN,
)
from homeassistant.config_entries import (
SOURCE_BLUETOOTH,
SOURCE_DHCP,
@@ -23,7 +27,12 @@ from homeassistant.data_entry_flow import FlowResultType
from homeassistant.helpers.service_info.bluetooth import BluetoothServiceInfo
from homeassistant.helpers.service_info.dhcp import DhcpServiceInfo
from . import USER_INPUT, async_init_integration, get_bluetooth_service_info
from . import (
MOCK_INSTALLATION_KEY,
USER_INPUT,
async_init_integration,
get_bluetooth_service_info,
)
from tests.common import MockConfigEntry
@@ -68,6 +77,7 @@ async def __do_sucessful_machine_selection_step(
assert result["data"] == {
**USER_INPUT,
CONF_TOKEN: None,
CONF_INSTALLATION_KEY: MOCK_INSTALLATION_KEY,
}
assert result["result"].unique_id == "GS012345"
@@ -344,6 +354,7 @@ async def test_bluetooth_discovery(
**USER_INPUT,
CONF_MAC: "aa:bb:cc:dd:ee:ff",
CONF_TOKEN: "dummyToken",
CONF_INSTALLATION_KEY: MOCK_INSTALLATION_KEY,
}
@@ -407,6 +418,7 @@ async def test_bluetooth_discovery_errors(
**USER_INPUT,
CONF_MAC: "aa:bb:cc:dd:ee:ff",
CONF_TOKEN: None,
CONF_INSTALLATION_KEY: MOCK_INSTALLATION_KEY,
}
@@ -438,6 +450,7 @@ async def test_dhcp_discovery(
**USER_INPUT,
CONF_ADDRESS: "aabbccddeeff",
CONF_TOKEN: None,
CONF_INSTALLATION_KEY: MOCK_INSTALLATION_KEY,
}
+30 -30
View File
@@ -8,15 +8,11 @@ from pylamarzocco.models import WebSocketDetails
import pytest
from syrupy.assertion import SnapshotAssertion
from homeassistant.components.lamarzocco.config_flow import CONF_MACHINE
from homeassistant.components.lamarzocco.const import DOMAIN
from homeassistant.components.lamarzocco.const import CONF_INSTALLATION_KEY, DOMAIN
from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntryState
from homeassistant.const import (
CONF_ADDRESS,
CONF_HOST,
CONF_MAC,
CONF_MODEL,
CONF_NAME,
CONF_TOKEN,
EVENT_HOMEASSISTANT_STOP,
)
@@ -27,7 +23,12 @@ from homeassistant.helpers import (
issue_registry as ir,
)
from . import USER_INPUT, async_init_integration, get_bluetooth_service_info
from . import (
MOCK_INSTALLATION_KEY,
USER_INPUT,
async_init_integration,
get_bluetooth_service_info,
)
from tests.common import MockConfigEntry
@@ -129,66 +130,65 @@ async def test_v1_migration_fails(
assert entry_v1.state is ConfigEntryState.MIGRATION_ERROR
async def test_v2_migration(
async def test_v4_migration(
hass: HomeAssistant,
mock_lamarzocco: MagicMock,
) -> None:
"""Test v2 -> v3 Migration."""
"""Test v3 -> v4 Migration."""
entry_v2 = MockConfigEntry(
entry_v3 = MockConfigEntry(
domain=DOMAIN,
version=2,
version=3,
unique_id=mock_lamarzocco.serial_number,
data={
**USER_INPUT,
CONF_HOST: "192.168.1.24",
CONF_NAME: "La Marzocco",
CONF_MODEL: ModelName.GS3_MP.value,
CONF_MAC: "aa:bb:cc:dd:ee:ff",
CONF_ADDRESS: "000000000000",
CONF_TOKEN: "token",
},
)
entry_v2.add_to_hass(hass)
entry_v3.add_to_hass(hass)
assert await hass.config_entries.async_setup(entry_v2.entry_id)
assert entry_v2.state is ConfigEntryState.LOADED
assert entry_v2.version == 3
assert dict(entry_v2.data) == {
assert await hass.config_entries.async_setup(entry_v3.entry_id)
assert entry_v3.state is ConfigEntryState.LOADED
assert entry_v3.version == 4
assert dict(entry_v3.data) == {
**USER_INPUT,
CONF_MAC: "aa:bb:cc:dd:ee:ff",
CONF_TOKEN: None,
CONF_ADDRESS: "000000000000",
CONF_TOKEN: "token",
CONF_INSTALLATION_KEY: MOCK_INSTALLATION_KEY,
}
async def test_migration_errors(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_cloud_client: MagicMock,
mock_lamarzocco: MagicMock,
) -> None:
"""Test errors during migration."""
mock_cloud_client.list_things.side_effect = RequestNotSuccessful("Error")
mock_cloud_client.async_register_client.side_effect = RequestNotSuccessful("Error")
entry_v2 = MockConfigEntry(
entry_v3 = MockConfigEntry(
domain=DOMAIN,
version=2,
version=3,
unique_id=mock_lamarzocco.serial_number,
data={
**USER_INPUT,
CONF_MACHINE: mock_lamarzocco.serial_number,
CONF_ADDRESS: "000000000000",
CONF_TOKEN: "token",
},
)
entry_v2.add_to_hass(hass)
entry_v3.add_to_hass(hass)
assert not await hass.config_entries.async_setup(entry_v2.entry_id)
assert entry_v2.state is ConfigEntryState.MIGRATION_ERROR
assert not await hass.config_entries.async_setup(entry_v3.entry_id)
assert entry_v3.state is ConfigEntryState.MIGRATION_ERROR
async def test_config_flow_entry_migration_downgrade(
hass: HomeAssistant,
) -> None:
"""Test that config entry fails setup if the version is from the future."""
entry = MockConfigEntry(domain=DOMAIN, version=4)
entry = MockConfigEntry(domain=DOMAIN, version=5)
entry.add_to_hass(hass)
assert not await hass.config_entries.async_setup(entry.entry_id)