diff --git a/build.json b/build.json index 43e23d32094..49cee1ff280 100644 --- a/build.json +++ b/build.json @@ -1,11 +1,11 @@ { "image": "homeassistant/{arch}-homeassistant", "build_from": { - "aarch64": "homeassistant/aarch64-homeassistant-base:2020.10.1", - "armhf": "homeassistant/armhf-homeassistant-base:2020.10.1", - "armv7": "homeassistant/armv7-homeassistant-base:2020.10.1", - "amd64": "homeassistant/amd64-homeassistant-base:2020.10.1", - "i386": "homeassistant/i386-homeassistant-base:2020.10.1" + "aarch64": "homeassistant/aarch64-homeassistant-base:2020.11.2", + "armhf": "homeassistant/armhf-homeassistant-base:2020.11.2", + "armv7": "homeassistant/armv7-homeassistant-base:2020.11.2", + "amd64": "homeassistant/amd64-homeassistant-base:2020.11.2", + "i386": "homeassistant/i386-homeassistant-base:2020.11.2" }, "labels": { "io.hass.type": "core" diff --git a/homeassistant/components/iqvia/__init__.py b/homeassistant/components/iqvia/__init__.py index d730b406896..db0df3a073b 100644 --- a/homeassistant/components/iqvia/__init__.py +++ b/homeassistant/components/iqvia/__init__.py @@ -155,6 +155,9 @@ class IQVIAEntity(CoordinatorEntity): @callback def _handle_coordinator_update(self) -> None: """Handle updated data from the coordinator.""" + if not self.coordinator.last_update_success: + return + self.update_from_latest_data() self.async_write_ha_state() diff --git a/homeassistant/components/iqvia/sensor.py b/homeassistant/components/iqvia/sensor.py index e53ee96b1c4..48ec1cf97b1 100644 --- a/homeassistant/components/iqvia/sensor.py +++ b/homeassistant/components/iqvia/sensor.py @@ -105,6 +105,7 @@ class ForecastSensor(IQVIAEntity): def update_from_latest_data(self): """Update the sensor.""" data = self.coordinator.data.get("Location") + if not data or not data.get("periods"): return @@ -142,6 +143,9 @@ class IndexSensor(IQVIAEntity): @callback def update_from_latest_data(self): """Update the sensor.""" + if not self.coordinator.last_update_success: + return + try: if self._type in (TYPE_ALLERGY_TODAY, TYPE_ALLERGY_TOMORROW): data = self.coordinator.data.get("Location") diff --git a/homeassistant/components/kodi/config_flow.py b/homeassistant/components/kodi/config_flow.py index c11255aba87..c48e4564f92 100644 --- a/homeassistant/components/kodi/config_flow.py +++ b/homeassistant/components/kodi/config_flow.py @@ -104,7 +104,10 @@ class KodiConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): self._host = discovery_info["host"] self._port = int(discovery_info["port"]) self._name = discovery_info["hostname"][: -len(".local.")] - uuid = discovery_info["properties"]["uuid"] + uuid = discovery_info["properties"].get("uuid") + if not uuid: + return self.async_abort(reason="no_uuid") + self._discovery_name = discovery_info["name"] await self.async_set_unique_id(uuid) diff --git a/homeassistant/components/kodi/strings.json b/homeassistant/components/kodi/strings.json index cf2f265f577..f1bb5342903 100644 --- a/homeassistant/components/kodi/strings.json +++ b/homeassistant/components/kodi/strings.json @@ -37,7 +37,8 @@ "already_configured": "[%key:common::config_flow::abort::already_configured_device%]", "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]", "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", - "unknown": "[%key:common::config_flow::error::unknown%]" + "unknown": "[%key:common::config_flow::error::unknown%]", + "no_uuid": "Kodi instance does not have a unique id. This is most likely due to an old Kodi version (17.x or below). You can configure the integration manually or upgrade to a more recent Kodi version." } }, "device_automation": { diff --git a/homeassistant/components/notion/__init__.py b/homeassistant/components/notion/__init__.py index 296eb34934b..007f18525fe 100644 --- a/homeassistant/components/notion/__init__.py +++ b/homeassistant/components/notion/__init__.py @@ -163,14 +163,17 @@ class NotionEntity(CoordinatorEntity): self._sensor_id = sensor_id self._state = None self._system_id = system_id - self._task_id = task_id + self._unique_id = ( + f'{sensor_id}_{self.coordinator.data["tasks"][task_id]["task_type"]}' + ) + self.task_id = task_id @property def available(self) -> bool: """Return True if entity is available.""" return ( self.coordinator.last_update_success - and self._task_id in self.coordinator.data["tasks"] + and self.task_id in self.coordinator.data["tasks"] ) @property @@ -207,8 +210,7 @@ class NotionEntity(CoordinatorEntity): @property def unique_id(self) -> str: """Return a unique, unchanging string that represents this entity.""" - task = self.coordinator.data["tasks"][self._task_id] - return f'{self._sensor_id}_{task["task_type"]}' + return self._unique_id async def _async_update_bridge_id(self) -> None: """Update the entity's bridge ID if it has changed. @@ -249,8 +251,10 @@ class NotionEntity(CoordinatorEntity): @callback def _handle_coordinator_update(self): """Respond to a DataUpdateCoordinator update.""" - self.hass.async_create_task(self._async_update_bridge_id()) - self._async_update_from_latest_data() + if self.task_id in self.coordinator.data["tasks"]: + self.hass.async_create_task(self._async_update_bridge_id()) + self._async_update_from_latest_data() + self.async_write_ha_state() async def async_added_to_hass(self): diff --git a/homeassistant/components/notion/binary_sensor.py b/homeassistant/components/notion/binary_sensor.py index b8fd96fabc5..a198903b99a 100644 --- a/homeassistant/components/notion/binary_sensor.py +++ b/homeassistant/components/notion/binary_sensor.py @@ -77,7 +77,7 @@ class NotionBinarySensor(NotionEntity, BinarySensorEntity): @callback def _async_update_from_latest_data(self) -> None: """Fetch new state data for the sensor.""" - task = self.coordinator.data["tasks"][self._task_id] + task = self.coordinator.data["tasks"][self.task_id] if "value" in task["status"]: self._state = task["status"]["value"] @@ -87,7 +87,7 @@ class NotionBinarySensor(NotionEntity, BinarySensorEntity): @property def is_on(self) -> bool: """Return whether the sensor is on or off.""" - task = self.coordinator.data["tasks"][self._task_id] + task = self.coordinator.data["tasks"][self.task_id] if task["task_type"] == SENSOR_BATTERY: return self._state == "critical" diff --git a/homeassistant/components/notion/sensor.py b/homeassistant/components/notion/sensor.py index 091dcd324dc..8f7337d200f 100644 --- a/homeassistant/components/notion/sensor.py +++ b/homeassistant/components/notion/sensor.py @@ -79,7 +79,7 @@ class NotionSensor(NotionEntity): @callback def _async_update_from_latest_data(self) -> None: """Fetch new state data for the sensor.""" - task = self.coordinator.data["tasks"][self._task_id] + task = self.coordinator.data["tasks"][self.task_id] if task["task_type"] == SENSOR_TEMPERATURE: self._state = round(float(task["status"]["value"]), 1) diff --git a/homeassistant/components/plex/__init__.py b/homeassistant/components/plex/__init__.py index 32ee5c56f64..e4f4f80dcfa 100644 --- a/homeassistant/components/plex/__init__.py +++ b/homeassistant/components/plex/__init__.py @@ -227,7 +227,7 @@ async def async_setup_entry(hass, entry): play_on_sonos_schema = vol.Schema( { vol.Required(ATTR_ENTITY_ID): cv.entity_id, - vol.Required(ATTR_MEDIA_CONTENT_ID): str, + vol.Required(ATTR_MEDIA_CONTENT_ID): cv.string, vol.Optional(ATTR_MEDIA_CONTENT_TYPE): vol.In("music"), } ) diff --git a/homeassistant/components/sleepiq/manifest.json b/homeassistant/components/sleepiq/manifest.json index 44e519f57da..0f5064f3264 100644 --- a/homeassistant/components/sleepiq/manifest.json +++ b/homeassistant/components/sleepiq/manifest.json @@ -2,6 +2,6 @@ "domain": "sleepiq", "name": "SleepIQ", "documentation": "https://www.home-assistant.io/integrations/sleepiq", - "requirements": ["sleepyq==0.7"], + "requirements": ["sleepyq==0.8.1"], "codeowners": [] } diff --git a/homeassistant/const.py b/homeassistant/const.py index 81b8482d21d..927773be73d 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 118 -PATCH_VERSION = "2" +PATCH_VERSION = "3" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER = (3, 7, 1) diff --git a/requirements_all.txt b/requirements_all.txt index cb8235a01cf..fc26d7f9f91 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2033,7 +2033,7 @@ skybellpy==0.6.1 slackclient==2.5.0 # homeassistant.components.sleepiq -sleepyq==0.7 +sleepyq==0.8.1 # homeassistant.components.xmpp slixmpp==1.5.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 45f37f2b18c..f0ab950c6dd 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -978,7 +978,7 @@ simplisafe-python==9.6.0 slackclient==2.5.0 # homeassistant.components.sleepiq -sleepyq==0.7 +sleepyq==0.8.1 # homeassistant.components.smart_meter_texas smart-meter-texas==0.4.0 diff --git a/tests/components/kodi/test_config_flow.py b/tests/components/kodi/test_config_flow.py index 9b010f3ed42..9e892033786 100644 --- a/tests/components/kodi/test_config_flow.py +++ b/tests/components/kodi/test_config_flow.py @@ -11,6 +11,7 @@ from homeassistant.components.kodi.const import DEFAULT_TIMEOUT, DOMAIN from .util import ( TEST_CREDENTIALS, TEST_DISCOVERY, + TEST_DISCOVERY_WO_UUID, TEST_HOST, TEST_IMPORT, TEST_WS_PORT, @@ -573,6 +574,16 @@ async def test_discovery_updates_unique_id(hass): assert entry.data["name"] == "hostname" +async def test_discovery_without_unique_id(hass): + """Test a discovery flow with no unique id aborts.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": "zeroconf"}, data=TEST_DISCOVERY_WO_UUID + ) + + assert result["type"] == "abort" + assert result["reason"] == "no_uuid" + + async def test_form_import(hass): """Test we get the form with import source.""" with patch( diff --git a/tests/components/kodi/util.py b/tests/components/kodi/util.py index 5a47ea88631..edd6950d76e 100644 --- a/tests/components/kodi/util.py +++ b/tests/components/kodi/util.py @@ -24,6 +24,16 @@ TEST_DISCOVERY = { } +TEST_DISCOVERY_WO_UUID = { + "host": "1.1.1.1", + "port": 8080, + "hostname": "hostname.local.", + "type": "_xbmc-jsonrpc-h._tcp.local.", + "name": "hostname._xbmc-jsonrpc-h._tcp.local.", + "properties": {}, +} + + TEST_IMPORT = { "name": "name", "host": "1.1.1.1",