From e0367dc721619a662b4a1e7cbb1e2f4085e01b5f Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 24 Jun 2020 21:35:02 +0200 Subject: [PATCH 01/47] Bumped version to 0.112.0b0 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index c95c6ec48cc..c2a725f4d82 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 112 -PATCH_VERSION = "0.dev0" +PATCH_VERSION = "0b0" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER = (3, 7, 0) From 67868220eb22a19c17058154949e6dbe6bf1beb7 Mon Sep 17 00:00:00 2001 From: Alexei Chetroi Date: Wed, 24 Jun 2020 17:52:56 -0400 Subject: [PATCH 02/47] Handle Centralite Pearl thermostat modes (#37065) --- homeassistant/components/zha/climate.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/homeassistant/components/zha/climate.py b/homeassistant/components/zha/climate.py index fc57b46ec34..7e2a0e147a7 100644 --- a/homeassistant/components/zha/climate.py +++ b/homeassistant/components/zha/climate.py @@ -587,3 +587,13 @@ class ZenWithinThermostat(Thermostat): if self.hvac_mode != HVAC_MODE_OFF: return CURRENT_HVAC_IDLE return CURRENT_HVAC_OFF + + +@STRICT_MATCH( + channel_names=CHANNEL_THERMOSTAT, + aux_channels=CHANNEL_FAN, + manufacturers="Centralite", + models="3157100", +) +class CentralitePearl(ZenWithinThermostat): + """Centralite Pearl Thermostat implementation.""" From 1e4c94b69c4dcde34a1b5aaea0870eaf29337b76 Mon Sep 17 00:00:00 2001 From: Alexei Chetroi Date: Wed, 24 Jun 2020 17:54:11 -0400 Subject: [PATCH 03/47] Don't reset multiplier/divisor on failures (#37066) If SmartEnergy.Metering channels fails to get multiplier/divisor when initializing, then keep the old values instead of resetting to 1. --- .../components/zha/core/channels/smartenergy.py | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/zha/core/channels/smartenergy.py b/homeassistant/components/zha/core/channels/smartenergy.py index 2e9b69be069..7b12411b84f 100644 --- a/homeassistant/components/zha/core/channels/smartenergy.py +++ b/homeassistant/components/zha/core/channels/smartenergy.py @@ -80,8 +80,8 @@ class Metering(ZigbeeChannel): ) -> None: """Initialize Metering.""" super().__init__(cluster, ch_pool) - self._divisor = None - self._multiplier = None + self._divisor = 1 + self._multiplier = 1 self._unit_enum = None self._format_spec = None @@ -114,14 +114,8 @@ class Metering(ZigbeeChannel): from_cache=from_cache, ) - self._divisor = results.get("divisor", 1) - if self._divisor == 0: - self._divisor = 1 - - self._multiplier = results.get("multiplier", 1) - if self._multiplier == 0: - self._multiplier = 1 - + self._divisor = results.get("divisor", self._divisor) + self._multiplier = results.get("multiplier", self._multiplier) self._unit_enum = results.get("unit_of_measure", 0x7F) # default to unknown fmting = results.get( From f90f4629ab483bb928848f7fafed1f327af8a365 Mon Sep 17 00:00:00 2001 From: RogerSelwyn Date: Thu, 25 Jun 2020 07:20:00 +0100 Subject: [PATCH 04/47] Fix geniushub spamming log with exceptions (#37067) --- homeassistant/components/geniushub/__init__.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/geniushub/__init__.py b/homeassistant/components/geniushub/__init__.py index 0b99224bf7f..16967fb265a 100644 --- a/homeassistant/components/geniushub/__init__.py +++ b/homeassistant/components/geniushub/__init__.py @@ -167,6 +167,7 @@ class GeniusBroker: self.hass = hass self.client = client self._hub_uid = hub_uid + self._connect_error = False @property def hub_uid(self) -> int: @@ -178,8 +179,19 @@ class GeniusBroker: """Update the geniushub client's data.""" try: await self.client.update() - except aiohttp.ClientResponseError as err: - _LOGGER.warning("Update failed, message is: %s", err) + if self._connect_error: + self._connect_error = False + _LOGGER.warning("Connection to geniushub re-established") + except ( + aiohttp.ClientResponseError, + aiohttp.client_exceptions.ClientConnectorError, + ) as err: + if not self._connect_error: + self._connect_error = True + _LOGGER.warning( + "Connection to geniushub failed (unable to update), message is: %s", + err, + ) return self.make_debug_log_entries() @@ -240,7 +252,6 @@ class GeniusDevice(GeniusEntity): @property def device_state_attributes(self) -> Dict[str, Any]: """Return the device state attributes.""" - attrs = {} attrs["assigned_zone"] = self._device.data["assignedZones"][0]["name"] if self._last_comms: From 7c63cfbbc7749f2e42d2669f194f9bfc392dd528 Mon Sep 17 00:00:00 2001 From: Alexei Chetroi Date: Thu, 25 Jun 2020 09:35:48 -0400 Subject: [PATCH 05/47] Use cached values for divisor/multiplier (#37070) --- homeassistant/components/zha/core/channels/homeautomation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/zha/core/channels/homeautomation.py b/homeassistant/components/zha/core/channels/homeautomation.py index 2601cf47573..d95180ce780 100644 --- a/homeassistant/components/zha/core/channels/homeautomation.py +++ b/homeassistant/components/zha/core/channels/homeautomation.py @@ -75,7 +75,7 @@ class ElectricalMeasurementChannel(ZigbeeChannel): async def async_initialize(self, from_cache): """Initialize channel.""" - await self.fetch_config(from_cache) + await self.fetch_config(True) await super().async_initialize(from_cache) async def fetch_config(self, from_cache): From 508afd3b90c5565365d95b9dad0ebb7ea9a513ca Mon Sep 17 00:00:00 2001 From: Eugene Prystupa Date: Thu, 25 Jun 2020 07:20:42 -0400 Subject: [PATCH 06/47] Clean up plum_lightpad (#37077) --- homeassistant/components/plum_lightpad/light.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/plum_lightpad/light.py b/homeassistant/components/plum_lightpad/light.py index 4a02e83de76..a94014ff1f9 100644 --- a/homeassistant/components/plum_lightpad/light.py +++ b/homeassistant/components/plum_lightpad/light.py @@ -1,4 +1,5 @@ """Support for Plum Lightpad lights.""" +import asyncio import logging from typing import Callable, List @@ -52,7 +53,7 @@ async def async_setup_entry( setup_entities(device) device_web_session = async_get_clientsession(hass, verify_ssl=False) - hass.loop.create_task( + asyncio.create_task( plum.discover( hass.loop, loadListener=new_load, From 5cbf77221a3559724dcfab74feaff5069df553cc Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 24 Jun 2020 18:14:50 -0700 Subject: [PATCH 07/47] Add logbook platforms (#37078) * Add logbook platforms * Fix logbook describe test --- homeassistant/components/alexa/__init__.py | 24 -------- homeassistant/components/alexa/logbook.py | 28 +++++++++ homeassistant/components/alexa/manifest.json | 13 +++- .../components/automation/__init__.py | 13 ---- .../components/automation/logbook.py | 23 ++++++++ .../components/automation/manifest.json | 9 ++- homeassistant/components/homekit/__init__.py | 24 -------- homeassistant/components/homekit/logbook.py | 28 +++++++++ .../components/homekit/manifest.json | 22 +++++-- homeassistant/components/logbook/__init__.py | 25 +++++--- homeassistant/components/script/__init__.py | 13 ---- homeassistant/components/script/logbook.py | 21 +++++++ homeassistant/components/script/manifest.json | 5 +- homeassistant/helpers/integration_platform.py | 16 ++++- tests/components/alexa/test_init.py | 2 + tests/components/automation/test_init.py | 2 + tests/components/homekit/test_init.py | 2 + tests/components/logbook/test_init.py | 59 ++++++++++++------- tests/components/script/test_init.py | 2 + 19 files changed, 215 insertions(+), 116 deletions(-) create mode 100644 homeassistant/components/alexa/logbook.py create mode 100644 homeassistant/components/automation/logbook.py create mode 100644 homeassistant/components/homekit/logbook.py create mode 100644 homeassistant/components/script/logbook.py diff --git a/homeassistant/components/alexa/__init__.py b/homeassistant/components/alexa/__init__.py index e8efa8a4752..7522b7e2d58 100644 --- a/homeassistant/components/alexa/__init__.py +++ b/homeassistant/components/alexa/__init__.py @@ -4,7 +4,6 @@ import logging import voluptuous as vol from homeassistant.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET, CONF_NAME -from homeassistant.core import callback from homeassistant.helpers import config_validation as cv, entityfilter from . import flash_briefings, intent, smart_home_http @@ -23,7 +22,6 @@ from .const import ( CONF_TITLE, CONF_UID, DOMAIN, - EVENT_ALEXA_SMART_HOME, ) _LOGGER = logging.getLogger(__name__) @@ -82,28 +80,6 @@ CONFIG_SCHEMA = vol.Schema( async def async_setup(hass, config): """Activate the Alexa component.""" - - @callback - def async_describe_logbook_event(event): - """Describe a logbook event.""" - data = event.data - entity_id = data["request"].get("entity_id") - - if entity_id: - state = hass.states.get(entity_id) - name = state.name if state else entity_id - message = f"send command {data['request']['namespace']}/{data['request']['name']} for {name}" - else: - message = ( - f"send command {data['request']['namespace']}/{data['request']['name']}" - ) - - return {"name": "Amazon Alexa", "message": message, "entity_id": entity_id} - - hass.components.logbook.async_describe_event( - DOMAIN, EVENT_ALEXA_SMART_HOME, async_describe_logbook_event - ) - if DOMAIN not in config: return True diff --git a/homeassistant/components/alexa/logbook.py b/homeassistant/components/alexa/logbook.py new file mode 100644 index 00000000000..efc188a7f8b --- /dev/null +++ b/homeassistant/components/alexa/logbook.py @@ -0,0 +1,28 @@ +"""Describe logbook events.""" +from homeassistant.core import callback + +from .const import DOMAIN, EVENT_ALEXA_SMART_HOME + + +@callback +def async_describe_events(hass, async_describe_event): + """Describe logbook events.""" + + @callback + def async_describe_logbook_event(event): + """Describe a logbook event.""" + data = event.data + entity_id = data["request"].get("entity_id") + + if entity_id: + state = hass.states.get(entity_id) + name = state.name if state else entity_id + message = f"send command {data['request']['namespace']}/{data['request']['name']} for {name}" + else: + message = ( + f"send command {data['request']['namespace']}/{data['request']['name']}" + ) + + return {"name": "Amazon Alexa", "message": message, "entity_id": entity_id} + + async_describe_event(DOMAIN, EVENT_ALEXA_SMART_HOME, async_describe_logbook_event) diff --git a/homeassistant/components/alexa/manifest.json b/homeassistant/components/alexa/manifest.json index 6144ccc6870..1ed91866cdc 100644 --- a/homeassistant/components/alexa/manifest.json +++ b/homeassistant/components/alexa/manifest.json @@ -2,7 +2,14 @@ "domain": "alexa", "name": "Amazon Alexa", "documentation": "https://www.home-assistant.io/integrations/alexa", - "dependencies": ["http"], - "after_dependencies": ["logbook", "camera"], - "codeowners": ["@home-assistant/cloud", "@ochlocracy"] + "dependencies": [ + "http" + ], + "after_dependencies": [ + "camera" + ], + "codeowners": [ + "@home-assistant/cloud", + "@ochlocracy" + ] } diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index 8b2c036034b..e5f2f611cdb 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -222,19 +222,6 @@ async def async_setup(hass, config): hass, DOMAIN, SERVICE_RELOAD, reload_service_handler, schema=vol.Schema({}) ) - @callback - def async_describe_logbook_event(event): - """Describe a logbook event.""" - return { - "name": event.data.get(ATTR_NAME), - "message": "has been triggered", - "entity_id": event.data.get(ATTR_ENTITY_ID), - } - - hass.components.logbook.async_describe_event( - DOMAIN, EVENT_AUTOMATION_TRIGGERED, async_describe_logbook_event - ) - return True diff --git a/homeassistant/components/automation/logbook.py b/homeassistant/components/automation/logbook.py new file mode 100644 index 00000000000..2e3ad2475fc --- /dev/null +++ b/homeassistant/components/automation/logbook.py @@ -0,0 +1,23 @@ +"""Describe logbook events.""" +from homeassistant.const import ATTR_ENTITY_ID, ATTR_NAME +from homeassistant.core import callback + +from . import DOMAIN, EVENT_AUTOMATION_TRIGGERED + + +@callback +def async_describe_events(hass, async_describe_event): # type: ignore + """Describe logbook events.""" + + @callback + def async_describe_logbook_event(event): # type: ignore + """Describe a logbook event.""" + return { + "name": event.data.get(ATTR_NAME), + "message": "has been triggered", + "entity_id": event.data.get(ATTR_ENTITY_ID), + } + + async_describe_event( + DOMAIN, EVENT_AUTOMATION_TRIGGERED, async_describe_logbook_event + ) diff --git a/homeassistant/components/automation/manifest.json b/homeassistant/components/automation/manifest.json index a93baa0528a..a8dc43844e0 100644 --- a/homeassistant/components/automation/manifest.json +++ b/homeassistant/components/automation/manifest.json @@ -2,7 +2,12 @@ "domain": "automation", "name": "Automation", "documentation": "https://www.home-assistant.io/integrations/automation", - "after_dependencies": ["device_automation", "logbook", "webhook"], - "codeowners": ["@home-assistant/core"], + "after_dependencies": [ + "device_automation", + "webhook" + ], + "codeowners": [ + "@home-assistant/core" + ], "quality_scale": "internal" } diff --git a/homeassistant/components/homekit/__init__.py b/homeassistant/components/homekit/__init__.py index 276a23b7354..c5a921a9dd2 100644 --- a/homeassistant/components/homekit/__init__.py +++ b/homeassistant/components/homekit/__init__.py @@ -21,7 +21,6 @@ from homeassistant.const import ( ATTR_BATTERY_CHARGING, ATTR_BATTERY_LEVEL, ATTR_ENTITY_ID, - ATTR_SERVICE, CONF_IP_ADDRESS, CONF_NAME, CONF_PORT, @@ -41,12 +40,10 @@ from .accessories import get_accessory from .aidmanager import AccessoryAidStorage from .const import ( AID_STORAGE, - ATTR_DISPLAY_NAME, ATTR_INTERGRATION, ATTR_MANUFACTURER, ATTR_MODEL, ATTR_SOFTWARE_VERSION, - ATTR_VALUE, BRIDGE_NAME, BRIDGE_SERIAL_NUMBER, CONF_ADVERTISE_IP, @@ -64,7 +61,6 @@ from .const import ( DEFAULT_PORT, DEFAULT_SAFE_MODE, DOMAIN, - EVENT_HOMEKIT_CHANGED, HOMEKIT, HOMEKIT_PAIRING_QR, HOMEKIT_PAIRING_QR_SECRET, @@ -325,26 +321,6 @@ def _async_register_events_and_services(hass: HomeAssistant): schema=RESET_ACCESSORY_SERVICE_SCHEMA, ) - @callback - def async_describe_logbook_event(event): - """Describe a logbook event.""" - data = event.data - entity_id = data.get(ATTR_ENTITY_ID) - value = data.get(ATTR_VALUE) - - value_msg = f" to {value}" if value else "" - message = f"send command {data[ATTR_SERVICE]}{value_msg} for {data[ATTR_DISPLAY_NAME]}" - - return { - "name": "HomeKit", - "message": message, - "entity_id": entity_id, - } - - hass.components.logbook.async_describe_event( - DOMAIN, EVENT_HOMEKIT_CHANGED, async_describe_logbook_event - ) - async def async_handle_homekit_service_start(service): """Handle start HomeKit service call.""" for entry_id in hass.data[DOMAIN]: diff --git a/homeassistant/components/homekit/logbook.py b/homeassistant/components/homekit/logbook.py new file mode 100644 index 00000000000..0ea5a5d542a --- /dev/null +++ b/homeassistant/components/homekit/logbook.py @@ -0,0 +1,28 @@ +"""Describe logbook events.""" +from homeassistant.const import ATTR_ENTITY_ID, ATTR_SERVICE +from homeassistant.core import callback + +from .const import ATTR_DISPLAY_NAME, ATTR_VALUE, DOMAIN, EVENT_HOMEKIT_CHANGED + + +@callback +def async_describe_events(hass, async_describe_event): + """Describe logbook events.""" + + @callback + def async_describe_logbook_event(event): + """Describe a logbook event.""" + data = event.data + entity_id = data.get(ATTR_ENTITY_ID) + value = data.get(ATTR_VALUE) + + value_msg = f" to {value}" if value else "" + message = f"send command {data[ATTR_SERVICE]}{value_msg} for {data[ATTR_DISPLAY_NAME]}" + + return { + "name": "HomeKit", + "message": message, + "entity_id": entity_id, + } + + async_describe_event(DOMAIN, EVENT_HOMEKIT_CHANGED, async_describe_logbook_event) diff --git a/homeassistant/components/homekit/manifest.json b/homeassistant/components/homekit/manifest.json index 985fcc1e799..8a5fc90ae07 100644 --- a/homeassistant/components/homekit/manifest.json +++ b/homeassistant/components/homekit/manifest.json @@ -2,9 +2,23 @@ "domain": "homekit", "name": "HomeKit", "documentation": "https://www.home-assistant.io/integrations/homekit", - "requirements": ["HAP-python==2.9.1","fnvhash==0.1.0","PyQRCode==1.2.1","base36==0.1.1","PyTurboJPEG==1.4.0"], - "dependencies": ["http", "camera", "ffmpeg"], - "after_dependencies": ["logbook", "zeroconf"], - "codeowners": ["@bdraco"], + "requirements": [ + "HAP-python==2.9.1", + "fnvhash==0.1.0", + "PyQRCode==1.2.1", + "base36==0.1.1", + "PyTurboJPEG==1.4.0" + ], + "dependencies": [ + "http", + "camera", + "ffmpeg" + ], + "after_dependencies": [ + "zeroconf" + ], + "codeowners": [ + "@bdraco" + ], "config_flow": true } diff --git a/homeassistant/components/logbook/__init__.py b/homeassistant/components/logbook/__init__.py index 13253300cf3..28d6c7fcd48 100644 --- a/homeassistant/components/logbook/__init__.py +++ b/homeassistant/components/logbook/__init__.py @@ -47,6 +47,9 @@ from homeassistant.helpers.entityfilter import ( convert_include_exclude_filter, generate_filter, ) +from homeassistant.helpers.integration_platform import ( + async_process_integration_platforms, +) from homeassistant.loader import bind_hass import homeassistant.util.dt as dt_util @@ -102,15 +105,9 @@ def async_log_entry(hass, name, message, domain=None, entity_id=None): hass.bus.async_fire(EVENT_LOGBOOK_ENTRY, data) -@bind_hass -def async_describe_event(hass, domain, event_name, describe_callback): - """Teach logbook how to describe a new event.""" - hass.data.setdefault(DOMAIN, {})[event_name] = (domain, describe_callback) - - async def async_setup(hass, config): """Logbook setup.""" - hass.data.setdefault(DOMAIN, {}) + hass.data[DOMAIN] = {} @callback def log_message(service): @@ -131,9 +128,23 @@ async def async_setup(hass, config): ) hass.services.async_register(DOMAIN, "log", log_message, schema=LOG_MESSAGE_SCHEMA) + + await async_process_integration_platforms(hass, DOMAIN, _process_logbook_platform) + return True +async def _process_logbook_platform(hass, domain, platform): + """Process a logbook platform.""" + + @callback + def _async_describe_event(domain, event_name, describe_callback): + """Teach logbook how to describe a new event.""" + hass.data[DOMAIN][event_name] = (domain, describe_callback) + + platform.async_describe_events(hass, _async_describe_event) + + class LogbookView(HomeAssistantView): """Handle logbook view requests.""" diff --git a/homeassistant/components/script/__init__.py b/homeassistant/components/script/__init__.py index b9043ff2f09..e80dcfa8027 100644 --- a/homeassistant/components/script/__init__.py +++ b/homeassistant/components/script/__init__.py @@ -188,19 +188,6 @@ async def async_setup(hass, config): DOMAIN, SERVICE_TOGGLE, toggle_service, schema=SCRIPT_TURN_ONOFF_SCHEMA ) - @callback - def async_describe_logbook_event(event): - """Describe the logbook event.""" - return { - "name": event.data.get(ATTR_NAME), - "message": "started", - "entity_id": event.data.get(ATTR_ENTITY_ID), - } - - hass.components.logbook.async_describe_event( - DOMAIN, EVENT_SCRIPT_STARTED, async_describe_logbook_event - ) - return True diff --git a/homeassistant/components/script/logbook.py b/homeassistant/components/script/logbook.py new file mode 100644 index 00000000000..72ff0d15fc7 --- /dev/null +++ b/homeassistant/components/script/logbook.py @@ -0,0 +1,21 @@ +"""Describe logbook events.""" +from homeassistant.const import ATTR_ENTITY_ID, ATTR_NAME +from homeassistant.core import callback + +from . import DOMAIN, EVENT_SCRIPT_STARTED + + +@callback +def async_describe_events(hass, async_describe_event): + """Describe logbook events.""" + + @callback + def async_describe_logbook_event(event): + """Describe the logbook event.""" + return { + "name": event.data.get(ATTR_NAME), + "message": "started", + "entity_id": event.data.get(ATTR_ENTITY_ID), + } + + async_describe_event(DOMAIN, EVENT_SCRIPT_STARTED, async_describe_logbook_event) diff --git a/homeassistant/components/script/manifest.json b/homeassistant/components/script/manifest.json index 9348469d258..b9d333ce553 100644 --- a/homeassistant/components/script/manifest.json +++ b/homeassistant/components/script/manifest.json @@ -2,7 +2,8 @@ "domain": "script", "name": "Scripts", "documentation": "https://www.home-assistant.io/integrations/script", - "after_dependencies": ["logbook"], - "codeowners": ["@home-assistant/core"], + "codeowners": [ + "@home-assistant/core" + ], "quality_scale": "internal" } diff --git a/homeassistant/helpers/integration_platform.py b/homeassistant/helpers/integration_platform.py index 01567c72c7b..93f3f3f7427 100644 --- a/homeassistant/helpers/integration_platform.py +++ b/homeassistant/helpers/integration_platform.py @@ -4,7 +4,7 @@ import logging from typing import Any, Awaitable, Callable from homeassistant.core import Event, HomeAssistant -from homeassistant.loader import IntegrationNotFound, async_get_integration, bind_hass +from homeassistant.loader import async_get_integration, bind_hass from homeassistant.setup import ATTR_COMPONENT, EVENT_COMPONENT_LOADED _LOGGER = logging.getLogger(__name__) @@ -21,10 +21,20 @@ async def async_process_integration_platforms( async def _process(component_name: str) -> None: """Process the intents of a component.""" + if "." in component_name: + return + + integration = await async_get_integration(hass, component_name) + try: - integration = await async_get_integration(hass, component_name) platform = integration.get_platform(platform_name) - except (IntegrationNotFound, ImportError): + except ImportError as err: + if f"{component_name}.{platform_name}" not in str(err): + _LOGGER.exception( + "Unexpected error importing %s/%s.py", + component_name, + platform_name, + ) return try: diff --git a/tests/components/alexa/test_init.py b/tests/components/alexa/test_init.py index f5071cf3f01..605ca96f190 100644 --- a/tests/components/alexa/test_init.py +++ b/tests/components/alexa/test_init.py @@ -8,7 +8,9 @@ from tests.components.logbook.test_init import MockLazyEventPartialState async def test_humanify_alexa_event(hass): """Test humanifying Alexa event.""" + hass.config.components.add("recorder") await async_setup_component(hass, "alexa", {}) + await async_setup_component(hass, "logbook", {}) hass.states.async_set("light.kitchen", "on", {"friendly_name": "Kitchen Light"}) entity_attr_cache = logbook.EntityAttributeCache(hass) diff --git a/tests/components/automation/test_init.py b/tests/components/automation/test_init.py index c41eb80d6f2..9af8a6591d9 100644 --- a/tests/components/automation/test_init.py +++ b/tests/components/automation/test_init.py @@ -1039,7 +1039,9 @@ async def test_extraction_functions(hass): async def test_logbook_humanify_automation_triggered_event(hass): """Test humanifying Automation Trigger event.""" + hass.config.components.add("recorder") await async_setup_component(hass, automation.DOMAIN, {}) + await async_setup_component(hass, "logbook", {}) entity_attr_cache = logbook.EntityAttributeCache(hass) event1, event2 = list( diff --git a/tests/components/homekit/test_init.py b/tests/components/homekit/test_init.py index 4db72ffb374..1fad563445b 100644 --- a/tests/components/homekit/test_init.py +++ b/tests/components/homekit/test_init.py @@ -15,8 +15,10 @@ from tests.components.logbook.test_init import MockLazyEventPartialState async def test_humanify_homekit_changed_event(hass, hk_driver): """Test humanifying HomeKit changed event.""" + hass.config.components.add("recorder") with patch("homeassistant.components.homekit.HomeKit"): assert await async_setup_component(hass, "homekit", {"homekit": {}}) + assert await async_setup_component(hass, "logbook", {}) entity_attr_cache = logbook.EntityAttributeCache(hass) event1, event2 = list( diff --git a/tests/components/logbook/test_init.py b/tests/components/logbook/test_init.py index 58c918477f8..03ef09b438d 100644 --- a/tests/components/logbook/test_init.py +++ b/tests/components/logbook/test_init.py @@ -36,8 +36,8 @@ from homeassistant.helpers.json import JSONEncoder from homeassistant.setup import async_setup_component, setup_component import homeassistant.util.dt as dt_util -from tests.async_mock import patch -from tests.common import get_test_home_assistant, init_recorder_component +from tests.async_mock import Mock, patch +from tests.common import get_test_home_assistant, init_recorder_component, mock_platform from tests.components.recorder.common import trigger_db_commit _LOGGER = logging.getLogger(__name__) @@ -1563,6 +1563,22 @@ async def test_logbook_view_period_entity(hass, hass_client): async def test_logbook_describe_event(hass, hass_client): """Test teaching logbook about a new event.""" await hass.async_add_executor_job(init_recorder_component, hass) + + def _describe(event): + """Describe an event.""" + return {"name": "Test Name", "message": "tested a message"} + + hass.config.components.add("fake_integration") + mock_platform( + hass, + "fake_integration.logbook", + Mock( + async_describe_events=lambda hass, async_describe_event: async_describe_event( + "test_domain", "some_event", _describe + ) + ), + ) + assert await async_setup_component(hass, "logbook", {}) with patch( "homeassistant.util.dt.utcnow", @@ -1574,12 +1590,6 @@ async def test_logbook_describe_event(hass, hass_client): hass.data[recorder.DATA_INSTANCE].block_till_done ) - def _describe(event): - """Describe an event.""" - return {"name": "Test Name", "message": "tested a message"} - - hass.components.logbook.async_describe_event("test_domain", "some_event", _describe) - client = await hass_client() response = await client.get("/api/logbook") results = await response.json() @@ -1597,6 +1607,26 @@ async def test_exclude_described_event(hass, hass_client): entity_id2 = "automation.included_rule" entity_id3 = "sensor.excluded_domain" + def _describe(event): + """Describe an event.""" + return { + "name": "Test Name", + "message": "tested a message", + "entity_id": event.data.get(ATTR_ENTITY_ID), + } + + def async_describe_events(hass, async_describe_event): + """Mock to describe events.""" + async_describe_event("automation", "some_automation_event", _describe) + async_describe_event("sensor", "some_event", _describe) + + hass.config.components.add("fake_integration") + mock_platform( + hass, + "fake_integration.logbook", + Mock(async_describe_events=async_describe_events), + ) + await hass.async_add_executor_job(init_recorder_component, hass) assert await async_setup_component( hass, @@ -1631,19 +1661,6 @@ async def test_exclude_described_event(hass, hass_client): hass.data[recorder.DATA_INSTANCE].block_till_done ) - def _describe(event): - """Describe an event.""" - return { - "name": "Test Name", - "message": "tested a message", - "entity_id": event.data.get(ATTR_ENTITY_ID), - } - - hass.components.logbook.async_describe_event( - "automation", "some_automation_event", _describe - ) - hass.components.logbook.async_describe_event("sensor", "some_event", _describe) - client = await hass_client() response = await client.get("/api/logbook") results = await response.json() diff --git a/tests/components/script/test_init.py b/tests/components/script/test_init.py index 9bcf0dc1be8..bb7340a08da 100644 --- a/tests/components/script/test_init.py +++ b/tests/components/script/test_init.py @@ -472,7 +472,9 @@ async def test_config(hass): async def test_logbook_humanify_script_started_event(hass): """Test humanifying script started event.""" + hass.config.components.add("recorder") await async_setup_component(hass, DOMAIN, {}) + await async_setup_component(hass, "logbook", {}) entity_attr_cache = logbook.EntityAttributeCache(hass) event1, event2 = list( From 9c90aaf8304468f3aab4fba380019e50e5d9ec89 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 24 Jun 2020 22:43:08 -0500 Subject: [PATCH 08/47] Ensure history states can be copied (#37081) The filter integration makes a copy of a state object obtained from history. --- homeassistant/components/history/__init__.py | 14 ++++++-- tests/components/history/test_init.py | 34 ++++++++++++++++++++ 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/history/__init__.py b/homeassistant/components/history/__init__.py index 9b81ffa08ed..f943c126d3e 100644 --- a/homeassistant/components/history/__init__.py +++ b/homeassistant/components/history/__init__.py @@ -616,7 +616,7 @@ class LazyState(State): self._last_updated = None self._context = None - @property + @property # type: ignore def attributes(self): """State attributes.""" if not self._attributes: @@ -628,13 +628,23 @@ class LazyState(State): self._attributes = {} return self._attributes - @property + @attributes.setter + def attributes(self, value): + """Set attributes.""" + self._attributes = value + + @property # type: ignore def context(self): """State context.""" if not self._context: self._context = Context(id=None) return self._context + @context.setter + def context(self, value): + """Set context.""" + self._context = value + @property # type: ignore def last_changed(self): """Last changed datetime.""" diff --git a/tests/components/history/test_init.py b/tests/components/history/test_init.py index 89e16ad0205..34b22481400 100644 --- a/tests/components/history/test_init.py +++ b/tests/components/history/test_init.py @@ -1,5 +1,6 @@ """The tests the History component.""" # pylint: disable=protected-access,invalid-name +from copy import copy from datetime import timedelta import json import unittest @@ -188,6 +189,39 @@ class TestComponentHistory(unittest.TestCase): assert states == hist[entity_id] + def test_ensure_state_can_be_copied(self): + """Ensure a state can pass though copy(). + + The filter integration uses copy() on states + from history. + """ + self.init_recorder() + entity_id = "sensor.test" + + def set_state(state): + """Set the state.""" + self.hass.states.set(entity_id, state) + wait_recording_done(self.hass) + return self.hass.states.get(entity_id) + + start = dt_util.utcnow() - timedelta(minutes=2) + point = start + timedelta(minutes=1) + + with patch( + "homeassistant.components.recorder.dt_util.utcnow", return_value=start + ): + set_state("1") + + with patch( + "homeassistant.components.recorder.dt_util.utcnow", return_value=point + ): + set_state("2") + + hist = history.get_last_state_changes(self.hass, 2, entity_id) + + assert copy(hist[entity_id][0]) == hist[entity_id][0] + assert copy(hist[entity_id][1]) == hist[entity_id][1] + def test_get_significant_states(self): """Test that only significant states are returned. From 654159d34dbaec0af103a6ce1a55f4dde2d9fe75 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 25 Jun 2020 14:17:31 +0200 Subject: [PATCH 09/47] Remove invalidation version from ZHA deprecated config options (#37089) --- homeassistant/components/zha/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/zha/__init__.py b/homeassistant/components/zha/__init__.py index 8a23c6fc20d..d5f76fa5e23 100644 --- a/homeassistant/components/zha/__init__.py +++ b/homeassistant/components/zha/__init__.py @@ -51,9 +51,9 @@ CONFIG_SCHEMA = vol.Schema( { DOMAIN: vol.Schema( vol.All( - cv.deprecated(CONF_USB_PATH, invalidation_version="0.112"), - cv.deprecated(CONF_BAUDRATE, invalidation_version="0.112"), - cv.deprecated(CONF_RADIO_TYPE, invalidation_version="0.112"), + cv.deprecated(CONF_USB_PATH), + cv.deprecated(CONF_BAUDRATE), + cv.deprecated(CONF_RADIO_TYPE), ZHA_CONFIG_SCHEMA, ), ), From 0698ae485013759005d1c12dbd2a479cdd5a9e30 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 26 Jun 2020 02:09:52 +0200 Subject: [PATCH 10/47] Fix missing service call context in multiple locations (#37094) --- homeassistant/components/alert/__init__.py | 4 +++- .../components/generic_thermostat/climate.py | 8 +++++-- homeassistant/components/group/cover.py | 24 ++++++++++++------- homeassistant/components/group/light.py | 19 ++++++++++++--- .../components/homeassistant/__init__.py | 4 +++- homeassistant/components/lifx/light.py | 4 +++- homeassistant/components/switch/light.py | 12 ++++++++-- homeassistant/components/tts/__init__.py | 6 ++++- .../components/universal/media_player.py | 2 +- tests/components/group/test_light.py | 6 ++--- 10 files changed, 66 insertions(+), 23 deletions(-) diff --git a/homeassistant/components/alert/__init__.py b/homeassistant/components/alert/__init__.py index 0ac4621cb0a..d85c13731b2 100644 --- a/homeassistant/components/alert/__init__.py +++ b/homeassistant/components/alert/__init__.py @@ -305,7 +305,9 @@ class Alert(ToggleEntity): _LOGGER.debug(msg_payload) for target in self._notifiers: - await self.hass.services.async_call(DOMAIN_NOTIFY, target, msg_payload) + await self.hass.services.async_call( + DOMAIN_NOTIFY, target, msg_payload, context=self._context + ) async def async_turn_on(self, **kwargs): """Async Unacknowledge alert.""" diff --git a/homeassistant/components/generic_thermostat/climate.py b/homeassistant/components/generic_thermostat/climate.py index d7889513402..407923dc161 100644 --- a/homeassistant/components/generic_thermostat/climate.py +++ b/homeassistant/components/generic_thermostat/climate.py @@ -449,12 +449,16 @@ class GenericThermostat(ClimateEntity, RestoreEntity): async def _async_heater_turn_on(self): """Turn heater toggleable device on.""" data = {ATTR_ENTITY_ID: self.heater_entity_id} - await self.hass.services.async_call(HA_DOMAIN, SERVICE_TURN_ON, data) + await self.hass.services.async_call( + HA_DOMAIN, SERVICE_TURN_ON, data, context=self._context + ) async def _async_heater_turn_off(self): """Turn heater toggleable device off.""" data = {ATTR_ENTITY_ID: self.heater_entity_id} - await self.hass.services.async_call(HA_DOMAIN, SERVICE_TURN_OFF, data) + await self.hass.services.async_call( + HA_DOMAIN, SERVICE_TURN_OFF, data, context=self._context + ) async def async_set_preset_mode(self, preset_mode: str): """Set new preset mode.""" diff --git a/homeassistant/components/group/cover.py b/homeassistant/components/group/cover.py index 2638ce072a3..427530dadb5 100644 --- a/homeassistant/components/group/cover.py +++ b/homeassistant/components/group/cover.py @@ -209,21 +209,21 @@ class CoverGroup(CoverEntity): """Move the covers up.""" data = {ATTR_ENTITY_ID: self._covers[KEY_OPEN_CLOSE]} await self.hass.services.async_call( - DOMAIN, SERVICE_OPEN_COVER, data, blocking=True + DOMAIN, SERVICE_OPEN_COVER, data, blocking=True, context=self._context ) async def async_close_cover(self, **kwargs): """Move the covers down.""" data = {ATTR_ENTITY_ID: self._covers[KEY_OPEN_CLOSE]} await self.hass.services.async_call( - DOMAIN, SERVICE_CLOSE_COVER, data, blocking=True + DOMAIN, SERVICE_CLOSE_COVER, data, blocking=True, context=self._context ) async def async_stop_cover(self, **kwargs): """Fire the stop action.""" data = {ATTR_ENTITY_ID: self._covers[KEY_STOP]} await self.hass.services.async_call( - DOMAIN, SERVICE_STOP_COVER, data, blocking=True + DOMAIN, SERVICE_STOP_COVER, data, blocking=True, context=self._context ) async def async_set_cover_position(self, **kwargs): @@ -233,28 +233,32 @@ class CoverGroup(CoverEntity): ATTR_POSITION: kwargs[ATTR_POSITION], } await self.hass.services.async_call( - DOMAIN, SERVICE_SET_COVER_POSITION, data, blocking=True + DOMAIN, + SERVICE_SET_COVER_POSITION, + data, + blocking=True, + context=self._context, ) async def async_open_cover_tilt(self, **kwargs): """Tilt covers open.""" data = {ATTR_ENTITY_ID: self._tilts[KEY_OPEN_CLOSE]} await self.hass.services.async_call( - DOMAIN, SERVICE_OPEN_COVER_TILT, data, blocking=True + DOMAIN, SERVICE_OPEN_COVER_TILT, data, blocking=True, context=self._context ) async def async_close_cover_tilt(self, **kwargs): """Tilt covers closed.""" data = {ATTR_ENTITY_ID: self._tilts[KEY_OPEN_CLOSE]} await self.hass.services.async_call( - DOMAIN, SERVICE_CLOSE_COVER_TILT, data, blocking=True + DOMAIN, SERVICE_CLOSE_COVER_TILT, data, blocking=True, context=self._context ) async def async_stop_cover_tilt(self, **kwargs): """Stop cover tilt.""" data = {ATTR_ENTITY_ID: self._tilts[KEY_STOP]} await self.hass.services.async_call( - DOMAIN, SERVICE_STOP_COVER_TILT, data, blocking=True + DOMAIN, SERVICE_STOP_COVER_TILT, data, blocking=True, context=self._context ) async def async_set_cover_tilt_position(self, **kwargs): @@ -264,7 +268,11 @@ class CoverGroup(CoverEntity): ATTR_TILT_POSITION: kwargs[ATTR_TILT_POSITION], } await self.hass.services.async_call( - DOMAIN, SERVICE_SET_COVER_TILT_POSITION, data, blocking=True + DOMAIN, + SERVICE_SET_COVER_TILT_POSITION, + data, + blocking=True, + context=self._context, ) async def async_update(self): diff --git a/homeassistant/components/group/light.py b/homeassistant/components/group/light.py index 1136df7eac0..69329b96122 100644 --- a/homeassistant/components/group/light.py +++ b/homeassistant/components/group/light.py @@ -233,7 +233,11 @@ class LightGroup(light.LightEntity): if not emulate_color_temp_entity_ids: await self.hass.services.async_call( - light.DOMAIN, light.SERVICE_TURN_ON, data, blocking=True + light.DOMAIN, + light.SERVICE_TURN_ON, + data, + blocking=True, + context=self._context, ) return @@ -249,13 +253,18 @@ class LightGroup(light.LightEntity): await asyncio.gather( self.hass.services.async_call( - light.DOMAIN, light.SERVICE_TURN_ON, data, blocking=True + light.DOMAIN, + light.SERVICE_TURN_ON, + data, + blocking=True, + context=self._context, ), self.hass.services.async_call( light.DOMAIN, light.SERVICE_TURN_ON, emulate_color_temp_data, blocking=True, + context=self._context, ), ) @@ -267,7 +276,11 @@ class LightGroup(light.LightEntity): data[ATTR_TRANSITION] = kwargs[ATTR_TRANSITION] await self.hass.services.async_call( - light.DOMAIN, light.SERVICE_TURN_OFF, data, blocking=True + light.DOMAIN, + light.SERVICE_TURN_OFF, + data, + blocking=True, + context=self._context, ) async def async_update(self): diff --git a/homeassistant/components/homeassistant/__init__.py b/homeassistant/components/homeassistant/__init__.py index e0a4d88ec6a..83166ba4cce 100644 --- a/homeassistant/components/homeassistant/__init__.py +++ b/homeassistant/components/homeassistant/__init__.py @@ -78,7 +78,9 @@ async def async_setup(hass: ha.HomeAssistant, config: dict) -> bool: data[ATTR_ENTITY_ID] = list(ent_ids) tasks.append( - hass.services.async_call(domain, service.service, data, blocking) + hass.services.async_call( + domain, service.service, data, blocking, context=service.context + ) ) if tasks: diff --git a/homeassistant/components/lifx/light.py b/homeassistant/components/lifx/light.py index 2b7629cdaf2..26a2acfa517 100644 --- a/homeassistant/components/lifx/light.py +++ b/homeassistant/components/lifx/light.py @@ -641,7 +641,9 @@ class LIFXLight(LightEntity): """Start an effect with default parameters.""" service = kwargs[ATTR_EFFECT] data = {ATTR_ENTITY_ID: self.entity_id} - await self.hass.services.async_call(LIFX_DOMAIN, service, data) + await self.hass.services.async_call( + LIFX_DOMAIN, service, data, context=self._context + ) async def async_update(self): """Update bulb status.""" diff --git a/homeassistant/components/switch/light.py b/homeassistant/components/switch/light.py index f40ccde5b0b..c23390a3e3e 100644 --- a/homeassistant/components/switch/light.py +++ b/homeassistant/components/switch/light.py @@ -84,14 +84,22 @@ class LightSwitch(LightEntity): """Forward the turn_on command to the switch in this light switch.""" data = {ATTR_ENTITY_ID: self._switch_entity_id} await self.hass.services.async_call( - switch.DOMAIN, switch.SERVICE_TURN_ON, data, blocking=True + switch.DOMAIN, + switch.SERVICE_TURN_ON, + data, + blocking=True, + context=self._context, ) async def async_turn_off(self, **kwargs): """Forward the turn_off command to the switch in this light switch.""" data = {ATTR_ENTITY_ID: self._switch_entity_id} await self.hass.services.async_call( - switch.DOMAIN, switch.SERVICE_TURN_OFF, data, blocking=True + switch.DOMAIN, + switch.SERVICE_TURN_OFF, + data, + blocking=True, + context=self._context, ) async def async_update(self): diff --git a/homeassistant/components/tts/__init__.py b/homeassistant/components/tts/__init__.py index ebd7a1c8411..39e4702e855 100644 --- a/homeassistant/components/tts/__init__.py +++ b/homeassistant/components/tts/__init__.py @@ -175,7 +175,11 @@ async def async_setup(hass, config): } await hass.services.async_call( - DOMAIN_MP, SERVICE_PLAY_MEDIA, data, blocking=True + DOMAIN_MP, + SERVICE_PLAY_MEDIA, + data, + blocking=True, + context=service.context, ) service_name = p_config.get(CONF_SERVICE_NAME, f"{p_type}_{SERVICE_SAY}") diff --git a/homeassistant/components/universal/media_player.py b/homeassistant/components/universal/media_player.py index f1cad7e8abf..ec4b53cd2e0 100644 --- a/homeassistant/components/universal/media_player.py +++ b/homeassistant/components/universal/media_player.py @@ -205,7 +205,7 @@ class UniversalMediaPlayer(MediaPlayerEntity): service_data[ATTR_ENTITY_ID] = active_child.entity_id await self.hass.services.async_call( - DOMAIN, service_name, service_data, blocking=True + DOMAIN, service_name, service_data, blocking=True, context=self._context ) @property diff --git a/tests/components/group/test_light.py b/tests/components/group/test_light.py index 1f68279ae05..2a2e21f77c5 100644 --- a/tests/components/group/test_light.py +++ b/tests/components/group/test_light.py @@ -570,14 +570,14 @@ async def test_invalid_service_calls(hass): await grouped_light.async_turn_on(brightness=150, four_oh_four="404") data = {ATTR_ENTITY_ID: ["light.test1", "light.test2"], ATTR_BRIGHTNESS: 150} mock_call.assert_called_once_with( - LIGHT_DOMAIN, SERVICE_TURN_ON, data, blocking=True + LIGHT_DOMAIN, SERVICE_TURN_ON, data, blocking=True, context=None ) mock_call.reset_mock() await grouped_light.async_turn_off(transition=4, four_oh_four="404") data = {ATTR_ENTITY_ID: ["light.test1", "light.test2"], ATTR_TRANSITION: 4} mock_call.assert_called_once_with( - LIGHT_DOMAIN, SERVICE_TURN_OFF, data, blocking=True + LIGHT_DOMAIN, SERVICE_TURN_OFF, data, blocking=True, context=None ) mock_call.reset_mock() @@ -596,5 +596,5 @@ async def test_invalid_service_calls(hass): data.pop(ATTR_RGB_COLOR) data.pop(ATTR_XY_COLOR) mock_call.assert_called_once_with( - LIGHT_DOMAIN, SERVICE_TURN_ON, data, blocking=True + LIGHT_DOMAIN, SERVICE_TURN_ON, data, blocking=True, context=None ) From 1508d7a3ac3382c6f4a48eff11ac78422fcf4e84 Mon Sep 17 00:00:00 2001 From: jjlawren Date: Thu, 25 Jun 2020 20:10:40 -0400 Subject: [PATCH 11/47] Fix Plex when using local tokenless authentication (#37096) --- homeassistant/components/plex/__init__.py | 20 ++++-- homeassistant/components/plex/server.py | 52 ++++++++++---- tests/components/plex/test_config_flow.py | 14 ++-- tests/components/plex/test_init.py | 79 +++++++++++++++++---- tests/components/plex/test_media_players.py | 31 ++++---- tests/components/plex/test_playback.py | 8 +-- tests/components/plex/test_server.py | 37 +++++----- 7 files changed, 157 insertions(+), 84 deletions(-) diff --git a/homeassistant/components/plex/__init__.py b/homeassistant/components/plex/__init__.py index 89a3570dd10..c83dfe13347 100644 --- a/homeassistant/components/plex/__init__.py +++ b/homeassistant/components/plex/__init__.py @@ -161,12 +161,20 @@ async def async_setup_entry(hass, entry): } ) - hass.services.async_register( - PLEX_DOMAIN, - SERVICE_PLAY_ON_SONOS, - async_play_on_sonos_service, - schema=play_on_sonos_schema, - ) + def get_plex_account(plex_server): + try: + return plex_server.account + except plexapi.exceptions.Unauthorized: + return None + + plex_account = await hass.async_add_executor_job(get_plex_account, plex_server) + if plex_account: + hass.services.async_register( + PLEX_DOMAIN, + SERVICE_PLAY_ON_SONOS, + async_play_on_sonos_service, + schema=play_on_sonos_schema, + ) return True diff --git a/homeassistant/components/plex/server.py b/homeassistant/components/plex/server.py index 05dae668512..a49a73cb51b 100644 --- a/homeassistant/components/plex/server.py +++ b/homeassistant/components/plex/server.py @@ -76,6 +76,7 @@ class PlexServer: self._plextv_clients = None self._plextv_client_timestamp = 0 self._plextv_device_cache = {} + self._use_plex_tv = self._token is not None self._version = None self.async_update_platforms = Debouncer( hass, @@ -94,18 +95,35 @@ class PlexServer: @property def account(self): """Return a MyPlexAccount instance.""" - if not self._plex_account: - self._plex_account = plexapi.myplex.MyPlexAccount(token=self._token) + if not self._plex_account and self._use_plex_tv: + try: + self._plex_account = plexapi.myplex.MyPlexAccount(token=self._token) + except Unauthorized: + self._use_plex_tv = False + _LOGGER.error("Not authorized to access plex.tv with provided token") + raise return self._plex_account + @property + def plextv_resources(self): + """Return all resources linked to Plex account.""" + if self.account is None: + return [] + + return self.account.resources() + def plextv_clients(self): """Return available clients linked to Plex account.""" + if self.account is None: + return [] + now = time.time() if now - self._plextv_client_timestamp > PLEXTV_THROTTLE: self._plextv_client_timestamp = now - resources = self.account.resources() self._plextv_clients = [ - x for x in resources if "player" in x.provides and x.presence + x + for x in self.plextv_resources + if "player" in x.provides and x.presence ] _LOGGER.debug( "Current available clients from plex.tv: %s", self._plextv_clients @@ -119,7 +137,7 @@ class PlexServer: def _connect_with_token(): available_servers = [ (x.name, x.clientIdentifier) - for x in self.account.resources() + for x in self.plextv_resources if "server" in x.provides ] @@ -145,14 +163,18 @@ class PlexServer: ) def _update_plexdirect_hostname(): - matching_server = [ + matching_servers = [ x.name - for x in self.account.resources() + for x in self.plextv_resources if x.clientIdentifier == self._server_id - ][0] - self._plex_server = self.account.resource(matching_server).connect( - timeout=10 - ) + ] + if matching_servers: + self._plex_server = self.account.resource(matching_servers[0]).connect( + timeout=10 + ) + return True + _LOGGER.error("Attempt to update plex.direct hostname failed") + return False if self._url: try: @@ -168,8 +190,12 @@ class PlexServer: _LOGGER.warning( "Plex SSL certificate's hostname changed, updating." ) - _update_plexdirect_hostname() - config_entry_update_needed = True + if _update_plexdirect_hostname(): + config_entry_update_needed = True + else: + raise Unauthorized( + "New certificate cannot be validated with provided token" + ) else: raise else: diff --git a/tests/components/plex/test_config_flow.py b/tests/components/plex/test_config_flow.py index f218b4c4d79..125367a32f6 100644 --- a/tests/components/plex/test_config_flow.py +++ b/tests/components/plex/test_config_flow.py @@ -367,8 +367,8 @@ async def test_option_flow(hass): ) with patch("plexapi.server.PlexServer", return_value=mock_plex_server), patch( - "homeassistant.components.plex.PlexWebsocket.listen" - ) as mock_listen: + "plexapi.myplex.MyPlexAccount", return_value=MockPlexAccount() + ), patch("homeassistant.components.plex.PlexWebsocket.listen") as mock_listen: entry.add_to_hass(hass) assert await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() @@ -417,8 +417,8 @@ async def test_missing_option_flow(hass): ) with patch("plexapi.server.PlexServer", return_value=mock_plex_server), patch( - "homeassistant.components.plex.PlexWebsocket.listen" - ) as mock_listen: + "plexapi.myplex.MyPlexAccount", return_value=MockPlexAccount() + ), patch("homeassistant.components.plex.PlexWebsocket.listen") as mock_listen: entry.add_to_hass(hass) assert await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() @@ -471,8 +471,8 @@ async def test_option_flow_new_users_available(hass, caplog): mock_plex_server = MockPlexServer(config_entry=entry) with patch("plexapi.server.PlexServer", return_value=mock_plex_server), patch( - "homeassistant.components.plex.PlexWebsocket.listen" - ): + "plexapi.myplex.MyPlexAccount", return_value=MockPlexAccount() + ), patch("homeassistant.components.plex.PlexWebsocket.listen"): entry.add_to_hass(hass) assert await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() @@ -741,6 +741,8 @@ async def test_setup_with_limited_credentials(hass): ), patch.object( mock_plex_server, "systemAccounts", side_effect=plexapi.exceptions.Unauthorized ) as mock_accounts, patch( + "plexapi.myplex.MyPlexAccount", return_value=MockPlexAccount() + ), patch( "homeassistant.components.plex.PlexWebsocket.listen" ) as mock_listen: entry.add_to_hass(hass) diff --git a/tests/components/plex/test_init.py b/tests/components/plex/test_init.py index 5f626bf6a23..76b1138fc06 100644 --- a/tests/components/plex/test_init.py +++ b/tests/components/plex/test_init.py @@ -13,7 +13,7 @@ from homeassistant.config_entries import ( ENTRY_STATE_SETUP_ERROR, ENTRY_STATE_SETUP_RETRY, ) -from homeassistant.const import CONF_URL, CONF_VERIFY_SSL +from homeassistant.const import CONF_TOKEN, CONF_URL, CONF_VERIFY_SSL from homeassistant.helpers.dispatcher import async_dispatcher_send import homeassistant.util.dt as dt_util @@ -115,8 +115,8 @@ async def test_set_config_entry_unique_id(hass): ) with patch("plexapi.server.PlexServer", return_value=mock_plex_server), patch( - "homeassistant.components.plex.PlexWebsocket.listen" - ) as mock_listen: + "plexapi.myplex.MyPlexAccount", return_value=MockPlexAccount() + ), patch("homeassistant.components.plex.PlexWebsocket.listen") as mock_listen: entry.add_to_hass(hass) assert await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() @@ -181,8 +181,8 @@ async def test_setup_with_insecure_config_entry(hass): ) with patch("plexapi.server.PlexServer", return_value=mock_plex_server), patch( - "homeassistant.components.plex.PlexWebsocket.listen" - ) as mock_listen: + "plexapi.myplex.MyPlexAccount", return_value=MockPlexAccount() + ), patch("homeassistant.components.plex.PlexWebsocket.listen") as mock_listen: entry.add_to_hass(hass) assert await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() @@ -210,8 +210,8 @@ async def test_unload_config_entry(hass): assert entry is config_entries[0] with patch("plexapi.server.PlexServer", return_value=mock_plex_server), patch( - "homeassistant.components.plex.PlexWebsocket.listen" - ) as mock_listen: + "plexapi.myplex.MyPlexAccount", return_value=MockPlexAccount() + ), patch("homeassistant.components.plex.PlexWebsocket.listen") as mock_listen: assert await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() assert mock_listen.called @@ -243,8 +243,8 @@ async def test_setup_with_photo_session(hass): ) with patch("plexapi.server.PlexServer", return_value=mock_plex_server), patch( - "homeassistant.components.plex.PlexWebsocket.listen" - ): + "plexapi.myplex.MyPlexAccount", return_value=MockPlexAccount() + ), patch("homeassistant.components.plex.PlexWebsocket.listen"): entry.add_to_hass(hass) assert await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() @@ -254,11 +254,8 @@ async def test_setup_with_photo_session(hass): server_id = mock_plex_server.machineIdentifier - with patch("plexapi.myplex.MyPlexAccount", return_value=MockPlexAccount()): - async_dispatcher_send( - hass, const.PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id) - ) - await hass.async_block_till_done() + async_dispatcher_send(hass, const.PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id)) + await hass.async_block_till_done() media_player = hass.states.get("media_player.plex_product_title") assert media_player.state == "idle" @@ -293,10 +290,33 @@ async def test_setup_when_certificate_changed(hass): new_entry = MockConfigEntry(domain=const.DOMAIN, data=DEFAULT_DATA) + # Test with account failure + with patch( + "plexapi.server.PlexServer", side_effect=WrongCertHostnameException + ), patch( + "plexapi.myplex.MyPlexAccount", side_effect=plexapi.exceptions.Unauthorized + ): + old_entry.add_to_hass(hass) + assert await hass.config_entries.async_setup(old_entry.entry_id) is False + await hass.async_block_till_done() + + assert old_entry.state == ENTRY_STATE_SETUP_ERROR + await hass.config_entries.async_unload(old_entry.entry_id) + + # Test with no servers found + with patch( + "plexapi.server.PlexServer", side_effect=WrongCertHostnameException + ), patch("plexapi.myplex.MyPlexAccount", return_value=MockPlexAccount(servers=0)): + assert await hass.config_entries.async_setup(old_entry.entry_id) is False + await hass.async_block_till_done() + + assert old_entry.state == ENTRY_STATE_SETUP_ERROR + await hass.config_entries.async_unload(old_entry.entry_id) + + # Test with success with patch( "plexapi.server.PlexServer", side_effect=WrongCertHostnameException ), patch("plexapi.myplex.MyPlexAccount", return_value=MockPlexAccount()): - old_entry.add_to_hass(hass) assert await hass.config_entries.async_setup(old_entry.entry_id) await hass.async_block_till_done() @@ -307,3 +327,32 @@ async def test_setup_when_certificate_changed(hass): old_entry.data[const.PLEX_SERVER_CONFIG][CONF_URL] == new_entry.data[const.PLEX_SERVER_CONFIG][CONF_URL] ) + + +async def test_tokenless_server(hass): + """Test setup with a server with token auth disabled.""" + mock_plex_server = MockPlexServer() + + TOKENLESS_DATA = copy.deepcopy(DEFAULT_DATA) + TOKENLESS_DATA[const.PLEX_SERVER_CONFIG].pop(CONF_TOKEN, None) + + entry = MockConfigEntry( + domain=const.DOMAIN, + data=TOKENLESS_DATA, + options=DEFAULT_OPTIONS, + unique_id=DEFAULT_DATA["server_id"], + ) + + with patch("plexapi.server.PlexServer", return_value=mock_plex_server), patch( + "plexapi.myplex.MyPlexAccount", side_effect=plexapi.exceptions.Unauthorized + ), patch("homeassistant.components.plex.PlexWebsocket.listen"): + entry.add_to_hass(hass) + assert await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + assert entry.state == ENTRY_STATE_LOADED + + server_id = mock_plex_server.machineIdentifier + + async_dispatcher_send(hass, const.PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id)) + await hass.async_block_till_done() diff --git a/tests/components/plex/test_media_players.py b/tests/components/plex/test_media_players.py index 0cd76c15ab6..d3e2de91cf9 100644 --- a/tests/components/plex/test_media_players.py +++ b/tests/components/plex/test_media_players.py @@ -23,8 +23,8 @@ async def test_plex_tv_clients(hass): mock_plex_account = MockPlexAccount() with patch("plexapi.server.PlexServer", return_value=mock_plex_server), patch( - "homeassistant.components.plex.PlexWebsocket.listen" - ): + "plexapi.myplex.MyPlexAccount", return_value=mock_plex_account + ), patch("homeassistant.components.plex.PlexWebsocket.listen"): entry.add_to_hass(hass) assert await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() @@ -37,9 +37,7 @@ async def test_plex_tv_clients(hass): for x in mock_plex_account.resources() if x.name.startswith("plex.tv Resource Player") ) - with patch( - "plexapi.myplex.MyPlexAccount", return_value=mock_plex_account - ), patch.object(resource, "connect", side_effect=NotFound): + with patch.object(resource, "connect", side_effect=NotFound): await plex_server._async_update_platforms() await hass.async_block_till_done() @@ -49,16 +47,15 @@ async def test_plex_tv_clients(hass): await hass.config_entries.async_unload(entry.entry_id) with patch("plexapi.server.PlexServer", return_value=mock_plex_server), patch( - "homeassistant.components.plex.PlexWebsocket.listen" - ): + "plexapi.myplex.MyPlexAccount", return_value=mock_plex_account + ), patch("homeassistant.components.plex.PlexWebsocket.listen"): assert await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() plex_server = hass.data[DOMAIN][SERVERS][server_id] - with patch("plexapi.myplex.MyPlexAccount", return_value=mock_plex_account): - await plex_server._async_update_platforms() - await hass.async_block_till_done() + await plex_server._async_update_platforms() + await hass.async_block_till_done() media_players_after = len(hass.states.async_entity_ids("media_player")) assert media_players_after == media_players_before + 1 @@ -70,22 +67,20 @@ async def test_plex_tv_clients(hass): mock_plex_server.clear_sessions() with patch("plexapi.server.PlexServer", return_value=mock_plex_server), patch( - "homeassistant.components.plex.PlexWebsocket.listen" - ): + "plexapi.myplex.MyPlexAccount", return_value=mock_plex_account + ), patch("homeassistant.components.plex.PlexWebsocket.listen"): assert await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() plex_server = hass.data[DOMAIN][SERVERS][server_id] - with patch("plexapi.myplex.MyPlexAccount", return_value=mock_plex_account): - await plex_server._async_update_platforms() - await hass.async_block_till_done() + await plex_server._async_update_platforms() + await hass.async_block_till_done() assert len(hass.states.async_entity_ids("media_player")) == 1 # Ensure cache gets called - with patch("plexapi.myplex.MyPlexAccount", return_value=mock_plex_account): - await plex_server._async_update_platforms() - await hass.async_block_till_done() + await plex_server._async_update_platforms() + await hass.async_block_till_done() assert len(hass.states.async_entity_ids("media_player")) == 1 diff --git a/tests/components/plex/test_playback.py b/tests/components/plex/test_playback.py index 7a90d8dfad8..dafc8720ab1 100644 --- a/tests/components/plex/test_playback.py +++ b/tests/components/plex/test_playback.py @@ -28,8 +28,8 @@ async def test_sonos_playback(hass): mock_plex_server = MockPlexServer(config_entry=entry) with patch("plexapi.server.PlexServer", return_value=mock_plex_server), patch( - "homeassistant.components.plex.PlexWebsocket.listen" - ): + "plexapi.myplex.MyPlexAccount", return_value=MockPlexAccount() + ), patch("homeassistant.components.plex.PlexWebsocket.listen"): entry.add_to_hass(hass) assert await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() @@ -37,10 +37,6 @@ async def test_sonos_playback(hass): server_id = mock_plex_server.machineIdentifier loaded_server = hass.data[DOMAIN][SERVERS][server_id] - with patch("plexapi.myplex.MyPlexAccount", return_value=MockPlexAccount()): - # Access and cache PlexAccount - assert loaded_server.account - # Test Sonos integration lookup failure with patch.object( hass.components.sonos, "get_coordinator_id", side_effect=HomeAssistantError diff --git a/tests/components/plex/test_server.py b/tests/components/plex/test_server.py index a42e1aff710..5cd0d13e90c 100644 --- a/tests/components/plex/test_server.py +++ b/tests/components/plex/test_server.py @@ -55,17 +55,16 @@ async def test_new_users_available(hass): mock_plex_server = MockPlexServer(config_entry=entry) with patch("plexapi.server.PlexServer", return_value=mock_plex_server), patch( - "homeassistant.components.plex.PlexWebsocket.listen" - ): + "plexapi.myplex.MyPlexAccount", return_value=MockPlexAccount() + ), patch("homeassistant.components.plex.PlexWebsocket.listen"): entry.add_to_hass(hass) assert await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() server_id = mock_plex_server.machineIdentifier - with patch("plexapi.myplex.MyPlexAccount", return_value=MockPlexAccount()): - async_dispatcher_send(hass, PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id)) - await hass.async_block_till_done() + async_dispatcher_send(hass, PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id)) + await hass.async_block_till_done() monitored_users = hass.data[DOMAIN][SERVERS][server_id].option_monitored_users @@ -95,17 +94,16 @@ async def test_new_ignored_users_available(hass, caplog): mock_plex_server = MockPlexServer(config_entry=entry) with patch("plexapi.server.PlexServer", return_value=mock_plex_server), patch( - "homeassistant.components.plex.PlexWebsocket.listen" - ): + "plexapi.myplex.MyPlexAccount", return_value=MockPlexAccount() + ), patch("homeassistant.components.plex.PlexWebsocket.listen"): entry.add_to_hass(hass) assert await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() server_id = mock_plex_server.machineIdentifier - with patch("plexapi.myplex.MyPlexAccount", return_value=MockPlexAccount()): - async_dispatcher_send(hass, PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id)) - await hass.async_block_till_done() + async_dispatcher_send(hass, PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id)) + await hass.async_block_till_done() monitored_users = hass.data[DOMAIN][SERVERS][server_id].option_monitored_users @@ -248,17 +246,16 @@ async def test_ignore_plex_web_client(hass): mock_plex_server = MockPlexServer(config_entry=entry) with patch("plexapi.server.PlexServer", return_value=mock_plex_server), patch( - "homeassistant.components.plex.PlexWebsocket.listen" - ): + "plexapi.myplex.MyPlexAccount", return_value=MockPlexAccount(players=0) + ), patch("homeassistant.components.plex.PlexWebsocket.listen"): entry.add_to_hass(hass) assert await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() server_id = mock_plex_server.machineIdentifier - with patch("plexapi.myplex.MyPlexAccount", return_value=MockPlexAccount(players=0)): - async_dispatcher_send(hass, PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id)) - await hass.async_block_till_done() + async_dispatcher_send(hass, PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id)) + await hass.async_block_till_done() sensor = hass.states.get("sensor.plex_plex_server_1") assert sensor.state == str(len(mock_plex_server.accounts)) @@ -281,8 +278,8 @@ async def test_media_lookups(hass): mock_plex_server = MockPlexServer(config_entry=entry) with patch("plexapi.server.PlexServer", return_value=mock_plex_server), patch( - "homeassistant.components.plex.PlexWebsocket.listen" - ): + "plexapi.myplex.MyPlexAccount", return_value=MockPlexAccount() + ), patch("homeassistant.components.plex.PlexWebsocket.listen"): entry.add_to_hass(hass) assert await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() @@ -291,9 +288,9 @@ async def test_media_lookups(hass): loaded_server = hass.data[DOMAIN][SERVERS][server_id] # Plex Key searches - with patch("plexapi.myplex.MyPlexAccount", return_value=MockPlexAccount()): - async_dispatcher_send(hass, PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id)) - await hass.async_block_till_done() + async_dispatcher_send(hass, PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id)) + await hass.async_block_till_done() + media_player_id = hass.states.async_entity_ids("media_player")[0] with patch("homeassistant.components.plex.PlexServer.create_playqueue"): assert await hass.services.async_call( From 7fdb76cdf288ef64ff161b4e903d0c0efe5aa154 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 25 Jun 2020 17:38:09 -0700 Subject: [PATCH 12/47] Bump frontend (#37113) --- homeassistant/components/frontend/manifest.json | 10 +++------- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 6 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/frontend/manifest.json b/homeassistant/components/frontend/manifest.json index 5427932774d..546ca70cff3 100644 --- a/homeassistant/components/frontend/manifest.json +++ b/homeassistant/components/frontend/manifest.json @@ -2,9 +2,7 @@ "domain": "frontend", "name": "Home Assistant Frontend", "documentation": "https://www.home-assistant.io/integrations/frontend", - "requirements": [ - "home-assistant-frontend==20200623.3" - ], + "requirements": ["home-assistant-frontend==20200626.0"], "dependencies": [ "api", "auth", @@ -17,8 +15,6 @@ "system_log", "websocket_api" ], - "codeowners": [ - "@home-assistant/frontend" - ], + "codeowners": ["@home-assistant/frontend"], "quality_scale": "internal" -} \ No newline at end of file +} diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 26232031566..824a1fe003a 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -13,7 +13,7 @@ defusedxml==0.6.0 distro==1.5.0 emoji==0.5.4 hass-nabucasa==0.34.7 -home-assistant-frontend==20200623.3 +home-assistant-frontend==20200626.0 importlib-metadata==1.6.0;python_version<'3.8' jinja2>=2.11.1 netdisco==2.7.1 diff --git a/requirements_all.txt b/requirements_all.txt index c00d3d3dfd3..756c1d9ae68 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -738,7 +738,7 @@ hole==0.5.1 holidays==0.10.2 # homeassistant.components.frontend -home-assistant-frontend==20200623.3 +home-assistant-frontend==20200626.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 6db4ba37354..fe5f1e2ba27 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -343,7 +343,7 @@ hole==0.5.1 holidays==0.10.2 # homeassistant.components.frontend -home-assistant-frontend==20200623.3 +home-assistant-frontend==20200626.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 From 748f1c3607cff5530133aa3f2a8b3ef83c2baac7 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 26 Jun 2020 00:45:10 +0000 Subject: [PATCH 13/47] Bumped version to 0.112.0b1 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index c2a725f4d82..27ecadd00a7 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 112 -PATCH_VERSION = "0b0" +PATCH_VERSION = "0b1" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER = (3, 7, 0) From 976cbdd2aa10f78377410e627f2c45d856a9d2fa Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 26 Jun 2020 19:27:45 +0200 Subject: [PATCH 14/47] Fix recorder purging by batch processing purges (#37140) --- homeassistant/components/recorder/__init__.py | 6 ++- homeassistant/components/recorder/models.py | 4 +- homeassistant/components/recorder/purge.py | 50 +++++++++++++++---- tests/components/recorder/test_init.py | 2 +- tests/components/recorder/test_purge.py | 21 ++++++-- 5 files changed, 66 insertions(+), 17 deletions(-) diff --git a/homeassistant/components/recorder/__init__.py b/homeassistant/components/recorder/__init__.py index 52dabad1faf..aadc8e61fa1 100644 --- a/homeassistant/components/recorder/__init__.py +++ b/homeassistant/components/recorder/__init__.py @@ -335,7 +335,7 @@ class Recorder(threading.Thread): self.event_session = self.get_session() # Use a session for the event read loop # with a commit every time the event time - # has changed. This reduces the disk io. + # has changed. This reduces the disk io. while True: event = self.queue.get() if event is None: @@ -344,7 +344,9 @@ class Recorder(threading.Thread): self.queue.task_done() return if isinstance(event, PurgeTask): - purge.purge_old_data(self, event.keep_days, event.repack) + # Schedule a new purge task if this one didn't finish + if not purge.purge_old_data(self, event.keep_days, event.repack): + self.queue.put(PurgeTask(event.keep_days, event.repack)) self.queue.task_done() continue if event.event_type == EVENT_TIME_CHANGED: diff --git a/homeassistant/components/recorder/models.py b/homeassistant/components/recorder/models.py index 03c81726310..0566faf1c4d 100644 --- a/homeassistant/components/recorder/models.py +++ b/homeassistant/components/recorder/models.py @@ -64,7 +64,7 @@ class Events(Base): # type: ignore context_parent_id=event.context.parent_id, ) - def to_native(self): + def to_native(self, validate_entity_id=True): """Convert to a natve HA Event.""" context = Context( id=self.context_id, @@ -183,7 +183,7 @@ class RecorderRuns(Base): # type: ignore return [row[0] for row in query] - def to_native(self): + def to_native(self, validate_entity_id=True): """Return self, native format is this model.""" return self diff --git a/homeassistant/components/recorder/purge.py b/homeassistant/components/recorder/purge.py index 78d92b8b65e..833926af219 100644 --- a/homeassistant/components/recorder/purge.py +++ b/homeassistant/components/recorder/purge.py @@ -7,32 +7,62 @@ from sqlalchemy.exc import SQLAlchemyError import homeassistant.util.dt as dt_util from .models import Events, RecorderRuns, States -from .util import session_scope +from .util import execute, session_scope _LOGGER = logging.getLogger(__name__) -def purge_old_data(instance, purge_days, repack): - """Purge events and states older than purge_days ago.""" +def purge_old_data(instance, purge_days: int, repack: bool) -> bool: + """Purge events and states older than purge_days ago. + + Cleans up an timeframe of an hour, based on the oldest record. + """ purge_before = dt_util.utcnow() - timedelta(days=purge_days) _LOGGER.debug("Purging events before %s", purge_before) try: with session_scope(session=instance.get_session()) as session: - deleted_rows = ( + query = session.query(States).order_by(States.last_updated.asc()).limit(1) + states = execute(query, to_native=True, validate_entity_ids=False) + + states_purge_before = purge_before + if states: + states_purge_before = min( + purge_before, states[0].last_updated + timedelta(hours=1) + ) + + deleted_rows_states = ( session.query(States) - .filter(States.last_updated < purge_before) + .filter(States.last_updated < states_purge_before) .delete(synchronize_session=False) ) - _LOGGER.debug("Deleted %s states", deleted_rows) + _LOGGER.debug("Deleted %s states", deleted_rows_states) - deleted_rows = ( + query = session.query(Events).order_by(Events.time_fired.asc()).limit(1) + events = execute(query, to_native=True) + + events_purge_before = purge_before + if events: + events_purge_before = min( + purge_before, events[0].time_fired + timedelta(hours=1) + ) + + deleted_rows_events = ( session.query(Events) - .filter(Events.time_fired < purge_before) + .filter(Events.time_fired < events_purge_before) .delete(synchronize_session=False) ) - _LOGGER.debug("Deleted %s events", deleted_rows) + _LOGGER.debug("Deleted %s events", deleted_rows_events) + # If states or events purging isn't processing the purge_before yet, + # return false, as we are not done yet. + if (states_purge_before and states_purge_before != purge_before) or ( + events_purge_before and events_purge_before != purge_before + ): + _LOGGER.debug("Purging hasn't fully completed yet.") + return False + + # Recorder runs is small, no need to batch run it deleted_rows = ( session.query(RecorderRuns) .filter(RecorderRuns.start < purge_before) @@ -52,3 +82,5 @@ def purge_old_data(instance, purge_days, repack): except SQLAlchemyError as err: _LOGGER.warning("Error purging history: %s.", err) + + return True diff --git a/tests/components/recorder/test_init.py b/tests/components/recorder/test_init.py index d4dfa0ecc1e..843609cf308 100644 --- a/tests/components/recorder/test_init.py +++ b/tests/components/recorder/test_init.py @@ -317,7 +317,7 @@ def test_auto_purge(hass_recorder): test_time = tz.localize(datetime(2020, 1, 1, 4, 12, 0)) with patch( - "homeassistant.components.recorder.purge.purge_old_data" + "homeassistant.components.recorder.purge.purge_old_data", return_value=True ) as purge_old_data: for delta in (-1, 0, 1): hass.bus.fire( diff --git a/tests/components/recorder/test_purge.py b/tests/components/recorder/test_purge.py index 05a184a8608..afcb1b2818f 100644 --- a/tests/components/recorder/test_purge.py +++ b/tests/components/recorder/test_purge.py @@ -130,9 +130,16 @@ class TestRecorderPurge(unittest.TestCase): assert states.count() == 6 # run purge_old_data() - purge_old_data(self.hass.data[DATA_INSTANCE], 4, repack=False) + finished = purge_old_data(self.hass.data[DATA_INSTANCE], 4, repack=False) + assert not finished + assert states.count() == 4 - # we should only have 2 states left after purging + finished = purge_old_data(self.hass.data[DATA_INSTANCE], 4, repack=False) + assert not finished + assert states.count() == 2 + + finished = purge_old_data(self.hass.data[DATA_INSTANCE], 4, repack=False) + assert finished assert states.count() == 2 def test_purge_old_events(self): @@ -144,9 +151,17 @@ class TestRecorderPurge(unittest.TestCase): assert events.count() == 6 # run purge_old_data() - purge_old_data(self.hass.data[DATA_INSTANCE], 4, repack=False) + finished = purge_old_data(self.hass.data[DATA_INSTANCE], 4, repack=False) + assert not finished + assert events.count() == 4 + + finished = purge_old_data(self.hass.data[DATA_INSTANCE], 4, repack=False) + assert not finished + assert events.count() == 2 # we should only have 2 events left + finished = purge_old_data(self.hass.data[DATA_INSTANCE], 4, repack=False) + assert finished assert events.count() == 2 def test_purge_method(self): From 56853787e7d073c8b15f34dcc949b9f67e1dc975 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 26 Jun 2020 11:45:40 -0500 Subject: [PATCH 15/47] Fix repack when using pymysql (#37142) --- homeassistant/components/recorder/purge.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/recorder/purge.py b/homeassistant/components/recorder/purge.py index 833926af219..8b0b71e24ae 100644 --- a/homeassistant/components/recorder/purge.py +++ b/homeassistant/components/recorder/purge.py @@ -76,7 +76,7 @@ def purge_old_data(instance, purge_days: int, repack: bool) -> bool: _LOGGER.debug("Vacuuming SQL DB to free space") instance.engine.execute("VACUUM") # Optimize mysql / mariadb tables to free up space on disk - elif instance.engine.driver == "mysqldb": + elif instance.engine.driver in ("mysqldb", "pymysql"): _LOGGER.debug("Optimizing SQL DB to free space") instance.engine.execute("OPTIMIZE TABLE states, events, recorder_runs") From 34c4dc2e801b2af0525b91cb362a04f61d684c10 Mon Sep 17 00:00:00 2001 From: jjlawren Date: Fri, 26 Jun 2020 13:30:44 -0400 Subject: [PATCH 16/47] Catch additional exception for Plex account login failures (#37143) --- homeassistant/components/plex/__init__.py | 2 +- homeassistant/components/plex/server.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/plex/__init__.py b/homeassistant/components/plex/__init__.py index c83dfe13347..01f80ed0d2b 100644 --- a/homeassistant/components/plex/__init__.py +++ b/homeassistant/components/plex/__init__.py @@ -164,7 +164,7 @@ async def async_setup_entry(hass, entry): def get_plex_account(plex_server): try: return plex_server.account - except plexapi.exceptions.Unauthorized: + except (plexapi.exceptions.BadRequest, plexapi.exceptions.Unauthorized): return None plex_account = await hass.async_add_executor_job(get_plex_account, plex_server) diff --git a/homeassistant/components/plex/server.py b/homeassistant/components/plex/server.py index a49a73cb51b..94ba9b6950d 100644 --- a/homeassistant/components/plex/server.py +++ b/homeassistant/components/plex/server.py @@ -4,7 +4,7 @@ import ssl import time from urllib.parse import urlparse -from plexapi.exceptions import NotFound, Unauthorized +from plexapi.exceptions import BadRequest, NotFound, Unauthorized import plexapi.myplex import plexapi.playqueue import plexapi.server @@ -98,7 +98,7 @@ class PlexServer: if not self._plex_account and self._use_plex_tv: try: self._plex_account = plexapi.myplex.MyPlexAccount(token=self._token) - except Unauthorized: + except (BadRequest, Unauthorized): self._use_plex_tv = False _LOGGER.error("Not authorized to access plex.tv with provided token") raise From 3a6a439c021a438bf37888bac4a78a029e5ab057 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 26 Jun 2020 13:44:28 -0700 Subject: [PATCH 17/47] Updated frontend to 20200626.1 (#37150) --- homeassistant/components/frontend/manifest.json | 10 +++++++--- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/frontend/manifest.json b/homeassistant/components/frontend/manifest.json index 546ca70cff3..6fc6bba73ff 100644 --- a/homeassistant/components/frontend/manifest.json +++ b/homeassistant/components/frontend/manifest.json @@ -2,7 +2,9 @@ "domain": "frontend", "name": "Home Assistant Frontend", "documentation": "https://www.home-assistant.io/integrations/frontend", - "requirements": ["home-assistant-frontend==20200626.0"], + "requirements": [ + "home-assistant-frontend==20200626.1" + ], "dependencies": [ "api", "auth", @@ -15,6 +17,8 @@ "system_log", "websocket_api" ], - "codeowners": ["@home-assistant/frontend"], + "codeowners": [ + "@home-assistant/frontend" + ], "quality_scale": "internal" -} +} \ No newline at end of file diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 824a1fe003a..683569773fa 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -13,7 +13,7 @@ defusedxml==0.6.0 distro==1.5.0 emoji==0.5.4 hass-nabucasa==0.34.7 -home-assistant-frontend==20200626.0 +home-assistant-frontend==20200626.1 importlib-metadata==1.6.0;python_version<'3.8' jinja2>=2.11.1 netdisco==2.7.1 diff --git a/requirements_all.txt b/requirements_all.txt index 756c1d9ae68..eee9096a00f 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -738,7 +738,7 @@ hole==0.5.1 holidays==0.10.2 # homeassistant.components.frontend -home-assistant-frontend==20200626.0 +home-assistant-frontend==20200626.1 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index fe5f1e2ba27..c15b2ed60aa 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -343,7 +343,7 @@ hole==0.5.1 holidays==0.10.2 # homeassistant.components.frontend -home-assistant-frontend==20200626.0 +home-assistant-frontend==20200626.1 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 From 7418011d6d99aa664ffd8684064234d2e063efdf Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 26 Jun 2020 14:26:05 -0700 Subject: [PATCH 18/47] Fix speedtest blowing up (#37151) --- homeassistant/components/speedtestdotnet/config_flow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/speedtestdotnet/config_flow.py b/homeassistant/components/speedtestdotnet/config_flow.py index 311a1a0d0d3..1d8f3cf189b 100644 --- a/homeassistant/components/speedtestdotnet/config_flow.py +++ b/homeassistant/components/speedtestdotnet/config_flow.py @@ -94,7 +94,7 @@ class SpeedTestOptionsFlowHandler(config_entries.OptionsFlow): for (key, value) in self._servers.items() if value.get("id") == self.config_entry.options[CONF_SERVER_ID] ] - server_name = server[0] + server_name = server[0] if server else "" options = { vol.Optional( From b9c233f01348468b5323d67c7960293d93d0ccb3 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 26 Jun 2020 14:25:50 -0700 Subject: [PATCH 19/47] Fix OwnTracks race condition (#37152) --- .../components/owntracks/device_tracker.py | 33 +++++++++---------- 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/homeassistant/components/owntracks/device_tracker.py b/homeassistant/components/owntracks/device_tracker.py index b1204082887..d4a5399a0ff 100644 --- a/homeassistant/components/owntracks/device_tracker.py +++ b/homeassistant/components/owntracks/device_tracker.py @@ -24,6 +24,19 @@ _LOGGER = logging.getLogger(__name__) async def async_setup_entry(hass, entry, async_add_entities): """Set up OwnTracks based off an entry.""" + # Restore previously loaded devices + dev_reg = await device_registry.async_get_registry(hass) + dev_ids = { + identifier[1] + for device in dev_reg.devices.values() + for identifier in device.identifiers + if identifier[0] == OT_DOMAIN + } + + entities = [] + for dev_id in dev_ids: + entity = hass.data[OT_DOMAIN]["devices"][dev_id] = OwnTracksEntity(dev_id) + entities.append(entity) @callback def _receive_data(dev_id, **data): @@ -39,24 +52,8 @@ async def async_setup_entry(hass, entry, async_add_entities): hass.data[OT_DOMAIN]["context"].set_async_see(_receive_data) - # Restore previously loaded devices - dev_reg = await device_registry.async_get_registry(hass) - dev_ids = { - identifier[1] - for device in dev_reg.devices.values() - for identifier in device.identifiers - if identifier[0] == OT_DOMAIN - } - - if not dev_ids: - return True - - entities = [] - for dev_id in dev_ids: - entity = hass.data[OT_DOMAIN]["devices"][dev_id] = OwnTracksEntity(dev_id) - entities.append(entity) - - async_add_entities(entities) + if entities: + async_add_entities(entities) return True From 6a6dfdff4db341fa6d4707de3d05a12499fff6b3 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 26 Jun 2020 21:26:42 +0000 Subject: [PATCH 20/47] Bumped version to 0.112.0b2 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 27ecadd00a7..5a786552a72 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 112 -PATCH_VERSION = "0b1" +PATCH_VERSION = "0b2" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER = (3, 7, 0) From 8a755e790fb16bdbad8fab30279c3a3a7975bd45 Mon Sep 17 00:00:00 2001 From: Tom Harris Date: Sun, 28 Jun 2020 05:46:44 -0400 Subject: [PATCH 21/47] Fix issue with Insteon devices not responding to device changes (#37160) --- homeassistant/components/insteon/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/insteon/manifest.json b/homeassistant/components/insteon/manifest.json index 70a7375d51f..d1a31117fb9 100644 --- a/homeassistant/components/insteon/manifest.json +++ b/homeassistant/components/insteon/manifest.json @@ -2,6 +2,6 @@ "domain": "insteon", "name": "Insteon", "documentation": "https://www.home-assistant.io/integrations/insteon", - "requirements": ["pyinsteon==1.0.4"], + "requirements": ["pyinsteon==1.0.5"], "codeowners": ["@teharris1"] } \ No newline at end of file diff --git a/requirements_all.txt b/requirements_all.txt index eee9096a00f..429cdb998b9 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1388,7 +1388,7 @@ pyialarm==0.3 pyicloud==0.9.7 # homeassistant.components.insteon -pyinsteon==1.0.4 +pyinsteon==1.0.5 # homeassistant.components.intesishome pyintesishome==1.7.5 From ff13b4c6b32631b6b4935df2f75ab8ec1ef6eac5 Mon Sep 17 00:00:00 2001 From: Alan Tse Date: Sat, 27 Jun 2020 09:45:34 -0700 Subject: [PATCH 22/47] Bump teslajsonpy to 0.9.0 (#37162) --- homeassistant/components/tesla/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/tesla/manifest.json b/homeassistant/components/tesla/manifest.json index 39aa00cfb60..9a0d80f9a05 100644 --- a/homeassistant/components/tesla/manifest.json +++ b/homeassistant/components/tesla/manifest.json @@ -3,6 +3,6 @@ "name": "Tesla", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/tesla", - "requirements": ["teslajsonpy==0.8.1"], + "requirements": ["teslajsonpy==0.9.0"], "codeowners": ["@zabuldon", "@alandtse"] } diff --git a/requirements_all.txt b/requirements_all.txt index 429cdb998b9..457207e51ab 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2094,7 +2094,7 @@ temperusb==1.5.3 tesla-powerwall==0.2.11 # homeassistant.components.tesla -teslajsonpy==0.8.1 +teslajsonpy==0.9.0 # homeassistant.components.thermoworks_smoke thermoworks_smoke==0.1.8 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index c15b2ed60aa..e9a8f482a92 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -890,7 +890,7 @@ tellduslive==0.10.11 tesla-powerwall==0.2.11 # homeassistant.components.tesla -teslajsonpy==0.8.1 +teslajsonpy==0.9.0 # homeassistant.components.toon toonapi==0.1.0 From a06595c08de80f03fe621191895c0e1510629ecd Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Sat, 27 Jun 2020 22:54:50 -0600 Subject: [PATCH 23/47] Fix bug where Tile session would expire (#37185) --- homeassistant/components/tile/__init__.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/tile/__init__.py b/homeassistant/components/tile/__init__.py index ec9e6bb0f45..4f6411ed368 100644 --- a/homeassistant/components/tile/__init__.py +++ b/homeassistant/components/tile/__init__.py @@ -3,7 +3,7 @@ import asyncio from datetime import timedelta from pytile import async_login -from pytile.errors import TileError +from pytile.errors import SessionExpiredError, TileError from homeassistant.const import ATTR_ATTRIBUTION, CONF_PASSWORD, CONF_USERNAME from homeassistant.core import callback @@ -44,6 +44,9 @@ async def async_setup_entry(hass, config_entry): """Get new data from the API.""" try: return await client.tiles.all() + except SessionExpiredError: + LOGGER.info("Tile session expired; creating a new one") + await client.async_init() except TileError as err: raise UpdateFailed(f"Error while retrieving data: {err}") From 4d7a468c0e861beaf639416f8ef119746239d5e7 Mon Sep 17 00:00:00 2001 From: Rami Mosleh Date: Mon, 29 Jun 2020 22:45:28 +0300 Subject: [PATCH 24/47] Fix updating ping sensor (#37220) --- homeassistant/components/speedtestdotnet/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/homeassistant/components/speedtestdotnet/__init__.py b/homeassistant/components/speedtestdotnet/__init__.py index 3ddd75bb715..3cad15a0967 100644 --- a/homeassistant/components/speedtestdotnet/__init__.py +++ b/homeassistant/components/speedtestdotnet/__init__.py @@ -129,11 +129,10 @@ class SpeedTestDataCoordinator(DataUpdateCoordinator): server_id = self.config_entry.options.get(CONF_SERVER_ID) self.api.closest.clear() self.api.get_servers(servers=[server_id]) - self.api.get_best_server() _LOGGER.debug( "Executing speedtest.net speed test with server_id: %s", self.api.best["id"] ) - + self.api.get_best_server() self.api.download() self.api.upload() return self.api.results.dict() From edc44230b472ed8d6b0ab7615bf8a76f5b421579 Mon Sep 17 00:00:00 2001 From: MatthewFlamm <39341281+MatthewFlamm@users.noreply.github.com> Date: Mon, 29 Jun 2020 18:41:52 -0400 Subject: [PATCH 25/47] Fix wind speed change in NWS (#37222) --- homeassistant/components/nws/weather.py | 11 +++++------ tests/components/nws/const.py | 6 ++---- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/nws/weather.py b/homeassistant/components/nws/weather.py index 7e1ca37ab6b..f7890190490 100644 --- a/homeassistant/components/nws/weather.py +++ b/homeassistant/components/nws/weather.py @@ -190,17 +190,16 @@ class NWSWeather(WeatherEntity): @property def wind_speed(self): """Return the current windspeed.""" - wind_m_s = None + wind_km_hr = None if self.observation: - wind_m_s = self.observation.get("windSpeed") - if wind_m_s is None: + wind_km_hr = self.observation.get("windSpeed") + if wind_km_hr is None: return None - wind_m_hr = wind_m_s * 3600 if self.is_metric: - wind = convert_distance(wind_m_hr, LENGTH_METERS, LENGTH_KILOMETERS) + wind = wind_km_hr else: - wind = convert_distance(wind_m_hr, LENGTH_METERS, LENGTH_MILES) + wind = convert_distance(wind_km_hr, LENGTH_KILOMETERS, LENGTH_MILES) return round(wind) @property diff --git a/tests/components/nws/const.py b/tests/components/nws/const.py index 6dee20a0759..8b23f9cc850 100644 --- a/tests/components/nws/const.py +++ b/tests/components/nws/const.py @@ -60,7 +60,7 @@ EXPECTED_OBSERVATION_IMPERIAL = { ), ATTR_WEATHER_WIND_BEARING: 180, ATTR_WEATHER_WIND_SPEED: round( - convert_distance(10, LENGTH_METERS, LENGTH_MILES) * 3600 + convert_distance(10, LENGTH_KILOMETERS, LENGTH_MILES) ), ATTR_WEATHER_PRESSURE: round( convert_pressure(100000, PRESSURE_PA, PRESSURE_INHG), 2 @@ -74,9 +74,7 @@ EXPECTED_OBSERVATION_IMPERIAL = { EXPECTED_OBSERVATION_METRIC = { ATTR_WEATHER_TEMPERATURE: 10, ATTR_WEATHER_WIND_BEARING: 180, - ATTR_WEATHER_WIND_SPEED: round( - convert_distance(10, LENGTH_METERS, LENGTH_KILOMETERS) * 3600 - ), + ATTR_WEATHER_WIND_SPEED: 10, ATTR_WEATHER_PRESSURE: round(convert_pressure(100000, PRESSURE_PA, PRESSURE_HPA)), ATTR_WEATHER_VISIBILITY: round( convert_distance(10000, LENGTH_METERS, LENGTH_KILOMETERS) From dbdd4f0e3923047befb66e839b70bfe27887e2a6 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 30 Jun 2020 01:23:11 +0200 Subject: [PATCH 26/47] Ensure recorder data integrity and MySQL lock error handling (#37228) --- homeassistant/components/recorder/purge.py | 64 +++++++++++++--------- tests/components/recorder/test_purge.py | 2 +- 2 files changed, 40 insertions(+), 26 deletions(-) diff --git a/homeassistant/components/recorder/purge.py b/homeassistant/components/recorder/purge.py index 8b0b71e24ae..19c2db47768 100644 --- a/homeassistant/components/recorder/purge.py +++ b/homeassistant/components/recorder/purge.py @@ -1,8 +1,9 @@ """Purge old data helper.""" from datetime import timedelta import logging +import time -from sqlalchemy.exc import SQLAlchemyError +from sqlalchemy.exc import OperationalError, SQLAlchemyError import homeassistant.util.dt as dt_util @@ -18,47 +19,46 @@ def purge_old_data(instance, purge_days: int, repack: bool) -> bool: Cleans up an timeframe of an hour, based on the oldest record. """ purge_before = dt_util.utcnow() - timedelta(days=purge_days) - _LOGGER.debug("Purging events before %s", purge_before) + _LOGGER.debug("Purging states and events before target %s", purge_before) try: with session_scope(session=instance.get_session()) as session: + # Purge a max of 1 hour, based on the oldest states or events record + batch_purge_before = purge_before + query = session.query(States).order_by(States.last_updated.asc()).limit(1) states = execute(query, to_native=True, validate_entity_ids=False) - - states_purge_before = purge_before if states: - states_purge_before = min( - purge_before, states[0].last_updated + timedelta(hours=1) + batch_purge_before = min( + batch_purge_before, states[0].last_updated + timedelta(hours=1), ) - deleted_rows_states = ( - session.query(States) - .filter(States.last_updated < states_purge_before) - .delete(synchronize_session=False) - ) - _LOGGER.debug("Deleted %s states", deleted_rows_states) - query = session.query(Events).order_by(Events.time_fired.asc()).limit(1) events = execute(query, to_native=True) - - events_purge_before = purge_before if events: - events_purge_before = min( - purge_before, events[0].time_fired + timedelta(hours=1) + batch_purge_before = min( + batch_purge_before, events[0].time_fired + timedelta(hours=1), ) - deleted_rows_events = ( - session.query(Events) - .filter(Events.time_fired < events_purge_before) + _LOGGER.debug("Purging states and events before %s", batch_purge_before) + + deleted_rows = ( + session.query(States) + .filter(States.last_updated < batch_purge_before) .delete(synchronize_session=False) ) - _LOGGER.debug("Deleted %s events", deleted_rows_events) + _LOGGER.debug("Deleted %s states", deleted_rows) + + deleted_rows = ( + session.query(Events) + .filter(Events.time_fired < batch_purge_before) + .delete(synchronize_session=False) + ) + _LOGGER.debug("Deleted %s events", deleted_rows) # If states or events purging isn't processing the purge_before yet, # return false, as we are not done yet. - if (states_purge_before and states_purge_before != purge_before) or ( - events_purge_before and events_purge_before != purge_before - ): + if batch_purge_before != purge_before: _LOGGER.debug("Purging hasn't fully completed yet.") return False @@ -80,7 +80,21 @@ def purge_old_data(instance, purge_days: int, repack: bool) -> bool: _LOGGER.debug("Optimizing SQL DB to free space") instance.engine.execute("OPTIMIZE TABLE states, events, recorder_runs") + except OperationalError as err: + # Retry when one of the following MySQL errors occurred: + # 1205: Lock wait timeout exceeded; try restarting transaction + # 1206: The total number of locks exceeds the lock table size + # 1213: Deadlock found when trying to get lock; try restarting transaction + if instance.engine.driver in ("mysqldb", "pymysql") and err.orig.args[0] in ( + 1205, + 1206, + 1213, + ): + _LOGGER.info("%s; purge not completed, retrying", err.orig.args[1]) + time.sleep(instance.db_retry_wait) + return False + + _LOGGER.warning("Error purging history: %s.", err) except SQLAlchemyError as err: _LOGGER.warning("Error purging history: %s.", err) - return True diff --git a/tests/components/recorder/test_purge.py b/tests/components/recorder/test_purge.py index afcb1b2818f..93fb6e51621 100644 --- a/tests/components/recorder/test_purge.py +++ b/tests/components/recorder/test_purge.py @@ -224,6 +224,6 @@ class TestRecorderPurge(unittest.TestCase): self.hass.block_till_done() self.hass.data[DATA_INSTANCE].block_till_done() assert ( - mock_logger.debug.mock_calls[4][1][0] + mock_logger.debug.mock_calls[5][1][0] == "Vacuuming SQL DB to free space" ) From d9a2cc93ba212b3d6ba2ef50ce617373048e929c Mon Sep 17 00:00:00 2001 From: definitio <37266727+definitio@users.noreply.github.com> Date: Tue, 30 Jun 2020 02:36:52 +0400 Subject: [PATCH 27/47] Fixes after PR #36479 (#37230) --- homeassistant/components/mqtt/__init__.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/mqtt/__init__.py b/homeassistant/components/mqtt/__init__.py index 69d84705088..bb2ec7a8bcb 100644 --- a/homeassistant/components/mqtt/__init__.py +++ b/homeassistant/components/mqtt/__init__.py @@ -994,6 +994,9 @@ class MqttAvailability(Entity): await self._availability_subscribe_topics() async_dispatcher_connect(self.hass, MQTT_CONNECTED, self.async_mqtt_connect) async_dispatcher_connect(self.hass, MQTT_DISCONNECTED, self.async_mqtt_connect) + self.async_on_remove( + async_dispatcher_connect(self.hass, MQTT_CONNECTED, self.async_mqtt_connect) + ) async def availability_discovery_update(self, config: dict): """Handle updated discovery message.""" @@ -1029,7 +1032,8 @@ class MqttAvailability(Entity): @callback def async_mqtt_connect(self): """Update state on connection/disconnection to MQTT broker.""" - self.async_write_ha_state() + if self.hass.is_running: + self.async_write_ha_state() async def async_will_remove_from_hass(self): """Unsubscribe when removed.""" From caf306799b1be01c9614462905c0e1bac0f189f9 Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Mon, 29 Jun 2020 18:25:01 -0600 Subject: [PATCH 28/47] Fix Tile location accuracy bug (#37233) Co-authored-by: Paulus Schoutsen --- .../components/tile/device_tracker.py | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/tile/device_tracker.py b/homeassistant/components/tile/device_tracker.py index 910732f7c04..5b0065b2c4e 100644 --- a/homeassistant/components/tile/device_tracker.py +++ b/homeassistant/components/tile/device_tracker.py @@ -84,13 +84,26 @@ class TileDeviceTracker(TileEntity, TrackerEntity): Value in meters. """ - return round( - ( - self._tile["last_tile_state"]["h_accuracy"] - + self._tile["last_tile_state"]["v_accuracy"] + state = self._tile["last_tile_state"] + h_accuracy = state.get("h_accuracy") + v_accuracy = state.get("v_accuracy") + + if h_accuracy is not None and v_accuracy is not None: + return round( + ( + self._tile["last_tile_state"]["h_accuracy"] + + self._tile["last_tile_state"]["v_accuracy"] + ) + / 2 ) - / 2 - ) + + if h_accuracy is not None: + return h_accuracy + + if v_accuracy is not None: + return v_accuracy + + return None @property def latitude(self) -> float: From f0a8e8ea0413431c36366b3006dc61ec43db2774 Mon Sep 17 00:00:00 2001 From: "David F. Mulcahey" Date: Mon, 29 Jun 2020 18:37:42 -0400 Subject: [PATCH 29/47] Bump ZHA Quirks to 0.0.41 (#37235) --- homeassistant/components/zha/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/zha/manifest.json b/homeassistant/components/zha/manifest.json index c6dbeecf68b..c9b25b58e25 100644 --- a/homeassistant/components/zha/manifest.json +++ b/homeassistant/components/zha/manifest.json @@ -6,7 +6,7 @@ "requirements": [ "bellows==0.17.0", "pyserial==3.4", - "zha-quirks==0.0.40", + "zha-quirks==0.0.41", "zigpy-cc==0.4.4", "zigpy-deconz==0.9.2", "zigpy==0.21.0", diff --git a/requirements_all.txt b/requirements_all.txt index 457207e51ab..63588b70ce6 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2255,7 +2255,7 @@ zengge==0.2 zeroconf==0.27.1 # homeassistant.components.zha -zha-quirks==0.0.40 +zha-quirks==0.0.41 # homeassistant.components.zhong_hong zhong_hong_hvac==1.0.9 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index e9a8f482a92..a2791efadfc 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -961,7 +961,7 @@ ya_ma==0.3.8 zeroconf==0.27.1 # homeassistant.components.zha -zha-quirks==0.0.40 +zha-quirks==0.0.41 # homeassistant.components.zha zigpy-cc==0.4.4 From b7a071b23f460722a4fe133d1ccbae374dc79fae Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 29 Jun 2020 15:54:02 -0700 Subject: [PATCH 30/47] Updated frontend to 20200629.0 (#37240) --- homeassistant/components/frontend/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/frontend/manifest.json b/homeassistant/components/frontend/manifest.json index 6fc6bba73ff..81370e96511 100644 --- a/homeassistant/components/frontend/manifest.json +++ b/homeassistant/components/frontend/manifest.json @@ -3,7 +3,7 @@ "name": "Home Assistant Frontend", "documentation": "https://www.home-assistant.io/integrations/frontend", "requirements": [ - "home-assistant-frontend==20200626.1" + "home-assistant-frontend==20200629.0" ], "dependencies": [ "api", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 683569773fa..657c1ebc33a 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -13,7 +13,7 @@ defusedxml==0.6.0 distro==1.5.0 emoji==0.5.4 hass-nabucasa==0.34.7 -home-assistant-frontend==20200626.1 +home-assistant-frontend==20200629.0 importlib-metadata==1.6.0;python_version<'3.8' jinja2>=2.11.1 netdisco==2.7.1 diff --git a/requirements_all.txt b/requirements_all.txt index 63588b70ce6..28885d92b7e 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -738,7 +738,7 @@ hole==0.5.1 holidays==0.10.2 # homeassistant.components.frontend -home-assistant-frontend==20200626.1 +home-assistant-frontend==20200629.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index a2791efadfc..208eec064f8 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -343,7 +343,7 @@ hole==0.5.1 holidays==0.10.2 # homeassistant.components.frontend -home-assistant-frontend==20200626.1 +home-assistant-frontend==20200629.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 From 4c052643cac741a18efb186b83686b7dfbbe38a8 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 30 Jun 2020 00:25:44 +0000 Subject: [PATCH 31/47] Bumped version to 0.112.0b3 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 5a786552a72..1402c44dcdf 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 112 -PATCH_VERSION = "0b2" +PATCH_VERSION = "0b3" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER = (3, 7, 0) From a8e86a62a4fc264c18853de73099fa17be2a0f64 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 30 Jun 2020 14:41:09 -0500 Subject: [PATCH 32/47] Update myq for latest client version requirement (#37104) --- homeassistant/components/myq/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/myq/manifest.json b/homeassistant/components/myq/manifest.json index 10107967056..540f08c0776 100644 --- a/homeassistant/components/myq/manifest.json +++ b/homeassistant/components/myq/manifest.json @@ -2,7 +2,7 @@ "domain": "myq", "name": "MyQ", "documentation": "https://www.home-assistant.io/integrations/myq", - "requirements": ["pymyq==2.0.4"], + "requirements": ["pymyq==2.0.5"], "codeowners": ["@bdraco"], "config_flow": true, "homekit": { diff --git a/requirements_all.txt b/requirements_all.txt index 28885d92b7e..a32f5b1cdbc 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1481,7 +1481,7 @@ pymsteams==0.1.12 pymusiccast==0.1.6 # homeassistant.components.myq -pymyq==2.0.4 +pymyq==2.0.5 # homeassistant.components.mysensors pymysensors==0.18.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 208eec064f8..348ef33084f 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -670,7 +670,7 @@ pymodbus==2.3.0 pymonoprice==0.3 # homeassistant.components.myq -pymyq==2.0.4 +pymyq==2.0.5 # homeassistant.components.nut pynut2==2.1.2 From a7be7bcd0aeb2b75abef9101f756c47198c21ec9 Mon Sep 17 00:00:00 2001 From: Sven-Hendrik Haase Date: Tue, 30 Jun 2020 02:49:22 +0200 Subject: [PATCH 33/47] Update fritzconnection to 1.3.0 (#37212) This effectively fixes an important bug where the graph would go negative because 1.2.0 used the 32-bit counters and 1.3.0 uses 64-bit counters will not realistically go negative any time soon. --- homeassistant/components/fritz/manifest.json | 2 +- homeassistant/components/fritzbox_callmonitor/manifest.json | 2 +- homeassistant/components/fritzbox_netmonitor/manifest.json | 2 +- requirements_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/fritz/manifest.json b/homeassistant/components/fritz/manifest.json index 3723bd7885a..b82038b8404 100644 --- a/homeassistant/components/fritz/manifest.json +++ b/homeassistant/components/fritz/manifest.json @@ -2,6 +2,6 @@ "domain": "fritz", "name": "AVM FRITZ!Box", "documentation": "https://www.home-assistant.io/integrations/fritz", - "requirements": ["fritzconnection==1.2.0"], + "requirements": ["fritzconnection==1.3.0"], "codeowners": [] } diff --git a/homeassistant/components/fritzbox_callmonitor/manifest.json b/homeassistant/components/fritzbox_callmonitor/manifest.json index b5fa26c096b..7f78b0ab9b5 100644 --- a/homeassistant/components/fritzbox_callmonitor/manifest.json +++ b/homeassistant/components/fritzbox_callmonitor/manifest.json @@ -2,6 +2,6 @@ "domain": "fritzbox_callmonitor", "name": "AVM FRITZ!Box Call Monitor", "documentation": "https://www.home-assistant.io/integrations/fritzbox_callmonitor", - "requirements": ["fritzconnection==1.2.0"], + "requirements": ["fritzconnection==1.3.0"], "codeowners": [] } diff --git a/homeassistant/components/fritzbox_netmonitor/manifest.json b/homeassistant/components/fritzbox_netmonitor/manifest.json index dde4d634867..4813c98442e 100644 --- a/homeassistant/components/fritzbox_netmonitor/manifest.json +++ b/homeassistant/components/fritzbox_netmonitor/manifest.json @@ -2,6 +2,6 @@ "domain": "fritzbox_netmonitor", "name": "AVM FRITZ!Box Net Monitor", "documentation": "https://www.home-assistant.io/integrations/fritzbox_netmonitor", - "requirements": ["fritzconnection==1.2.0"], + "requirements": ["fritzconnection==1.3.0"], "codeowners": [] } diff --git a/requirements_all.txt b/requirements_all.txt index a32f5b1cdbc..c085c6bf007 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -610,7 +610,7 @@ freesms==0.1.2 # homeassistant.components.fritz # homeassistant.components.fritzbox_callmonitor # homeassistant.components.fritzbox_netmonitor -fritzconnection==1.2.0 +fritzconnection==1.3.0 # homeassistant.components.google_translate gTTS-token==1.1.3 From cce95312a958c7649e8a832009a9df392736397a Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 30 Jun 2020 19:43:05 +0000 Subject: [PATCH 34/47] Bumped version to 0.112.0b4 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 1402c44dcdf..8cd38bb0f33 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 112 -PATCH_VERSION = "0b3" +PATCH_VERSION = "0b4" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER = (3, 7, 0) From 3c260c91c83aefa8f6f451b29a61e93c65f04b2b Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Wed, 1 Jul 2020 13:31:06 +0200 Subject: [PATCH 35/47] Revert "Update fritzconnection to 1.3.0" (#37278) This reverts commit 2f46a81e3ebddadb501f8c2a796271e543d20648. --- homeassistant/components/fritz/manifest.json | 2 +- homeassistant/components/fritzbox_callmonitor/manifest.json | 2 +- homeassistant/components/fritzbox_netmonitor/manifest.json | 2 +- requirements_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/fritz/manifest.json b/homeassistant/components/fritz/manifest.json index b82038b8404..3723bd7885a 100644 --- a/homeassistant/components/fritz/manifest.json +++ b/homeassistant/components/fritz/manifest.json @@ -2,6 +2,6 @@ "domain": "fritz", "name": "AVM FRITZ!Box", "documentation": "https://www.home-assistant.io/integrations/fritz", - "requirements": ["fritzconnection==1.3.0"], + "requirements": ["fritzconnection==1.2.0"], "codeowners": [] } diff --git a/homeassistant/components/fritzbox_callmonitor/manifest.json b/homeassistant/components/fritzbox_callmonitor/manifest.json index 7f78b0ab9b5..b5fa26c096b 100644 --- a/homeassistant/components/fritzbox_callmonitor/manifest.json +++ b/homeassistant/components/fritzbox_callmonitor/manifest.json @@ -2,6 +2,6 @@ "domain": "fritzbox_callmonitor", "name": "AVM FRITZ!Box Call Monitor", "documentation": "https://www.home-assistant.io/integrations/fritzbox_callmonitor", - "requirements": ["fritzconnection==1.3.0"], + "requirements": ["fritzconnection==1.2.0"], "codeowners": [] } diff --git a/homeassistant/components/fritzbox_netmonitor/manifest.json b/homeassistant/components/fritzbox_netmonitor/manifest.json index 4813c98442e..dde4d634867 100644 --- a/homeassistant/components/fritzbox_netmonitor/manifest.json +++ b/homeassistant/components/fritzbox_netmonitor/manifest.json @@ -2,6 +2,6 @@ "domain": "fritzbox_netmonitor", "name": "AVM FRITZ!Box Net Monitor", "documentation": "https://www.home-assistant.io/integrations/fritzbox_netmonitor", - "requirements": ["fritzconnection==1.3.0"], + "requirements": ["fritzconnection==1.2.0"], "codeowners": [] } diff --git a/requirements_all.txt b/requirements_all.txt index c085c6bf007..a32f5b1cdbc 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -610,7 +610,7 @@ freesms==0.1.2 # homeassistant.components.fritz # homeassistant.components.fritzbox_callmonitor # homeassistant.components.fritzbox_netmonitor -fritzconnection==1.3.0 +fritzconnection==1.2.0 # homeassistant.components.google_translate gTTS-token==1.1.3 From 20c66b1fa393ed43c907af932d2466ae1baea9bc Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Wed, 1 Jul 2020 14:34:36 +0200 Subject: [PATCH 36/47] Updated frontend to 20200701.0 (#37279) --- homeassistant/components/frontend/manifest.json | 10 +++------- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 6 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/frontend/manifest.json b/homeassistant/components/frontend/manifest.json index 81370e96511..8be62aa42f6 100644 --- a/homeassistant/components/frontend/manifest.json +++ b/homeassistant/components/frontend/manifest.json @@ -2,9 +2,7 @@ "domain": "frontend", "name": "Home Assistant Frontend", "documentation": "https://www.home-assistant.io/integrations/frontend", - "requirements": [ - "home-assistant-frontend==20200629.0" - ], + "requirements": ["home-assistant-frontend==20200701.0"], "dependencies": [ "api", "auth", @@ -17,8 +15,6 @@ "system_log", "websocket_api" ], - "codeowners": [ - "@home-assistant/frontend" - ], + "codeowners": ["@home-assistant/frontend"], "quality_scale": "internal" -} \ No newline at end of file +} diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 657c1ebc33a..8045e7b808b 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -13,7 +13,7 @@ defusedxml==0.6.0 distro==1.5.0 emoji==0.5.4 hass-nabucasa==0.34.7 -home-assistant-frontend==20200629.0 +home-assistant-frontend==20200701.0 importlib-metadata==1.6.0;python_version<'3.8' jinja2>=2.11.1 netdisco==2.7.1 diff --git a/requirements_all.txt b/requirements_all.txt index a32f5b1cdbc..3dce8223d59 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -738,7 +738,7 @@ hole==0.5.1 holidays==0.10.2 # homeassistant.components.frontend -home-assistant-frontend==20200629.0 +home-assistant-frontend==20200701.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 348ef33084f..6a755a2db01 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -343,7 +343,7 @@ hole==0.5.1 holidays==0.10.2 # homeassistant.components.frontend -home-assistant-frontend==20200629.0 +home-assistant-frontend==20200701.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 From 96d0ee3153031a4917e9a8104cad2f950c06da1b Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 1 Jul 2020 14:38:24 +0200 Subject: [PATCH 37/47] Bumped version to 0.112.0 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 8cd38bb0f33..f72c0011b17 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 112 -PATCH_VERSION = "0b4" +PATCH_VERSION = "0" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER = (3, 7, 0) From 976d375a332dce44450c7365e2cb2375e51debb7 Mon Sep 17 00:00:00 2001 From: John Hollowell Date: Thu, 2 Jul 2020 08:10:14 -0400 Subject: [PATCH 38/47] Update proxmoxve integration to correctly renew authentication (#37016) --- homeassistant/components/proxmoxve/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/proxmoxve/manifest.json b/homeassistant/components/proxmoxve/manifest.json index 4040ca7c469..a47ce0a28ee 100644 --- a/homeassistant/components/proxmoxve/manifest.json +++ b/homeassistant/components/proxmoxve/manifest.json @@ -3,5 +3,5 @@ "name": "Proxmox VE", "documentation": "https://www.home-assistant.io/integrations/proxmoxve", "codeowners": ["@k4ds3", "@jhollowe"], - "requirements": ["proxmoxer==1.1.0"] + "requirements": ["proxmoxer==1.1.1"] } diff --git a/requirements_all.txt b/requirements_all.txt index 3dce8223d59..5ca305e33c1 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1125,7 +1125,7 @@ prometheus_client==0.7.1 protobuf==3.6.1 # homeassistant.components.proxmoxve -proxmoxer==1.1.0 +proxmoxer==1.1.1 # homeassistant.components.systemmonitor psutil==5.7.0 From 9131f5fa69230d2f3440222e25a83602596079fc Mon Sep 17 00:00:00 2001 From: Courtenay Date: Thu, 2 Jul 2020 05:14:17 -0700 Subject: [PATCH 39/47] Change log url in config check error notification (#37311) --- homeassistant/components/hassio/__init__.py | 2 +- homeassistant/components/homeassistant/__init__.py | 2 +- homeassistant/components/safe_mode/__init__.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/hassio/__init__.py b/homeassistant/components/hassio/__init__.py index 6939c1f7073..d427b2be60d 100644 --- a/homeassistant/components/hassio/__init__.py +++ b/homeassistant/components/hassio/__init__.py @@ -325,7 +325,7 @@ async def async_setup(hass, config): if errors: _LOGGER.error(errors) hass.components.persistent_notification.async_create( - "Config error. See [the logs](/developer-tools/logs) for details.", + "Config error. See [the logs](/config/logs) for details.", "Config validating", f"{HASS_DOMAIN}.check_config", ) diff --git a/homeassistant/components/homeassistant/__init__.py b/homeassistant/components/homeassistant/__init__.py index 83166ba4cce..f1b8d4e87d6 100644 --- a/homeassistant/components/homeassistant/__init__.py +++ b/homeassistant/components/homeassistant/__init__.py @@ -112,7 +112,7 @@ async def async_setup(hass: ha.HomeAssistant, config: dict) -> bool: if errors: _LOGGER.error(errors) hass.components.persistent_notification.async_create( - "Config error. See [the logs](/developer-tools/logs) for details.", + "Config error. See [the logs](/config/logs) for details.", "Config validating", f"{ha.DOMAIN}.check_config", ) diff --git a/homeassistant/components/safe_mode/__init__.py b/homeassistant/components/safe_mode/__init__.py index aef6834303b..94bd95aabe0 100644 --- a/homeassistant/components/safe_mode/__init__.py +++ b/homeassistant/components/safe_mode/__init__.py @@ -9,7 +9,7 @@ async def async_setup(hass: HomeAssistant, config: dict): """Set up the Safe Mode component.""" persistent_notification.async_create( hass, - "Home Assistant is running in safe mode. Check [the error log](/developer-tools/logs) to see what went wrong.", + "Home Assistant is running in safe mode. Check [the error log](/config/logs) to see what went wrong.", "Safe Mode", ) return True From f3351277506adf33ab2140eb09ca957462dbf7b9 Mon Sep 17 00:00:00 2001 From: bsmappee <58250533+bsmappee@users.noreply.github.com> Date: Thu, 2 Jul 2020 13:57:43 +0200 Subject: [PATCH 40/47] Smappee dependency update (#37331) --- homeassistant/components/smappee/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/smappee/manifest.json b/homeassistant/components/smappee/manifest.json index e316273f3ed..3ca8dc4b1b6 100644 --- a/homeassistant/components/smappee/manifest.json +++ b/homeassistant/components/smappee/manifest.json @@ -5,7 +5,7 @@ "documentation": "https://www.home-assistant.io/integrations/smappee", "dependencies": ["http"], "requirements": [ - "pysmappee==0.1.0" + "pysmappee==0.1.2" ], "codeowners": [ "@bsmappee" diff --git a/requirements_all.txt b/requirements_all.txt index 5ca305e33c1..4e3673cf826 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1610,7 +1610,7 @@ pysignalclirestapi==0.3.4 pysma==0.3.5 # homeassistant.components.smappee -pysmappee==0.1.0 +pysmappee==0.1.2 # homeassistant.components.smartthings pysmartapp==0.3.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 6a755a2db01..d86b0e10ecd 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -715,7 +715,7 @@ pysignalclirestapi==0.3.4 pysma==0.3.5 # homeassistant.components.smappee -pysmappee==0.1.0 +pysmappee==0.1.2 # homeassistant.components.smartthings pysmartapp==0.3.2 From 9b854bdcd3932a468e3046c78d9d3168fff23309 Mon Sep 17 00:00:00 2001 From: Markus Bong Date: Thu, 2 Jul 2020 13:54:30 +0200 Subject: [PATCH 41/47] Fix devolo sensor subscriber (#37337) --- homeassistant/components/devolo_home_control/sensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/devolo_home_control/sensor.py b/homeassistant/components/devolo_home_control/sensor.py index d0d3388ef17..0f02f6d0dd0 100644 --- a/homeassistant/components/devolo_home_control/sensor.py +++ b/homeassistant/components/devolo_home_control/sensor.py @@ -80,7 +80,7 @@ class DevoloMultiLevelDeviceEntity(DevoloDeviceEntity): def _sync(self, message=None): """Update the multi level sensor state.""" - if message[0].startswith("devolo.MultiLevelSensor"): + if message[0] == self._multi_level_sensor_property.element_uid: self._state = self._device_instance.multi_level_sensor_property[ message[0] ].value From 69a5c63b715b5e87b56bef171dcde077e7172288 Mon Sep 17 00:00:00 2001 From: Robert Van Gorkom Date: Thu, 2 Jul 2020 10:49:26 -0700 Subject: [PATCH 42/47] Fix gogogate2 issue where non-admin users could not login (#37353) --- homeassistant/components/gogogate2/config_flow.py | 2 +- homeassistant/components/gogogate2/manifest.json | 2 +- homeassistant/components/gogogate2/strings.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/gogogate2/config_flow.py b/homeassistant/components/gogogate2/config_flow.py index 8c33af6af10..bca340fa62b 100644 --- a/homeassistant/components/gogogate2/config_flow.py +++ b/homeassistant/components/gogogate2/config_flow.py @@ -63,7 +63,7 @@ class Gogogate2FlowHandler(ConfigFlow, domain=DOMAIN): CONF_IP_ADDRESS, default=user_input.get(CONF_IP_ADDRESS, "") ): str, vol.Required( - CONF_USERNAME, default=user_input.get(CONF_USERNAME, "admin") + CONF_USERNAME, default=user_input.get(CONF_USERNAME, "") ): str, vol.Required( CONF_PASSWORD, default=user_input.get(CONF_PASSWORD, "") diff --git a/homeassistant/components/gogogate2/manifest.json b/homeassistant/components/gogogate2/manifest.json index 98aabba43b8..588d68484f2 100644 --- a/homeassistant/components/gogogate2/manifest.json +++ b/homeassistant/components/gogogate2/manifest.json @@ -3,6 +3,6 @@ "name": "Gogogate2", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/gogogate2", - "requirements": ["gogogate2-api==1.0.3"], + "requirements": ["gogogate2-api==1.0.4"], "codeowners": ["@vangorra"] } diff --git a/homeassistant/components/gogogate2/strings.json b/homeassistant/components/gogogate2/strings.json index d519d9b9ea0..bbd4e8d80d1 100644 --- a/homeassistant/components/gogogate2/strings.json +++ b/homeassistant/components/gogogate2/strings.json @@ -10,7 +10,7 @@ "step": { "user": { "title": "Setup GogoGate2", - "description": "Provide requisite information below. Note: only the 'admin' user is known to work.", + "description": "Provide requisite information below.", "data": { "ip_address": "IP Address", "username": "[%key:common::config_flow::data::username%]", diff --git a/requirements_all.txt b/requirements_all.txt index 4e3673cf826..47e17858319 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -660,7 +660,7 @@ glances_api==0.2.0 gntp==1.0.3 # homeassistant.components.gogogate2 -gogogate2-api==1.0.3 +gogogate2-api==1.0.4 # homeassistant.components.google google-api-python-client==1.6.4 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index d86b0e10ecd..024b72210c1 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -310,7 +310,7 @@ gios==0.1.1 glances_api==0.2.0 # homeassistant.components.gogogate2 -gogogate2-api==1.0.3 +gogogate2-api==1.0.4 # homeassistant.components.google google-api-python-client==1.6.4 From b21c81656fb2e30623577ae126efd73f57a51161 Mon Sep 17 00:00:00 2001 From: uvjustin <46082645+uvjustin@users.noreply.github.com> Date: Fri, 3 Jul 2020 01:51:13 +0800 Subject: [PATCH 43/47] =?UTF-8?q?Use=20entry.data.get()=20in=20forked=5Fda?= =?UTF-8?q?apd=20config=5Fflow=20as=20some=20entries=20miss=E2=80=A6=20(#3?= =?UTF-8?q?7359)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- homeassistant/components/forked_daapd/config_flow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/forked_daapd/config_flow.py b/homeassistant/components/forked_daapd/config_flow.py index d27c40af316..45ea8861d4a 100644 --- a/homeassistant/components/forked_daapd/config_flow.py +++ b/homeassistant/components/forked_daapd/config_flow.py @@ -133,7 +133,7 @@ class ForkedDaapdFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): if user_input is not None: # check for any entries with same host, abort if found for entry in self._async_current_entries(): - if entry.data[CONF_HOST] == user_input[CONF_HOST]: + if entry.data.get(CONF_HOST) == user_input[CONF_HOST]: return self.async_abort(reason="already_configured") validate_result = await self.validate_input(user_input) if validate_result[0] == "ok": # success From 5013b7e049e74b1f5d5a7e076eaec9447efab665 Mon Sep 17 00:00:00 2001 From: Robert Van Gorkom Date: Thu, 2 Jul 2020 13:09:49 -0700 Subject: [PATCH 44/47] Fix withings bug that grabbed oldest value instead of the newest (#37362) Co-authored-by: Paulus Schoutsen --- homeassistant/components/withings/common.py | 9 +++- tests/components/withings/test_sensor.py | 59 +++++++++++++++++++-- 2 files changed, 61 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/withings/common.py b/homeassistant/components/withings/common.py index b7c3cec6d9b..5d8d5799fc1 100644 --- a/homeassistant/components/withings/common.py +++ b/homeassistant/components/withings/common.py @@ -770,8 +770,13 @@ class DataManager: response = await self._hass.async_add_executor_job(self._api.measure_get_meas) - groups = query_measure_groups( - response, MeasureTypes.ANY, MeasureGroupAttribs.UNAMBIGUOUS + # Sort from oldest to newest. + groups = sorted( + query_measure_groups( + response, MeasureTypes.ANY, MeasureGroupAttribs.UNAMBIGUOUS + ), + key=lambda group: group.created.datetime, + reverse=False, ) return { diff --git a/tests/components/withings/test_sensor.py b/tests/components/withings/test_sensor.py index abfc4758251..3370c23e3d8 100644 --- a/tests/components/withings/test_sensor.py +++ b/tests/components/withings/test_sensor.py @@ -1,5 +1,4 @@ """Tests for the Withings component.""" -import time from typing import Any from unittest.mock import patch @@ -40,8 +39,8 @@ PERSON0 = new_profile_config( MeasureGetMeasGroup( attrib=MeasureGetMeasGroupAttrib.DEVICE_ENTRY_FOR_USER, category=MeasureGetMeasGroupCategory.REAL, - created=time.time(), - date=time.time(), + created=arrow.utcnow().shift(hours=-1), + date=arrow.utcnow().shift(hours=-1), deviceid="DEV_ID", grpid=1, measures=( @@ -87,11 +86,61 @@ PERSON0 = new_profile_config( ), ), ), + MeasureGetMeasGroup( + attrib=MeasureGetMeasGroupAttrib.DEVICE_ENTRY_FOR_USER, + category=MeasureGetMeasGroupCategory.REAL, + created=arrow.utcnow().shift(hours=-2), + date=arrow.utcnow().shift(hours=-2), + deviceid="DEV_ID", + grpid=1, + measures=( + MeasureGetMeasMeasure(type=MeasureType.WEIGHT, unit=0, value=71), + MeasureGetMeasMeasure( + type=MeasureType.FAT_MASS_WEIGHT, unit=0, value=51 + ), + MeasureGetMeasMeasure( + type=MeasureType.FAT_FREE_MASS, unit=0, value=61 + ), + MeasureGetMeasMeasure( + type=MeasureType.MUSCLE_MASS, unit=0, value=51 + ), + MeasureGetMeasMeasure(type=MeasureType.BONE_MASS, unit=0, value=11), + MeasureGetMeasMeasure(type=MeasureType.HEIGHT, unit=0, value=21), + MeasureGetMeasMeasure( + type=MeasureType.TEMPERATURE, unit=0, value=41 + ), + MeasureGetMeasMeasure( + type=MeasureType.BODY_TEMPERATURE, unit=0, value=41 + ), + MeasureGetMeasMeasure( + type=MeasureType.SKIN_TEMPERATURE, unit=0, value=21 + ), + MeasureGetMeasMeasure( + type=MeasureType.FAT_RATIO, unit=-3, value=71 + ), + MeasureGetMeasMeasure( + type=MeasureType.DIASTOLIC_BLOOD_PRESSURE, unit=0, value=71 + ), + MeasureGetMeasMeasure( + type=MeasureType.SYSTOLIC_BLOOD_PRESSURE, unit=0, value=101 + ), + MeasureGetMeasMeasure( + type=MeasureType.HEART_RATE, unit=0, value=61 + ), + MeasureGetMeasMeasure(type=MeasureType.SP02, unit=-2, value=96), + MeasureGetMeasMeasure( + type=MeasureType.HYDRATION, unit=-2, value=96 + ), + MeasureGetMeasMeasure( + type=MeasureType.PULSE_WAVE_VELOCITY, unit=0, value=101 + ), + ), + ), MeasureGetMeasGroup( attrib=MeasureGetMeasGroupAttrib.DEVICE_ENTRY_FOR_USER_AMBIGUOUS, category=MeasureGetMeasGroupCategory.REAL, - created=time.time(), - date=time.time(), + created=arrow.utcnow(), + date=arrow.utcnow(), deviceid="DEV_ID", grpid=1, measures=( From 38599d2970c26fba615377327e2ddeb5fac31526 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Thu, 2 Jul 2020 21:53:16 +0200 Subject: [PATCH 45/47] Update frontend to 20200702.0 (#37369) --- homeassistant/components/frontend/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/frontend/manifest.json b/homeassistant/components/frontend/manifest.json index 8be62aa42f6..8100d3afd7e 100644 --- a/homeassistant/components/frontend/manifest.json +++ b/homeassistant/components/frontend/manifest.json @@ -2,7 +2,7 @@ "domain": "frontend", "name": "Home Assistant Frontend", "documentation": "https://www.home-assistant.io/integrations/frontend", - "requirements": ["home-assistant-frontend==20200701.0"], + "requirements": ["home-assistant-frontend==20200702.0"], "dependencies": [ "api", "auth", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 8045e7b808b..79cc585d735 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -13,7 +13,7 @@ defusedxml==0.6.0 distro==1.5.0 emoji==0.5.4 hass-nabucasa==0.34.7 -home-assistant-frontend==20200701.0 +home-assistant-frontend==20200702.0 importlib-metadata==1.6.0;python_version<'3.8' jinja2>=2.11.1 netdisco==2.7.1 diff --git a/requirements_all.txt b/requirements_all.txt index 47e17858319..9b2133e9d34 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -738,7 +738,7 @@ hole==0.5.1 holidays==0.10.2 # homeassistant.components.frontend -home-assistant-frontend==20200701.0 +home-assistant-frontend==20200702.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 024b72210c1..8ec86122a41 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -343,7 +343,7 @@ hole==0.5.1 holidays==0.10.2 # homeassistant.components.frontend -home-assistant-frontend==20200701.0 +home-assistant-frontend==20200702.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 From 16dae8457abd88d776a5c900943cc842e8d23994 Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Thu, 2 Jul 2020 22:10:38 +0200 Subject: [PATCH 46/47] Add DenonAvr missing error message (#37370) --- homeassistant/components/denonavr/receiver.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/homeassistant/components/denonavr/receiver.py b/homeassistant/components/denonavr/receiver.py index 557427c8c41..f30469961df 100644 --- a/homeassistant/components/denonavr/receiver.py +++ b/homeassistant/components/denonavr/receiver.py @@ -39,6 +39,13 @@ class ConnectDenonAVR: or self._receiver.model_name is None or self._receiver.receiver_type is None ): + _LOGGER.error( + "Missing receiver information: manufacturer '%s', name '%s', model '%s', type '%s'", + self._receiver.manufacturer, + self._receiver.name, + self._receiver.model_name, + self._receiver.receiver_type, + ) return False _LOGGER.debug( From ec690bb3697167197917403ecaa5078164f03730 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 2 Jul 2020 20:15:44 +0000 Subject: [PATCH 47/47] Bumped version to 0.112.1 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index f72c0011b17..dffaf87e1c2 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 112 -PATCH_VERSION = "0" +PATCH_VERSION = "1" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER = (3, 7, 0)