From 649ec2fc8ecf1b64b4896fe1b406548638fae87d Mon Sep 17 00:00:00 2001 From: elmurato <1382097+elmurato@users.noreply.github.com> Date: Fri, 28 Feb 2020 22:39:46 +0100 Subject: [PATCH 1/6] Fixed TypeError with old server versions (#32329) --- homeassistant/components/minecraft_server/__init__.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/homeassistant/components/minecraft_server/__init__.py b/homeassistant/components/minecraft_server/__init__.py index 789e4d8f1b83f9..a025c44e33c59d 100644 --- a/homeassistant/components/minecraft_server/__init__.py +++ b/homeassistant/components/minecraft_server/__init__.py @@ -47,7 +47,7 @@ async def async_setup_entry(hass: HomeAssistantType, config_entry: ConfigEntry) await server.async_update() server.start_periodic_update() - # Set up platform(s). + # Set up platforms. for platform in PLATFORMS: hass.async_create_task( hass.config_entries.async_forward_entry_setup(config_entry, platform) @@ -103,7 +103,6 @@ def __init__( self._mc_status = MCStatus(self.host, self.port) # Data provided by 3rd party library - self.description = None self.version = None self.protocol_version = None self.latency_time = None @@ -168,7 +167,6 @@ async def _async_status_request(self) -> None: ) # Got answer to request, update properties. - self.description = status_response.description["text"] self.version = status_response.version.name self.protocol_version = status_response.version.protocol self.players_online = status_response.players.online @@ -185,7 +183,6 @@ async def _async_status_request(self) -> None: self._last_status_request_failed = False except OSError as error: # No answer to request, set all properties to unknown. - self.description = None self.version = None self.protocol_version = None self.players_online = None From fab55b0ea2f7f2a32c1b0435416e2addac9d5b7c Mon Sep 17 00:00:00 2001 From: mezz64 <2854333+mezz64@users.noreply.github.com> Date: Sun, 1 Mar 2020 09:05:37 -0500 Subject: [PATCH 2/6] Bump pyeight to 0.1.4 (#32363) --- homeassistant/components/eight_sleep/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/eight_sleep/manifest.json b/homeassistant/components/eight_sleep/manifest.json index 6372967b42b90c..ac7a11eed3c844 100644 --- a/homeassistant/components/eight_sleep/manifest.json +++ b/homeassistant/components/eight_sleep/manifest.json @@ -2,7 +2,7 @@ "domain": "eight_sleep", "name": "Eight Sleep", "documentation": "https://www.home-assistant.io/integrations/eight_sleep", - "requirements": ["pyeight==0.1.3"], + "requirements": ["pyeight==0.1.4"], "dependencies": [], "codeowners": ["@mezz64"] } diff --git a/requirements_all.txt b/requirements_all.txt index 52f06eda1bfab4..3cae53619bf0f1 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1226,7 +1226,7 @@ pyeconet==0.0.11 pyedimax==0.2.1 # homeassistant.components.eight_sleep -pyeight==0.1.3 +pyeight==0.1.4 # homeassistant.components.emby pyemby==1.6 From 08f5b49dc49b03bf96fce78f741d7a1f1d0f3626 Mon Sep 17 00:00:00 2001 From: mezz64 <2854333+mezz64@users.noreply.github.com> Date: Mon, 2 Mar 2020 21:01:39 -0500 Subject: [PATCH 3/6] Catch Eight Sleep API errors, don't round None type (#32410) * Catch API errors, don't round None type * Specify error type --- .../components/eight_sleep/sensor.py | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/eight_sleep/sensor.py b/homeassistant/components/eight_sleep/sensor.py index af6de2657ce0c8..f0fc4b5d1d653d 100644 --- a/homeassistant/components/eight_sleep/sensor.py +++ b/homeassistant/components/eight_sleep/sensor.py @@ -261,14 +261,26 @@ def device_state_attributes(self): bed_temp = None if "current" in self._sensor_root: - state_attr[ATTR_RESP_RATE] = round(self._attr["resp_rate"], 2) - state_attr[ATTR_HEART_RATE] = round(self._attr["heart_rate"], 2) + try: + state_attr[ATTR_RESP_RATE] = round(self._attr["resp_rate"], 2) + except TypeError: + state_attr[ATTR_RESP_RATE] = None + try: + state_attr[ATTR_HEART_RATE] = round(self._attr["heart_rate"], 2) + except TypeError: + state_attr[ATTR_HEART_RATE] = None state_attr[ATTR_SLEEP_STAGE] = self._attr["stage"] state_attr[ATTR_ROOM_TEMP] = room_temp state_attr[ATTR_BED_TEMP] = bed_temp elif "last" in self._sensor_root: - state_attr[ATTR_AVG_RESP_RATE] = round(self._attr["resp_rate"], 2) - state_attr[ATTR_AVG_HEART_RATE] = round(self._attr["heart_rate"], 2) + try: + state_attr[ATTR_AVG_RESP_RATE] = round(self._attr["resp_rate"], 2) + except TypeError: + state_attr[ATTR_AVG_RESP_RATE] = None + try: + state_attr[ATTR_AVG_HEART_RATE] = round(self._attr["heart_rate"], 2) + except TypeError: + state_attr[ATTR_AVG_HEART_RATE] = None state_attr[ATTR_AVG_ROOM_TEMP] = room_temp state_attr[ATTR_AVG_BED_TEMP] = bed_temp From 815502044e4d7d6634af5651dd4dcc42c141a040 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 2 Mar 2020 17:59:32 -0800 Subject: [PATCH 4/6] Coronavirus updates (#32417) * Sort countries alphabetically * Update sensor name * Add migration to stable unique IDs * Update sensor.py --- .../components/coronavirus/__init__.py | 23 +++++++- .../components/coronavirus/config_flow.py | 6 +- .../components/coronavirus/sensor.py | 4 +- homeassistant/helpers/entity_registry.py | 20 ++++++- tests/components/coronavirus/test_init.py | 55 +++++++++++++++++++ 5 files changed, 100 insertions(+), 8 deletions(-) create mode 100644 tests/components/coronavirus/test_init.py diff --git a/homeassistant/components/coronavirus/__init__.py b/homeassistant/components/coronavirus/__init__.py index 95c3cd1c024eb6..d5dbcd9f3f4e92 100644 --- a/homeassistant/components/coronavirus/__init__.py +++ b/homeassistant/components/coronavirus/__init__.py @@ -8,8 +8,8 @@ import coronavirus from homeassistant.config_entries import ConfigEntry -from homeassistant.core import HomeAssistant -from homeassistant.helpers import aiohttp_client, update_coordinator +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers import aiohttp_client, entity_registry, update_coordinator from .const import DOMAIN @@ -25,6 +25,23 @@ async def async_setup(hass: HomeAssistant, config: dict): async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): """Set up Coronavirus from a config entry.""" + if isinstance(entry.data["country"], int): + hass.config_entries.async_update_entry( + entry, data={**entry.data, "country": entry.title} + ) + + @callback + def _async_migrator(entity_entry: entity_registry.RegistryEntry): + """Migrate away from unstable ID.""" + country, info_type = entity_entry.unique_id.rsplit("-", 1) + if not country.isnumeric(): + return None + return {"new_unique_id": f"{entry.title}-{info_type}"} + + await entity_registry.async_migrate_entries( + hass, entry.entry_id, _async_migrator + ) + for component in PLATFORMS: hass.async_create_task( hass.config_entries.async_forward_entry_setup(entry, component) @@ -56,7 +73,7 @@ async def async_get_cases(): try: with async_timeout.timeout(10): return { - case.id: case + case.country: case for case in await coronavirus.get_cases( aiohttp_client.async_get_clientsession(hass) ) diff --git a/homeassistant/components/coronavirus/config_flow.py b/homeassistant/components/coronavirus/config_flow.py index 59d25e16709275..4a313a6837ffa9 100644 --- a/homeassistant/components/coronavirus/config_flow.py +++ b/homeassistant/components/coronavirus/config_flow.py @@ -26,8 +26,10 @@ async def async_step_user(self, user_input=None): if self._options is None: self._options = {OPTION_WORLDWIDE: "Worldwide"} coordinator = await get_coordinator(self.hass) - for case_id in sorted(coordinator.data): - self._options[case_id] = coordinator.data[case_id].country + for case in sorted( + coordinator.data.values(), key=lambda case: case.country + ): + self._options[case.country] = case.country if user_input is not None: return self.async_create_entry( diff --git a/homeassistant/components/coronavirus/sensor.py b/homeassistant/components/coronavirus/sensor.py index 770ab78b43ea17..20f188964317d8 100644 --- a/homeassistant/components/coronavirus/sensor.py +++ b/homeassistant/components/coronavirus/sensor.py @@ -25,9 +25,9 @@ class CoronavirusSensor(Entity): def __init__(self, coordinator, country, info_type): """Initialize coronavirus sensor.""" if country == OPTION_WORLDWIDE: - self.name = f"Worldwide {info_type}" + self.name = f"Worldwide Coronavirus {info_type}" else: - self.name = f"{coordinator.data[country].country} {info_type}" + self.name = f"{coordinator.data[country].country} Coronavirus {info_type}" self.unique_id = f"{country}-{info_type}" self.coordinator = coordinator self.country = country diff --git a/homeassistant/helpers/entity_registry.py b/homeassistant/helpers/entity_registry.py index 5996fb6eaf7080..87383d45635134 100644 --- a/homeassistant/helpers/entity_registry.py +++ b/homeassistant/helpers/entity_registry.py @@ -11,7 +11,7 @@ from collections import OrderedDict from itertools import chain import logging -from typing import TYPE_CHECKING, Any, Dict, Iterable, List, Optional, cast +from typing import TYPE_CHECKING, Any, Callable, Dict, Iterable, List, Optional, cast import attr @@ -560,3 +560,21 @@ def _write_unavailable_states(_: Event) -> None: states.async_set(entry.entity_id, STATE_UNAVAILABLE, attrs) hass.bus.async_listen(EVENT_HOMEASSISTANT_START, _write_unavailable_states) + + +async def async_migrate_entries( + hass: HomeAssistantType, + config_entry_id: str, + entry_callback: Callable[[RegistryEntry], Optional[dict]], +) -> None: + """Migrator of unique IDs.""" + ent_reg = await async_get_registry(hass) + + for entry in ent_reg.entities.values(): + if entry.config_entry_id != config_entry_id: + continue + + updates = entry_callback(entry) + + if updates is not None: + ent_reg.async_update_entity(entry.entity_id, **updates) # type: ignore diff --git a/tests/components/coronavirus/test_init.py b/tests/components/coronavirus/test_init.py new file mode 100644 index 00000000000000..05a14f2f296517 --- /dev/null +++ b/tests/components/coronavirus/test_init.py @@ -0,0 +1,55 @@ +"""Test init of Coronavirus integration.""" +from asynctest import Mock, patch + +from homeassistant.components.coronavirus.const import DOMAIN, OPTION_WORLDWIDE +from homeassistant.helpers import entity_registry +from homeassistant.setup import async_setup_component + +from tests.common import MockConfigEntry, mock_registry + + +async def test_migration(hass): + """Test that we can migrate coronavirus to stable unique ID.""" + nl_entry = MockConfigEntry(domain=DOMAIN, title="Netherlands", data={"country": 34}) + nl_entry.add_to_hass(hass) + worldwide_entry = MockConfigEntry( + domain=DOMAIN, title="Worldwide", data={"country": OPTION_WORLDWIDE} + ) + worldwide_entry.add_to_hass(hass) + mock_registry( + hass, + { + "sensor.netherlands_confirmed": entity_registry.RegistryEntry( + entity_id="sensor.netherlands_confirmed", + unique_id="34-confirmed", + platform="coronavirus", + config_entry_id=nl_entry.entry_id, + ), + "sensor.worldwide_confirmed": entity_registry.RegistryEntry( + entity_id="sensor.worldwide_confirmed", + unique_id="__worldwide-confirmed", + platform="coronavirus", + config_entry_id=worldwide_entry.entry_id, + ), + }, + ) + with patch( + "coronavirus.get_cases", + return_value=[ + Mock(country="Netherlands", confirmed=10, recovered=8, deaths=1, current=1), + Mock(country="Germany", confirmed=1, recovered=0, deaths=0, current=0), + ], + ): + assert await async_setup_component(hass, DOMAIN, {}) + await hass.async_block_till_done() + + ent_reg = await entity_registry.async_get_registry(hass) + + sensor_nl = ent_reg.async_get("sensor.netherlands_confirmed") + assert sensor_nl.unique_id == "Netherlands-confirmed" + + sensor_worldwide = ent_reg.async_get("sensor.worldwide_confirmed") + assert sensor_worldwide.unique_id == "__worldwide-confirmed" + + assert hass.states.get("sensor.netherlands_confirmed").state == "10" + assert hass.states.get("sensor.worldwide_confirmed").state == "11" From d6c15d2f45da136c164e95841b75da020869bfe8 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 2 Mar 2020 18:04:07 -0800 Subject: [PATCH 5/6] Bumped version to 0.106.4 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 473fe1e3aced7f..fd4ac8cc1a168e 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 106 -PATCH_VERSION = "3" +PATCH_VERSION = "4" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER = (3, 7, 0) From bfaad973182df6aae1506547f12f949a3815045c Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 2 Mar 2020 18:10:38 -0800 Subject: [PATCH 6/6] Add unique ID to coronavirus (#32423) --- homeassistant/components/coronavirus/__init__.py | 3 +++ homeassistant/components/coronavirus/config_flow.py | 2 ++ homeassistant/components/coronavirus/strings.json | 3 +++ tests/components/coronavirus/test_config_flow.py | 2 +- tests/components/coronavirus/test_init.py | 3 +++ 5 files changed, 12 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/coronavirus/__init__.py b/homeassistant/components/coronavirus/__init__.py index d5dbcd9f3f4e92..04976a1e4c58d1 100644 --- a/homeassistant/components/coronavirus/__init__.py +++ b/homeassistant/components/coronavirus/__init__.py @@ -42,6 +42,9 @@ def _async_migrator(entity_entry: entity_registry.RegistryEntry): hass, entry.entry_id, _async_migrator ) + if not entry.unique_id: + hass.config_entries.async_update_entry(entry, unique_id=entry.data["country"]) + for component in PLATFORMS: hass.async_create_task( hass.config_entries.async_forward_entry_setup(entry, component) diff --git a/homeassistant/components/coronavirus/config_flow.py b/homeassistant/components/coronavirus/config_flow.py index 4a313a6837ffa9..49183dd028eeba 100644 --- a/homeassistant/components/coronavirus/config_flow.py +++ b/homeassistant/components/coronavirus/config_flow.py @@ -32,6 +32,8 @@ async def async_step_user(self, user_input=None): self._options[case.country] = case.country if user_input is not None: + await self.async_set_unique_id(user_input["country"]) + self._abort_if_unique_id_configured() return self.async_create_entry( title=self._options[user_input["country"]], data=user_input ) diff --git a/homeassistant/components/coronavirus/strings.json b/homeassistant/components/coronavirus/strings.json index 13cd5f04012f49..fd4873c808ccae 100644 --- a/homeassistant/components/coronavirus/strings.json +++ b/homeassistant/components/coronavirus/strings.json @@ -8,6 +8,9 @@ "country": "Country" } } + }, + "abort": { + "already_configured": "This country is already configured." } } } diff --git a/tests/components/coronavirus/test_config_flow.py b/tests/components/coronavirus/test_config_flow.py index 6d940d8e53da07..ef04d0df07ae18 100644 --- a/tests/components/coronavirus/test_config_flow.py +++ b/tests/components/coronavirus/test_config_flow.py @@ -22,9 +22,9 @@ async def test_form(hass): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], {"country": OPTION_WORLDWIDE}, ) - assert result2["type"] == "create_entry" assert result2["title"] == "Worldwide" + assert result2["result"].unique_id == OPTION_WORLDWIDE assert result2["data"] == { "country": OPTION_WORLDWIDE, } diff --git a/tests/components/coronavirus/test_init.py b/tests/components/coronavirus/test_init.py index 05a14f2f296517..57293635570f16 100644 --- a/tests/components/coronavirus/test_init.py +++ b/tests/components/coronavirus/test_init.py @@ -53,3 +53,6 @@ async def test_migration(hass): assert hass.states.get("sensor.netherlands_confirmed").state == "10" assert hass.states.get("sensor.worldwide_confirmed").state == "11" + + assert nl_entry.unique_id == "Netherlands" + assert worldwide_entry.unique_id == OPTION_WORLDWIDE