From 722d285904b2cb3344772cd23110684fb41d365b Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 16 Jan 2019 16:27:15 -0800 Subject: [PATCH 01/37] Bumped version to 0.86.0b0 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 370a4d82faf..bb70e635ce3 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -2,7 +2,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 86 -PATCH_VERSION = '0.dev0' +PATCH_VERSION = '0b0' __short_version__ = '{}.{}'.format(MAJOR_VERSION, MINOR_VERSION) __version__ = '{}.{}'.format(__short_version__, PATCH_VERSION) REQUIRED_PYTHON_VER = (3, 5, 3) From d2dec44b18faa6e07a051c68f7756a8ae7cdd73e Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 20 Jan 2019 16:22:42 -0800 Subject: [PATCH 02/37] Updated frontend to 20190120.0 --- homeassistant/components/frontend/__init__.py | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/frontend/__init__.py b/homeassistant/components/frontend/__init__.py index a60168ac114..4d18b2fa841 100644 --- a/homeassistant/components/frontend/__init__.py +++ b/homeassistant/components/frontend/__init__.py @@ -24,7 +24,7 @@ from homeassistant.core import callback from homeassistant.helpers.translation import async_get_translations from homeassistant.loader import bind_hass -REQUIREMENTS = ['home-assistant-frontend==20190116.0'] +REQUIREMENTS = ['home-assistant-frontend==20190120.0'] DOMAIN = 'frontend' DEPENDENCIES = ['api', 'websocket_api', 'http', 'system_log', diff --git a/requirements_all.txt b/requirements_all.txt index cd6c901c4b7..dc14bdf84c5 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -514,7 +514,7 @@ hole==0.3.0 holidays==0.9.9 # homeassistant.components.frontend -home-assistant-frontend==20190116.0 +home-assistant-frontend==20190120.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index fb49f040b7e..934daa56d98 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -107,7 +107,7 @@ hdate==0.8.7 holidays==0.9.9 # homeassistant.components.frontend -home-assistant-frontend==20190116.0 +home-assistant-frontend==20190120.0 # homeassistant.components.homematicip_cloud homematicip==0.10.3 From 27be95e5978877bb606eeccbef1fa835ef96a296 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 16 Jan 2019 23:12:18 -0800 Subject: [PATCH 03/37] Sensibo to use HA operation modes (#20180) --- homeassistant/components/climate/sensibo.py | 25 ++++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/climate/sensibo.py b/homeassistant/components/climate/sensibo.py index 8532c611d25..bf1cf5bf345 100644 --- a/homeassistant/components/climate/sensibo.py +++ b/homeassistant/components/climate/sensibo.py @@ -19,7 +19,8 @@ from homeassistant.components.climate import ( ATTR_CURRENT_HUMIDITY, ClimateDevice, DOMAIN, PLATFORM_SCHEMA, SUPPORT_TARGET_TEMPERATURE, SUPPORT_OPERATION_MODE, SUPPORT_FAN_MODE, SUPPORT_SWING_MODE, - SUPPORT_ON_OFF) + SUPPORT_ON_OFF, STATE_HEAT, STATE_COOL, STATE_FAN_ONLY, STATE_DRY, + STATE_AUTO) from homeassistant.exceptions import PlatformNotReady from homeassistant.helpers import config_validation as cv from homeassistant.helpers.aiohttp_client import async_get_clientsession @@ -57,6 +58,16 @@ FIELD_TO_FLAG = { 'on': SUPPORT_ON_OFF, } +SENSIBO_TO_HA = { + "cool": STATE_COOL, + "heat": STATE_HEAT, + "fan": STATE_FAN_ONLY, + "auto": STATE_AUTO, + "dry": STATE_DRY +} + +HA_TO_SENSIBO = {value: key for key, value in SENSIBO_TO_HA.items()} + async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): @@ -129,9 +140,10 @@ class SensiboClimate(ClimateDevice): self._ac_states = data['acState'] self._status = data['connectionStatus']['isAlive'] capabilities = data['remoteCapabilities'] - self._operations = sorted(capabilities['modes'].keys()) - self._current_capabilities = capabilities[ - 'modes'][self.current_operation] + self._operations = [SENSIBO_TO_HA[mode] for mode + in capabilities['modes']] + self._current_capabilities = \ + capabilities['modes'][self._ac_states['mode']] temperature_unit_key = data.get('temperatureUnit') or \ self._ac_states.get('temperatureUnit') if temperature_unit_key: @@ -186,7 +198,7 @@ class SensiboClimate(ClimateDevice): @property def current_operation(self): """Return current operation ie. heat, cool, idle.""" - return self._ac_states['mode'] + return SENSIBO_TO_HA.get(self._ac_states['mode']) @property def current_humidity(self): @@ -293,7 +305,8 @@ class SensiboClimate(ClimateDevice): """Set new target operation mode.""" with async_timeout.timeout(TIMEOUT): await self._client.async_set_ac_state_property( - self._id, 'mode', operation_mode, self._ac_states) + self._id, 'mode', HA_TO_SENSIBO[operation_mode], + self._ac_states) async def async_set_swing_mode(self, swing_mode): """Set new target swing operation.""" From 1f54edfbc4dc630b88087b4eef0899c88b76d8da Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 17 Jan 2019 10:30:47 -0800 Subject: [PATCH 04/37] Distribute reconnect (#20181) --- homeassistant/components/cloud/iot.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/cloud/iot.py b/homeassistant/components/cloud/iot.py index 7d633a4b2ac..8638a4233ce 100644 --- a/homeassistant/components/cloud/iot.py +++ b/homeassistant/components/cloud/iot.py @@ -2,6 +2,7 @@ import asyncio import logging import pprint +import random import uuid from aiohttp import hdrs, client_exceptions, WSMsgType @@ -107,9 +108,11 @@ class CloudIoT: self.tries += 1 try: - # Sleep 2^tries seconds between retries - self.retry_task = hass.async_create_task(asyncio.sleep( - 2**min(9, self.tries), loop=hass.loop)) + # Sleep 2^tries + 0…tries*3 seconds between retries + self.retry_task = hass.async_create_task( + asyncio.sleep(2**min(9, self.tries) + + random.randint(0, self.tries * 3), + loop=hass.loop)) yield from self.retry_task self.retry_task = None except asyncio.CancelledError: From 84d6453a9774587f2e1e6956d6f33be89e30fb75 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 17 Jan 2019 10:33:01 -0800 Subject: [PATCH 05/37] Add command to refresh auth (#20183) --- homeassistant/components/cloud/iot.py | 11 ++++++++--- tests/components/cloud/test_iot.py | 28 +++++++++++++++++++++++---- 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/cloud/iot.py b/homeassistant/components/cloud/iot.py index 8638a4233ce..ed24fe48d40 100644 --- a/homeassistant/components/cloud/iot.py +++ b/homeassistant/components/cloud/iot.py @@ -316,15 +316,20 @@ def async_handle_google_actions(hass, cloud, payload): @HANDLERS.register('cloud') -@asyncio.coroutine -def async_handle_cloud(hass, cloud, payload): +async def async_handle_cloud(hass, cloud, payload): """Handle an incoming IoT message for cloud component.""" action = payload['action'] if action == 'logout': - yield from cloud.logout() + # Log out of Home Assistant Cloud + await cloud.logout() _LOGGER.error("You have been logged out from Home Assistant cloud: %s", payload['reason']) + elif action == 'refresh_auth': + # Refresh the auth token between now and payload['seconds'] + hass.helpers.event.async_call_later( + random.randint(0, payload['seconds']), + lambda now: auth_api.check_token(cloud)) else: _LOGGER.warning("Received unknown cloud action: %s", action) diff --git a/tests/components/cloud/test_iot.py b/tests/components/cloud/test_iot.py index 2133a803aef..1a528f8cedf 100644 --- a/tests/components/cloud/test_iot.py +++ b/tests/components/cloud/test_iot.py @@ -10,8 +10,9 @@ from homeassistant.components.cloud import ( Cloud, iot, auth_api, MODE_DEV) from homeassistant.components.cloud.const import ( PREF_ENABLE_ALEXA, PREF_ENABLE_GOOGLE) +from homeassistant.util import dt as dt_util from tests.components.alexa import test_smart_home as test_alexa -from tests.common import mock_coro +from tests.common import mock_coro, async_fire_time_changed from . import mock_cloud_prefs @@ -147,17 +148,36 @@ def test_handler_forwarding(): assert payload == 'payload' -@asyncio.coroutine -def test_handling_core_messages(hass, mock_cloud): +async def test_handling_core_messages_logout(hass, mock_cloud): """Test handling core messages.""" mock_cloud.logout.return_value = mock_coro() - yield from iot.async_handle_cloud(hass, mock_cloud, { + await iot.async_handle_cloud(hass, mock_cloud, { 'action': 'logout', 'reason': 'Logged in at two places.' }) assert len(mock_cloud.logout.mock_calls) == 1 +async def test_handling_core_messages_refresh_auth(hass, mock_cloud): + """Test handling core messages.""" + mock_cloud.hass = hass + with patch('random.randint', return_value=0) as mock_rand, patch( + 'homeassistant.components.cloud.auth_api.check_token' + ) as mock_check: + await iot.async_handle_cloud(hass, mock_cloud, { + 'action': 'refresh_auth', + 'seconds': 230, + }) + async_fire_time_changed(hass, dt_util.utcnow()) + await hass.async_block_till_done() + + assert len(mock_rand.mock_calls) == 1 + assert mock_rand.mock_calls[0][1] == (0, 230) + + assert len(mock_check.mock_calls) == 1 + assert mock_check.mock_calls[0][1][0] is mock_cloud + + @asyncio.coroutine def test_cloud_getting_disconnected_by_server(mock_client, caplog, mock_cloud): """Test server disconnecting instance.""" From d843bf9c58979cf906da37aaf07a76e05fa8665d Mon Sep 17 00:00:00 2001 From: Anders Melchiorsen Date: Fri, 18 Jan 2019 13:43:48 +0100 Subject: [PATCH 06/37] Improve Sonos discovery (#20196) --- homeassistant/components/sonos/__init__.py | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/sonos/__init__.py b/homeassistant/components/sonos/__init__.py index 529df41de58..b4f507a60dd 100644 --- a/homeassistant/components/sonos/__init__.py +++ b/homeassistant/components/sonos/__init__.py @@ -4,7 +4,7 @@ from homeassistant.helpers import config_entry_flow DOMAIN = 'sonos' -REQUIREMENTS = ['pysonos==0.0.5'] +REQUIREMENTS = ['pysonos==0.0.6'] async def async_setup(hass, config): diff --git a/requirements_all.txt b/requirements_all.txt index dc14bdf84c5..31202d3a6ad 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1208,7 +1208,7 @@ pysma==0.3.1 pysnmp==4.4.8 # homeassistant.components.sonos -pysonos==0.0.5 +pysonos==0.0.6 # homeassistant.components.spc pyspcwebgw==0.4.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 934daa56d98..5733d7e9630 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -202,7 +202,7 @@ pyotp==2.2.6 pyqwikswitch==0.8 # homeassistant.components.sonos -pysonos==0.0.5 +pysonos==0.0.6 # homeassistant.components.spc pyspcwebgw==0.4.0 From ba2b28cd4dcfc9d6c816ff54af11baba05cc74d9 Mon Sep 17 00:00:00 2001 From: Phil Bruckner Date: Sun, 20 Jan 2019 18:46:14 -0600 Subject: [PATCH 07/37] Handle non-string values in JSON renderer (#20233) Handle the case of async_render_with_possible_json_value's value argument being something other than a string. This can happen, e.g., when using the SQL sensor to extract a datetime column such as last_changed and also using its value_template to convert that datetime to another format. This was causing a TypeError from json.loads, but async_render_with_possible_json_value was only catching ValueError's. --- homeassistant/helpers/template.py | 2 +- tests/helpers/test_template.py | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/homeassistant/helpers/template.py b/homeassistant/helpers/template.py index e82302dfd3b..d8bbcbc6e12 100644 --- a/homeassistant/helpers/template.py +++ b/homeassistant/helpers/template.py @@ -165,7 +165,7 @@ class Template: try: variables['value_json'] = json.loads(value) - except ValueError: + except (ValueError, TypeError): pass try: diff --git a/tests/helpers/test_template.py b/tests/helpers/test_template.py index 02331c400d3..3febd4037ad 100644 --- a/tests/helpers/test_template.py +++ b/tests/helpers/test_template.py @@ -4,6 +4,7 @@ from datetime import datetime import unittest import random import math +import pytz from unittest.mock import patch from homeassistant.components import group @@ -422,6 +423,16 @@ class TestHelpersTemplate(unittest.TestCase): assert '' == \ tpl.render_with_possible_json_value('{"hello": "world"}', '') + def test_render_with_possible_json_value_non_string_value(self): + """Render with possible JSON value with non-string value.""" + tpl = template.Template(""" +{{ strptime(value~'+0000', '%Y-%m-%d %H:%M:%S%z') }} + """, self.hass) + value = datetime(2019, 1, 18, 12, 13, 14) + expected = str(pytz.utc.localize(value)) + assert expected == \ + tpl.render_with_possible_json_value(value) + def test_raise_exception_on_error(self): """Test raising an exception on error.""" with pytest.raises(TemplateError): From 33ed113211f5cd35abe272057dbf9ba735d80239 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Sat, 19 Jan 2019 17:13:32 +0100 Subject: [PATCH 08/37] Bump aioesphomeapi to 1.4.2 (#20247) * Bump aioesphomeapi to 1.4.2 * Update requirements_all.txt --- homeassistant/components/esphome/__init__.py | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/esphome/__init__.py b/homeassistant/components/esphome/__init__.py index 7578f2e244f..1ff2c10c828 100644 --- a/homeassistant/components/esphome/__init__.py +++ b/homeassistant/components/esphome/__init__.py @@ -31,7 +31,7 @@ if TYPE_CHECKING: ServiceCall DOMAIN = 'esphome' -REQUIREMENTS = ['aioesphomeapi==1.4.1'] +REQUIREMENTS = ['aioesphomeapi==1.4.2'] DISPATCHER_UPDATE_ENTITY = 'esphome_{entry_id}_update_{component_key}_{key}' diff --git a/requirements_all.txt b/requirements_all.txt index 31202d3a6ad..cbeb6732c02 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -96,7 +96,7 @@ aioautomatic==0.6.5 aiodns==1.1.1 # homeassistant.components.esphome -aioesphomeapi==1.4.1 +aioesphomeapi==1.4.2 # homeassistant.components.freebox aiofreepybox==0.0.6 From 97e8e20bcc46857af84541f38098674898050e29 Mon Sep 17 00:00:00 2001 From: Anders Melchiorsen Date: Mon, 21 Jan 2019 00:10:12 +0100 Subject: [PATCH 09/37] Remove double logging of automation action (#20264) --- homeassistant/components/automation/__init__.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index 6c9b04f9fa2..836901cde30 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -375,8 +375,6 @@ def _async_get_action(hass, config, name): async def action(entity_id, variables, context): """Execute an action.""" _LOGGER.info('Executing %s', name) - hass.components.logbook.async_log_entry( - name, 'has been triggered', DOMAIN, entity_id) try: await script_obj.async_run(variables, context) From f02e887fccd6b870df64b1b7ef0e6413ec1fbd28 Mon Sep 17 00:00:00 2001 From: Anders Melchiorsen Date: Mon, 21 Jan 2019 01:33:39 +0100 Subject: [PATCH 10/37] Allow 'all' entity_id in service schema (#20278) --- homeassistant/helpers/config_validation.py | 2 +- tests/helpers/test_config_validation.py | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py index 245cc5d46bd..92fe935085a 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -517,7 +517,7 @@ SERVICE_SCHEMA = vol.All(vol.Schema({ vol.Exclusive('service_template', 'service name'): template, vol.Optional('data'): dict, vol.Optional('data_template'): {match_all: template_complex}, - vol.Optional(CONF_ENTITY_ID): entity_ids, + vol.Optional(CONF_ENTITY_ID): comp_entity_ids, }), has_at_least_one_key('service', 'service_template')) NUMERIC_STATE_CONDITION_SCHEMA = vol.All(vol.Schema({ diff --git a/tests/helpers/test_config_validation.py b/tests/helpers/test_config_validation.py index 791570981e2..03dd3cfe55a 100644 --- a/tests/helpers/test_config_validation.py +++ b/tests/helpers/test_config_validation.py @@ -332,6 +332,10 @@ def test_service_schema(): 'service': 'homeassistant.turn_on', 'entity_id': 'light.kitchen', }, + { + 'service': 'light.turn_on', + 'entity_id': 'all', + }, { 'service': 'homeassistant.turn_on', 'entity_id': ['light.kitchen', 'light.ceiling'], From 41d232175612f1f20b6c811ff15fa5e7d0ea9c3e Mon Sep 17 00:00:00 2001 From: Anders Melchiorsen Date: Mon, 21 Jan 2019 01:33:11 +0100 Subject: [PATCH 11/37] Fix 'all' entity_id in service call extraction (#20281) --- homeassistant/helpers/entity_component.py | 5 +++-- tests/helpers/test_entity_component.py | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/homeassistant/helpers/entity_component.py b/homeassistant/helpers/entity_component.py index ce876991097..21634121cd2 100644 --- a/homeassistant/helpers/entity_component.py +++ b/homeassistant/helpers/entity_component.py @@ -7,7 +7,8 @@ import logging from homeassistant import config as conf_util from homeassistant.setup import async_prepare_setup_platform from homeassistant.const import ( - ATTR_ENTITY_ID, CONF_SCAN_INTERVAL, CONF_ENTITY_NAMESPACE, MATCH_ALL) + ATTR_ENTITY_ID, CONF_SCAN_INTERVAL, CONF_ENTITY_NAMESPACE, + ENTITY_MATCH_ALL) from homeassistant.core import callback from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import config_per_platform, discovery @@ -163,7 +164,7 @@ class EntityComponent: """ data_ent_id = service.data.get(ATTR_ENTITY_ID) - if data_ent_id in (None, MATCH_ALL): + if data_ent_id in (None, ENTITY_MATCH_ALL): if data_ent_id is None: self.logger.warning( 'Not passing an entity ID to a service to target all ' diff --git a/tests/helpers/test_entity_component.py b/tests/helpers/test_entity_component.py index 8f54f0ee5bc..27e33a4fe7d 100644 --- a/tests/helpers/test_entity_component.py +++ b/tests/helpers/test_entity_component.py @@ -479,7 +479,7 @@ async def test_extract_all_use_match_all(hass, caplog): MockEntity(name='test_2'), ]) - call = ha.ServiceCall('test', 'service', {'entity_id': '*'}) + call = ha.ServiceCall('test', 'service', {'entity_id': 'all'}) assert ['test_domain.test_1', 'test_domain.test_2'] == \ sorted(ent.entity_id for ent in From 143eb4e8f42e44379c9d8623e4add0f43b4f010d Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 20 Jan 2019 17:02:44 -0800 Subject: [PATCH 12/37] Bumped version to 0.86.0b1 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index bb70e635ce3..6cd6bb21940 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -2,7 +2,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 86 -PATCH_VERSION = '0b0' +PATCH_VERSION = '0b1' __short_version__ = '{}.{}'.format(MAJOR_VERSION, MINOR_VERSION) __version__ = '{}.{}'.format(__short_version__, PATCH_VERSION) REQUIRED_PYTHON_VER = (3, 5, 3) From 657544a381ac2e9d40bd439514249b6a1c75d4cc Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 20 Jan 2019 17:31:09 -0800 Subject: [PATCH 13/37] Clean up build artifacts correctly --- script/release | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script/release b/script/release index cf4f808377e..4dc94eb7f15 100755 --- a/script/release +++ b/script/release @@ -27,6 +27,6 @@ then exit 1 fi -rm -rf dist +rm -rf dist build python3 setup.py sdist bdist_wheel python3 -m twine upload dist/* --skip-existing From df47a8c58cadba1787142a5d9959e9a27856eb3e Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 21 Jan 2019 09:21:11 -0800 Subject: [PATCH 14/37] Updated frontend to 20190121.0 --- homeassistant/components/frontend/__init__.py | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/frontend/__init__.py b/homeassistant/components/frontend/__init__.py index 4d18b2fa841..d7b77fa903f 100644 --- a/homeassistant/components/frontend/__init__.py +++ b/homeassistant/components/frontend/__init__.py @@ -24,7 +24,7 @@ from homeassistant.core import callback from homeassistant.helpers.translation import async_get_translations from homeassistant.loader import bind_hass -REQUIREMENTS = ['home-assistant-frontend==20190120.0'] +REQUIREMENTS = ['home-assistant-frontend==20190121.0'] DOMAIN = 'frontend' DEPENDENCIES = ['api', 'websocket_api', 'http', 'system_log', diff --git a/requirements_all.txt b/requirements_all.txt index cbeb6732c02..4d3fc03f89d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -514,7 +514,7 @@ hole==0.3.0 holidays==0.9.9 # homeassistant.components.frontend -home-assistant-frontend==20190120.0 +home-assistant-frontend==20190121.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 5733d7e9630..8b24a7b2466 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -107,7 +107,7 @@ hdate==0.8.7 holidays==0.9.9 # homeassistant.components.frontend -home-assistant-frontend==20190120.0 +home-assistant-frontend==20190121.0 # homeassistant.components.homematicip_cloud homematicip==0.10.3 From 028cc8d24f327f0f6c56c71589df24bfd58c3557 Mon Sep 17 00:00:00 2001 From: Johann Kellerman Date: Mon, 21 Jan 2019 19:45:11 +0200 Subject: [PATCH 15/37] Align valid_entity_id with new slugify (#20231) * slug * ensure a dot * fix * schema_with_slug_keys * lint * test --- homeassistant/components/script.py | 2 +- homeassistant/config.py | 9 ++++----- homeassistant/core.py | 14 +++++++------- homeassistant/helpers/config_validation.py | 20 ++++++++++++++++++-- tests/components/switch/test_wake_on_lan.py | 4 ++-- 5 files changed, 32 insertions(+), 17 deletions(-) diff --git a/homeassistant/components/script.py b/homeassistant/components/script.py index 54490af3cfa..15df6907468 100644 --- a/homeassistant/components/script.py +++ b/homeassistant/components/script.py @@ -45,7 +45,7 @@ SCRIPT_ENTRY_SCHEMA = vol.Schema({ }) CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({cv.slug: SCRIPT_ENTRY_SCHEMA}) + DOMAIN: cv.schema_with_slug_keys(SCRIPT_ENTRY_SCHEMA) }, extra=vol.ALLOW_EXTRA) SCRIPT_SERVICE_SCHEMA = vol.Schema(dict) diff --git a/homeassistant/config.py b/homeassistant/config.py index 10d3ce21a00..0edadf6a78d 100644 --- a/homeassistant/config.py +++ b/homeassistant/config.py @@ -170,10 +170,9 @@ def _no_duplicate_auth_mfa_module(configs: Sequence[Dict[str, Any]]) \ return configs -PACKAGES_CONFIG_SCHEMA = vol.Schema({ - cv.slug: vol.Schema( # Package names are slugs - {cv.string: vol.Any(dict, list, None)}) # Component configuration -}) +PACKAGES_CONFIG_SCHEMA = cv.schema_with_slug_keys( # Package names are slugs + vol.Schema({cv.string: vol.Any(dict, list, None)}) # Component config +) CUSTOMIZE_DICT_SCHEMA = vol.Schema({ vol.Optional(ATTR_FRIENDLY_NAME): cv.string, @@ -627,7 +626,7 @@ def _identify_config_schema(module: ModuleType) -> \ except (AttributeError, KeyError): return None, None t_schema = str(schema) - if t_schema.startswith('{'): + if t_schema.startswith('{') or 'schema_with_slug_keys' in t_schema: return ('dict', schema) if t_schema.startswith(('[', 'All(.) -ENTITY_ID_PATTERN = re.compile(r"^(\w+)\.(\w+)$") - # How long to wait till things that run on startup have to finish. TIMEOUT_EVENT_START = 15 @@ -77,8 +73,12 @@ def split_entity_id(entity_id: str) -> List[str]: def valid_entity_id(entity_id: str) -> bool: - """Test if an entity ID is a valid format.""" - return ENTITY_ID_PATTERN.match(entity_id) is not None + """Test if an entity ID is a valid format. + + Format: . where both are slugs. + """ + return ('.' in entity_id and + slugify(entity_id) == entity_id.replace('.', '_', 1)) def valid_state(state: str) -> bool: diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py index 92fe935085a..ef0166bc16d 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -319,7 +319,23 @@ def service(value): .format(value)) -def slug(value): +def schema_with_slug_keys(value_schema: Union[T, Callable]) -> Callable: + """Ensure dicts have slugs as keys. + + Replacement of vol.Schema({cv.slug: value_schema}) to prevent misleading + "Extra keys" errors from voluptuous. + """ + schema = vol.Schema({str: value_schema}) + + def verify(value: Dict) -> Dict: + """Validate all keys are slugs and then the value_schema.""" + for key in value.keys(): + slug(key) + return schema(value) + return verify + + +def slug(value: Any) -> str: """Validate value is a valid slug.""" if value is None: raise vol.Invalid('Slug should not be None') @@ -330,7 +346,7 @@ def slug(value): raise vol.Invalid('invalid slug {} (try {})'.format(value, slg)) -def slugify(value): +def slugify(value: Any) -> str: """Coerce a value to a slug.""" if value is None: raise vol.Invalid('Slug should not be None') diff --git a/tests/components/switch/test_wake_on_lan.py b/tests/components/switch/test_wake_on_lan.py index c3f4e04057d..312a49b5183 100644 --- a/tests/components/switch/test_wake_on_lan.py +++ b/tests/components/switch/test_wake_on_lan.py @@ -139,11 +139,11 @@ class TestWOLSwitch(unittest.TestCase): 'mac_address': '00-01-02-03-04-05', 'host': 'validhostname', 'turn_off': { - 'service': 'shell_command.turn_off_TARGET', + 'service': 'shell_command.turn_off_target', }, } }) - calls = mock_service(self.hass, 'shell_command', 'turn_off_TARGET') + calls = mock_service(self.hass, 'shell_command', 'turn_off_target') state = self.hass.states.get('switch.wake_on_lan') assert STATE_OFF == state.state From 9667c8057f496b63ceb0de8ad35be55ded99329b Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 21 Jan 2019 09:45:42 -0800 Subject: [PATCH 16/37] Bumped version to 0.86.0b2 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 6cd6bb21940..36d66939e78 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -2,7 +2,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 86 -PATCH_VERSION = '0b1' +PATCH_VERSION = '0b2' __short_version__ = '{}.{}'.format(MAJOR_VERSION, MINOR_VERSION) __version__ = '{}.{}'.format(__short_version__, PATCH_VERSION) REQUIRED_PYTHON_VER = (3, 5, 3) From 5945929e7e4555fbb51742e518cc1c049f0b9264 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 21 Jan 2019 21:20:08 -0800 Subject: [PATCH 17/37] Updated frontend to 20190121.1 --- homeassistant/components/frontend/__init__.py | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/frontend/__init__.py b/homeassistant/components/frontend/__init__.py index d7b77fa903f..f5cc33b63a0 100644 --- a/homeassistant/components/frontend/__init__.py +++ b/homeassistant/components/frontend/__init__.py @@ -24,7 +24,7 @@ from homeassistant.core import callback from homeassistant.helpers.translation import async_get_translations from homeassistant.loader import bind_hass -REQUIREMENTS = ['home-assistant-frontend==20190121.0'] +REQUIREMENTS = ['home-assistant-frontend==20190121.1'] DOMAIN = 'frontend' DEPENDENCIES = ['api', 'websocket_api', 'http', 'system_log', diff --git a/requirements_all.txt b/requirements_all.txt index 4d3fc03f89d..7a3cca7a7ee 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -514,7 +514,7 @@ hole==0.3.0 holidays==0.9.9 # homeassistant.components.frontend -home-assistant-frontend==20190121.0 +home-assistant-frontend==20190121.1 # homeassistant.components.zwave homeassistant-pyozw==0.1.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 8b24a7b2466..edcd1d101aa 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -107,7 +107,7 @@ hdate==0.8.7 holidays==0.9.9 # homeassistant.components.frontend -home-assistant-frontend==20190121.0 +home-assistant-frontend==20190121.1 # homeassistant.components.homematicip_cloud homematicip==0.10.3 From ec7f2657cda8035a23aeac3644a7030735056033 Mon Sep 17 00:00:00 2001 From: Johann Kellerman Date: Tue, 22 Jan 2019 02:36:04 +0200 Subject: [PATCH 18/37] Config Validator: schema_with_slug_keys (#20298) * schema_with_slug_keys * Update config_validation.py * Update config_validation.py --- homeassistant/components/alert.py | 4 +--- homeassistant/components/axis/__init__.py | 4 +--- homeassistant/components/binary_sensor/template.py | 2 +- homeassistant/components/binary_sensor/trend.py | 2 +- homeassistant/components/counter/__init__.py | 6 +++--- homeassistant/components/cover/command_line.py | 2 +- homeassistant/components/cover/garadget.py | 2 +- homeassistant/components/cover/opengarage.py | 2 +- homeassistant/components/cover/scsgate.py | 3 ++- homeassistant/components/cover/template.py | 2 +- homeassistant/components/cover/velbus.py | 2 +- homeassistant/components/fan/template.py | 2 +- homeassistant/components/history_graph.py | 2 +- homeassistant/components/input_boolean.py | 6 +++--- homeassistant/components/input_datetime.py | 7 ++++--- homeassistant/components/input_number.py | 6 +++--- homeassistant/components/input_select.py | 7 ++++--- homeassistant/components/input_text.py | 6 +++--- homeassistant/components/light/scsgate.py | 3 ++- homeassistant/components/light/template.py | 2 +- homeassistant/components/media_player/universal.py | 4 ++-- homeassistant/components/panel_iframe.py | 8 +++++--- homeassistant/components/proximity.py | 4 +--- homeassistant/components/remote/xiaomi_miio.py | 2 +- homeassistant/components/rest_command.py | 4 +--- homeassistant/components/sensor/lacrosse.py | 2 +- homeassistant/components/sensor/sma.py | 4 ++-- homeassistant/components/sensor/template.py | 2 +- homeassistant/components/shell_command.py | 4 +--- homeassistant/components/switch/broadlink.py | 2 +- homeassistant/components/switch/command_line.py | 2 +- homeassistant/components/switch/kankun.py | 2 +- homeassistant/components/switch/scsgate.py | 3 ++- homeassistant/components/switch/telnet.py | 2 +- homeassistant/components/switch/template.py | 2 +- homeassistant/components/timer/__init__.py | 6 +++--- homeassistant/components/volvooncall.py | 4 ++-- homeassistant/helpers/config_validation.py | 3 +++ 38 files changed, 66 insertions(+), 66 deletions(-) diff --git a/homeassistant/components/alert.py b/homeassistant/components/alert.py index 759a2185047..3a18281e49b 100644 --- a/homeassistant/components/alert.py +++ b/homeassistant/components/alert.py @@ -46,9 +46,7 @@ ALERT_SCHEMA = vol.Schema({ vol.Required(CONF_NOTIFIERS): cv.ensure_list}) CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - cv.slug: ALERT_SCHEMA, - }), + DOMAIN: cv.schema_with_slug_keys(ALERT_SCHEMA), }, extra=vol.ALLOW_EXTRA) diff --git a/homeassistant/components/axis/__init__.py b/homeassistant/components/axis/__init__.py index 26fe41724f9..fd2e603445c 100644 --- a/homeassistant/components/axis/__init__.py +++ b/homeassistant/components/axis/__init__.py @@ -50,9 +50,7 @@ DEVICE_SCHEMA = vol.Schema({ }) CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - cv.slug: DEVICE_SCHEMA, - }), + DOMAIN: cv.schema_with_slug_keys(DEVICE_SCHEMA), }, extra=vol.ALLOW_EXTRA) SERVICE_VAPIX_CALL = 'vapix_call' diff --git a/homeassistant/components/binary_sensor/template.py b/homeassistant/components/binary_sensor/template.py index d5f8b16e0c1..605ab24a264 100644 --- a/homeassistant/components/binary_sensor/template.py +++ b/homeassistant/components/binary_sensor/template.py @@ -41,7 +41,7 @@ SENSOR_SCHEMA = vol.Schema({ }) PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_SENSORS): vol.Schema({cv.slug: SENSOR_SCHEMA}), + vol.Required(CONF_SENSORS): cv.schema_with_slug_keys(SENSOR_SCHEMA), }) diff --git a/homeassistant/components/binary_sensor/trend.py b/homeassistant/components/binary_sensor/trend.py index 4773e88f5df..8f3ff5d798e 100644 --- a/homeassistant/components/binary_sensor/trend.py +++ b/homeassistant/components/binary_sensor/trend.py @@ -51,7 +51,7 @@ SENSOR_SCHEMA = vol.Schema({ }) PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_SENSORS): vol.Schema({cv.slug: SENSOR_SCHEMA}), + vol.Required(CONF_SENSORS): cv.schema_with_slug_keys(SENSOR_SCHEMA), }) diff --git a/homeassistant/components/counter/__init__.py b/homeassistant/components/counter/__init__.py index cd3a29df2b6..aeef2818f63 100644 --- a/homeassistant/components/counter/__init__.py +++ b/homeassistant/components/counter/__init__.py @@ -37,8 +37,8 @@ SERVICE_SCHEMA = vol.Schema({ }) CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - cv.slug: vol.Any({ + DOMAIN: cv.schema_with_slug_keys( + vol.Any({ vol.Optional(CONF_ICON): cv.icon, vol.Optional(CONF_INITIAL, default=DEFAULT_INITIAL): cv.positive_int, @@ -46,7 +46,7 @@ CONFIG_SCHEMA = vol.Schema({ vol.Optional(CONF_RESTORE, default=True): cv.boolean, vol.Optional(CONF_STEP, default=DEFAULT_STEP): cv.positive_int, }, None) - }) + ) }, extra=vol.ALLOW_EXTRA) diff --git a/homeassistant/components/cover/command_line.py b/homeassistant/components/cover/command_line.py index bebf78b1db6..4f4fca1b27a 100644 --- a/homeassistant/components/cover/command_line.py +++ b/homeassistant/components/cover/command_line.py @@ -27,7 +27,7 @@ COVER_SCHEMA = vol.Schema({ }) PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_COVERS): vol.Schema({cv.slug: COVER_SCHEMA}), + vol.Required(CONF_COVERS): cv.schema_with_slug_keys(COVER_SCHEMA), }) diff --git a/homeassistant/components/cover/garadget.py b/homeassistant/components/cover/garadget.py index 03756a971bc..28be3dc6b82 100644 --- a/homeassistant/components/cover/garadget.py +++ b/homeassistant/components/cover/garadget.py @@ -47,7 +47,7 @@ COVER_SCHEMA = vol.Schema({ }) PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_COVERS): vol.Schema({cv.slug: COVER_SCHEMA}), + vol.Required(CONF_COVERS): cv.schema_with_slug_keys(COVER_SCHEMA), }) diff --git a/homeassistant/components/cover/opengarage.py b/homeassistant/components/cover/opengarage.py index 19a87c5bf7c..664d2e291ac 100644 --- a/homeassistant/components/cover/opengarage.py +++ b/homeassistant/components/cover/opengarage.py @@ -46,7 +46,7 @@ COVER_SCHEMA = vol.Schema({ }) PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_COVERS): vol.Schema({cv.slug: COVER_SCHEMA}), + vol.Required(CONF_COVERS): cv.schema_with_slug_keys(COVER_SCHEMA), }) diff --git a/homeassistant/components/cover/scsgate.py b/homeassistant/components/cover/scsgate.py index a6f09c7237d..2d85c1fe3c3 100644 --- a/homeassistant/components/cover/scsgate.py +++ b/homeassistant/components/cover/scsgate.py @@ -18,7 +18,8 @@ _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['scsgate'] PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_DEVICES): vol.Schema({cv.slug: scsgate.SCSGATE_SCHEMA}), + vol.Required(CONF_DEVICES): + cv.schema_with_slug_keys(scsgate.SCSGATE_SCHEMA), }) diff --git a/homeassistant/components/cover/template.py b/homeassistant/components/cover/template.py index f64e4ae7a3f..1d3642a6036 100644 --- a/homeassistant/components/cover/template.py +++ b/homeassistant/components/cover/template.py @@ -67,7 +67,7 @@ COVER_SCHEMA = vol.Schema({ }) PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_COVERS): vol.Schema({cv.slug: COVER_SCHEMA}), + vol.Required(CONF_COVERS): cv.schema_with_slug_keys(COVER_SCHEMA), }) diff --git a/homeassistant/components/cover/velbus.py b/homeassistant/components/cover/velbus.py index a8501778884..7e5099cecf8 100644 --- a/homeassistant/components/cover/velbus.py +++ b/homeassistant/components/cover/velbus.py @@ -26,7 +26,7 @@ COVER_SCHEMA = vol.Schema({ }) PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_COVERS): vol.Schema({cv.slug: COVER_SCHEMA}), + vol.Required(CONF_COVERS): cv.schema_with_slug_keys(COVER_SCHEMA), }) DEPENDENCIES = ['velbus'] diff --git a/homeassistant/components/fan/template.py b/homeassistant/components/fan/template.py index a2f33d40e48..d9182b79a40 100644 --- a/homeassistant/components/fan/template.py +++ b/homeassistant/components/fan/template.py @@ -62,7 +62,7 @@ FAN_SCHEMA = vol.Schema({ }) PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA.extend({ - vol.Required(CONF_FANS): vol.Schema({cv.slug: FAN_SCHEMA}), + vol.Required(CONF_FANS): cv.schema_with_slug_keys(FAN_SCHEMA), }) diff --git a/homeassistant/components/history_graph.py b/homeassistant/components/history_graph.py index fa7d615dce2..7d9db379705 100644 --- a/homeassistant/components/history_graph.py +++ b/homeassistant/components/history_graph.py @@ -34,7 +34,7 @@ GRAPH_SCHEMA = vol.Schema({ CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({cv.slug: GRAPH_SCHEMA}) + DOMAIN: cv.schema_with_slug_keys(GRAPH_SCHEMA) }, extra=vol.ALLOW_EXTRA) diff --git a/homeassistant/components/input_boolean.py b/homeassistant/components/input_boolean.py index 541e38202fc..896de61130c 100644 --- a/homeassistant/components/input_boolean.py +++ b/homeassistant/components/input_boolean.py @@ -30,13 +30,13 @@ SERVICE_SCHEMA = vol.Schema({ }) CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - cv.slug: vol.Any({ + DOMAIN: cv.schema_with_slug_keys( + vol.Any({ vol.Optional(CONF_NAME): cv.string, vol.Optional(CONF_INITIAL): cv.boolean, vol.Optional(CONF_ICON): cv.icon, }, None) - }) + ) }, extra=vol.ALLOW_EXTRA) diff --git a/homeassistant/components/input_datetime.py b/homeassistant/components/input_datetime.py index 6ac9a24d044..63dcc364c9c 100644 --- a/homeassistant/components/input_datetime.py +++ b/homeassistant/components/input_datetime.py @@ -46,14 +46,15 @@ def has_date_or_time(conf): CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - cv.slug: vol.All({ + DOMAIN: cv.schema_with_slug_keys( + vol.All({ vol.Optional(CONF_NAME): cv.string, vol.Optional(CONF_HAS_DATE, default=False): cv.boolean, vol.Optional(CONF_HAS_TIME, default=False): cv.boolean, vol.Optional(CONF_ICON): cv.icon, vol.Optional(CONF_INITIAL): cv.string, - }, has_date_or_time)}) + }, has_date_or_time) + ) }, extra=vol.ALLOW_EXTRA) diff --git a/homeassistant/components/input_number.py b/homeassistant/components/input_number.py index b6c6eab3cf5..8cfa7abaf20 100644 --- a/homeassistant/components/input_number.py +++ b/homeassistant/components/input_number.py @@ -63,8 +63,8 @@ def _cv_input_number(cfg): CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - cv.slug: vol.All({ + DOMAIN: cv.schema_with_slug_keys( + vol.All({ vol.Optional(CONF_NAME): cv.string, vol.Required(CONF_MIN): vol.Coerce(float), vol.Required(CONF_MAX): vol.Coerce(float), @@ -76,7 +76,7 @@ CONFIG_SCHEMA = vol.Schema({ vol.Optional(CONF_MODE, default=MODE_SLIDER): vol.In([MODE_BOX, MODE_SLIDER]), }, _cv_input_number) - }) + ) }, required=True, extra=vol.ALLOW_EXTRA) diff --git a/homeassistant/components/input_select.py b/homeassistant/components/input_select.py index cc9a73bf915..fc858e75397 100644 --- a/homeassistant/components/input_select.py +++ b/homeassistant/components/input_select.py @@ -64,14 +64,15 @@ def _cv_input_select(cfg): CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - cv.slug: vol.All({ + DOMAIN: cv.schema_with_slug_keys( + vol.All({ vol.Optional(CONF_NAME): cv.string, vol.Required(CONF_OPTIONS): vol.All(cv.ensure_list, vol.Length(min=1), [cv.string]), vol.Optional(CONF_INITIAL): cv.string, vol.Optional(CONF_ICON): cv.icon, - }, _cv_input_select)}) + }, _cv_input_select) + ) }, required=True, extra=vol.ALLOW_EXTRA) diff --git a/homeassistant/components/input_text.py b/homeassistant/components/input_text.py index 8ac64b398f4..580337a3af3 100644 --- a/homeassistant/components/input_text.py +++ b/homeassistant/components/input_text.py @@ -55,8 +55,8 @@ def _cv_input_text(cfg): CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - cv.slug: vol.All({ + DOMAIN: cv.schema_with_slug_keys( + vol.All({ vol.Optional(CONF_NAME): cv.string, vol.Optional(CONF_MIN, default=0): vol.Coerce(int), vol.Optional(CONF_MAX, default=100): vol.Coerce(int), @@ -67,7 +67,7 @@ CONFIG_SCHEMA = vol.Schema({ vol.Optional(CONF_MODE, default=MODE_TEXT): vol.In([MODE_TEXT, MODE_PASSWORD]), }, _cv_input_text) - }) + ) }, required=True, extra=vol.ALLOW_EXTRA) diff --git a/homeassistant/components/light/scsgate.py b/homeassistant/components/light/scsgate.py index 4a18bc99672..c218e194791 100644 --- a/homeassistant/components/light/scsgate.py +++ b/homeassistant/components/light/scsgate.py @@ -19,7 +19,8 @@ _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['scsgate'] PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_DEVICES): vol.Schema({cv.slug: scsgate.SCSGATE_SCHEMA}), + vol.Required(CONF_DEVICES): + cv.schema_with_slug_keys(scsgate.SCSGATE_SCHEMA), }) diff --git a/homeassistant/components/light/template.py b/homeassistant/components/light/template.py index 2447dabe3c7..bf930dd1b38 100644 --- a/homeassistant/components/light/template.py +++ b/homeassistant/components/light/template.py @@ -44,7 +44,7 @@ LIGHT_SCHEMA = vol.Schema({ }) PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_LIGHTS): vol.Schema({cv.slug: LIGHT_SCHEMA}), + vol.Required(CONF_LIGHTS): cv.schema_with_slug_keys(LIGHT_SCHEMA), }) diff --git a/homeassistant/components/media_player/universal.py b/homeassistant/components/media_player/universal.py index 47eaf599929..18b953a0372 100644 --- a/homeassistant/components/media_player/universal.py +++ b/homeassistant/components/media_player/universal.py @@ -47,8 +47,8 @@ CONF_SERVICE_DATA = 'service_data' OFF_STATES = [STATE_IDLE, STATE_OFF, STATE_UNAVAILABLE] -ATTRS_SCHEMA = vol.Schema({cv.slug: cv.string}) -CMD_SCHEMA = vol.Schema({cv.slug: cv.SERVICE_SCHEMA}) +ATTRS_SCHEMA = cv.schema_with_slug_keys(cv.string) +CMD_SCHEMA = cv.schema_with_slug_keys(cv.SERVICE_SCHEMA) PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_NAME): cv.string, diff --git a/homeassistant/components/panel_iframe.py b/homeassistant/components/panel_iframe.py index 86594b74995..030fbbf9324 100644 --- a/homeassistant/components/panel_iframe.py +++ b/homeassistant/components/panel_iframe.py @@ -19,8 +19,8 @@ CONF_RELATIVE_URL_ERROR_MSG = "Invalid relative URL. Absolute path required." CONF_RELATIVE_URL_REGEX = r'\A/' CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - cv.slug: { + DOMAIN: cv.schema_with_slug_keys( + vol.Schema({ # pylint: disable=no-value-for-parameter vol.Optional(CONF_TITLE): cv.string, vol.Optional(CONF_ICON): cv.icon, @@ -29,7 +29,9 @@ CONFIG_SCHEMA = vol.Schema({ CONF_RELATIVE_URL_REGEX, msg=CONF_RELATIVE_URL_ERROR_MSG), vol.Url()), - }})}, extra=vol.ALLOW_EXTRA) + }) + ) +}, extra=vol.ALLOW_EXTRA) async def async_setup(hass, config): diff --git a/homeassistant/components/proximity.py b/homeassistant/components/proximity.py index 38b37cad51e..e8d86d480e5 100644 --- a/homeassistant/components/proximity.py +++ b/homeassistant/components/proximity.py @@ -49,9 +49,7 @@ ZONE_SCHEMA = vol.Schema({ }) CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - cv.slug: ZONE_SCHEMA, - }), + DOMAIN: cv.schema_with_slug_keys(ZONE_SCHEMA), }, extra=vol.ALLOW_EXTRA) diff --git a/homeassistant/components/remote/xiaomi_miio.py b/homeassistant/components/remote/xiaomi_miio.py index a247cb3e914..c8ffd043321 100644 --- a/homeassistant/components/remote/xiaomi_miio.py +++ b/homeassistant/components/remote/xiaomi_miio.py @@ -57,7 +57,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Optional(ATTR_HIDDEN, default=True): cv.boolean, vol.Required(CONF_TOKEN): vol.All(str, vol.Length(min=32, max=32)), vol.Optional(CONF_COMMANDS, default={}): - vol.Schema({cv.slug: COMMAND_SCHEMA}), + cv.schema_with_slug_keys(COMMAND_SCHEMA), }, extra=vol.ALLOW_EXTRA) diff --git a/homeassistant/components/rest_command.py b/homeassistant/components/rest_command.py index 3f9b258634d..c8fb1568152 100644 --- a/homeassistant/components/rest_command.py +++ b/homeassistant/components/rest_command.py @@ -47,9 +47,7 @@ COMMAND_SCHEMA = vol.Schema({ }) CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - cv.slug: COMMAND_SCHEMA, - }), + DOMAIN: cv.schema_with_slug_keys(COMMAND_SCHEMA), }, extra=vol.ALLOW_EXTRA) diff --git a/homeassistant/components/sensor/lacrosse.py b/homeassistant/components/sensor/lacrosse.py index a2dbaa8f324..32b1dac9250 100644 --- a/homeassistant/components/sensor/lacrosse.py +++ b/homeassistant/components/sensor/lacrosse.py @@ -45,7 +45,7 @@ SENSOR_SCHEMA = vol.Schema({ }) PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_SENSORS): vol.Schema({cv.slug: SENSOR_SCHEMA}), + vol.Required(CONF_SENSORS): cv.schema_with_slug_keys(SENSOR_SCHEMA), vol.Optional(CONF_BAUD, default=DEFAULT_BAUD): cv.string, vol.Optional(CONF_DATARATE): cv.positive_int, vol.Optional(CONF_DEVICE, default=DEFAULT_DEVICE): cv.string, diff --git a/homeassistant/components/sensor/sma.py b/homeassistant/components/sensor/sma.py index 4bfa62bf6dd..61009a472fb 100644 --- a/homeassistant/components/sensor/sma.py +++ b/homeassistant/components/sensor/sma.py @@ -68,9 +68,9 @@ PLATFORM_SCHEMA = vol.All(PLATFORM_SCHEMA.extend({ vol.Required(CONF_PASSWORD): cv.string, vol.Optional(CONF_GROUP, default=GROUPS[0]): vol.In(GROUPS), vol.Optional(CONF_SENSORS, default={}): - vol.Schema({cv.slug: cv.ensure_list}), + cv.schema_with_slug_keys(cv.ensure_list), vol.Optional(CONF_CUSTOM, default={}): - vol.Schema({cv.slug: CUSTOM_SCHEMA}), + cv.schema_with_slug_keys(CUSTOM_SCHEMA), }, extra=vol.PREVENT_EXTRA), _check_sensor_schema) diff --git a/homeassistant/components/sensor/template.py b/homeassistant/components/sensor/template.py index 3fa45935617..5f3af4a06a4 100644 --- a/homeassistant/components/sensor/template.py +++ b/homeassistant/components/sensor/template.py @@ -36,7 +36,7 @@ SENSOR_SCHEMA = vol.Schema({ }) PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_SENSORS): vol.Schema({cv.slug: SENSOR_SCHEMA}), + vol.Required(CONF_SENSORS): cv.schema_with_slug_keys(SENSOR_SCHEMA), }) diff --git a/homeassistant/components/shell_command.py b/homeassistant/components/shell_command.py index 2a95dd5c144..f9ec8da54e3 100644 --- a/homeassistant/components/shell_command.py +++ b/homeassistant/components/shell_command.py @@ -21,9 +21,7 @@ DOMAIN = 'shell_command' _LOGGER = logging.getLogger(__name__) CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - cv.slug: cv.string, - }), + DOMAIN: cv.schema_with_slug_keys(cv.string), }, extra=vol.ALLOW_EXTRA) diff --git a/homeassistant/components/switch/broadlink.py b/homeassistant/components/switch/broadlink.py index 685402611a0..9c17767f033 100644 --- a/homeassistant/components/switch/broadlink.py +++ b/homeassistant/components/switch/broadlink.py @@ -59,7 +59,7 @@ MP1_SWITCH_SLOT_SCHEMA = vol.Schema({ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Optional(CONF_SWITCHES, default={}): - vol.Schema({cv.slug: SWITCH_SCHEMA}), + cv.schema_with_slug_keys(SWITCH_SCHEMA), vol.Optional(CONF_SLOTS, default={}): MP1_SWITCH_SLOT_SCHEMA, vol.Required(CONF_HOST): cv.string, vol.Required(CONF_MAC): cv.string, diff --git a/homeassistant/components/switch/command_line.py b/homeassistant/components/switch/command_line.py index d25c5708316..4edbd79ee0c 100644 --- a/homeassistant/components/switch/command_line.py +++ b/homeassistant/components/switch/command_line.py @@ -28,7 +28,7 @@ SWITCH_SCHEMA = vol.Schema({ }) PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_SWITCHES): vol.Schema({cv.slug: SWITCH_SCHEMA}), + vol.Required(CONF_SWITCHES): cv.schema_with_slug_keys(SWITCH_SCHEMA), }) diff --git a/homeassistant/components/switch/kankun.py b/homeassistant/components/switch/kankun.py index 59966739b91..86e7fcdab3e 100644 --- a/homeassistant/components/switch/kankun.py +++ b/homeassistant/components/switch/kankun.py @@ -30,7 +30,7 @@ SWITCH_SCHEMA = vol.Schema({ }) PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_SWITCHES): vol.Schema({cv.slug: SWITCH_SCHEMA}), + vol.Required(CONF_SWITCHES): cv.schema_with_slug_keys(SWITCH_SCHEMA), }) diff --git a/homeassistant/components/switch/scsgate.py b/homeassistant/components/switch/scsgate.py index bb8c067ebd9..9344aeab7ed 100644 --- a/homeassistant/components/switch/scsgate.py +++ b/homeassistant/components/switch/scsgate.py @@ -24,7 +24,8 @@ CONF_SCENARIO = 'scenario' CONF_SCS_ID = 'scs_id' PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_DEVICES): vol.Schema({cv.slug: scsgate.SCSGATE_SCHEMA}), + vol.Required(CONF_DEVICES): + cv.schema_with_slug_keys(scsgate.SCSGATE_SCHEMA), }) diff --git a/homeassistant/components/switch/telnet.py b/homeassistant/components/switch/telnet.py index 440279a70a8..7c3baf2981a 100644 --- a/homeassistant/components/switch/telnet.py +++ b/homeassistant/components/switch/telnet.py @@ -32,7 +32,7 @@ SWITCH_SCHEMA = vol.Schema({ }) PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_SWITCHES): vol.Schema({cv.slug: SWITCH_SCHEMA}), + vol.Required(CONF_SWITCHES): cv.schema_with_slug_keys(SWITCH_SCHEMA), }) SCAN_INTERVAL = timedelta(seconds=10) diff --git a/homeassistant/components/switch/template.py b/homeassistant/components/switch/template.py index 51cea68f6b3..a2098c2f5fd 100644 --- a/homeassistant/components/switch/template.py +++ b/homeassistant/components/switch/template.py @@ -38,7 +38,7 @@ SWITCH_SCHEMA = vol.Schema({ }) PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_SWITCHES): vol.Schema({cv.slug: SWITCH_SCHEMA}), + vol.Required(CONF_SWITCHES): cv.schema_with_slug_keys(SWITCH_SCHEMA), }) diff --git a/homeassistant/components/timer/__init__.py b/homeassistant/components/timer/__init__.py index 364975671c4..b898c577bb2 100644 --- a/homeassistant/components/timer/__init__.py +++ b/homeassistant/components/timer/__init__.py @@ -53,14 +53,14 @@ SERVICE_SCHEMA_DURATION = vol.Schema({ }) CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - cv.slug: vol.Any({ + DOMAIN: cv.schema_with_slug_keys( + vol.Any({ vol.Optional(CONF_NAME): cv.string, vol.Optional(CONF_ICON): cv.icon, vol.Optional(CONF_DURATION, timedelta(DEFAULT_DURATION)): cv.time_period, }, None) - }) + ) }, extra=vol.ALLOW_EXTRA) diff --git a/homeassistant/components/volvooncall.py b/homeassistant/components/volvooncall.py index 9f9b58ec8b6..ce4dccbaf75 100644 --- a/homeassistant/components/volvooncall.py +++ b/homeassistant/components/volvooncall.py @@ -93,8 +93,8 @@ CONFIG_SCHEMA = vol.Schema({ vol.Required(CONF_PASSWORD): cv.string, vol.Optional(CONF_UPDATE_INTERVAL, default=DEFAULT_UPDATE_INTERVAL): ( vol.All(cv.time_period, vol.Clamp(min=MIN_UPDATE_INTERVAL))), - vol.Optional(CONF_NAME, default={}): vol.Schema( - {cv.slug: cv.string}), + vol.Optional(CONF_NAME, default={}): + cv.schema_with_slug_keys(cv.string), vol.Optional(CONF_RESOURCES): vol.All( cv.ensure_list, [vol.In(RESOURCES)]), vol.Optional(CONF_REGION): cv.string, diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py index ef0166bc16d..475135b4cce 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -329,6 +329,9 @@ def schema_with_slug_keys(value_schema: Union[T, Callable]) -> Callable: def verify(value: Dict) -> Dict: """Validate all keys are slugs and then the value_schema.""" + if not isinstance(value, dict): + raise vol.Invalid('expected dictionary') + for key in value.keys(): slug(key) return schema(value) From 0ceace96e77cf3a88985718553ea75f8fd95910a Mon Sep 17 00:00:00 2001 From: Fabien Piuzzi Date: Tue, 22 Jan 2019 06:21:59 +0100 Subject: [PATCH 19/37] Bugfix: prevent error notification when octoprint server auto detected but no configuration present. (#20303) --- homeassistant/components/octoprint.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/homeassistant/components/octoprint.py b/homeassistant/components/octoprint.py index b626e9a93b5..853ee67db9d 100644 --- a/homeassistant/components/octoprint.py +++ b/homeassistant/components/octoprint.py @@ -92,6 +92,10 @@ def setup(hass, config): discovery.listen(hass, SERVICE_OCTOPRINT, device_discovered) + if DOMAIN not in config: + # Skip the setup if there is no configuration present + return True + for printer in config[DOMAIN]: name = printer[CONF_NAME] ssl = 's' if printer[CONF_SSL] else '' From 6d0ac30687407b8884f72722bed4d272a66cca21 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 21 Jan 2019 21:23:00 -0800 Subject: [PATCH 20/37] Bumped version to 0.86.0b3 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 36d66939e78..0191c09551b 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -2,7 +2,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 86 -PATCH_VERSION = '0b2' +PATCH_VERSION = '0b3' __short_version__ = '{}.{}'.format(MAJOR_VERSION, MINOR_VERSION) __version__ = '{}.{}'.format(__short_version__, PATCH_VERSION) REQUIRED_PYTHON_VER = (3, 5, 3) From b7218e6a1d213f41493bfdf46119609b85d46643 Mon Sep 17 00:00:00 2001 From: Richard Mitchell Date: Tue, 22 Jan 2019 17:50:21 +0000 Subject: [PATCH 21/37] Should require the 'GATTOOL' setup extras which includes pexpect. (#20263) * Should require the 'GATTOOL' setup extras which includes pexpect. * Also fix skybeacon's requirement and requirements_all. --- homeassistant/components/device_tracker/bluetooth_le_tracker.py | 2 +- homeassistant/components/sensor/skybeacon.py | 2 +- requirements_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/device_tracker/bluetooth_le_tracker.py b/homeassistant/components/device_tracker/bluetooth_le_tracker.py index a07fdfdcf81..825ef04ccc5 100644 --- a/homeassistant/components/device_tracker/bluetooth_le_tracker.py +++ b/homeassistant/components/device_tracker/bluetooth_le_tracker.py @@ -15,7 +15,7 @@ import homeassistant.util.dt as dt_util _LOGGER = logging.getLogger(__name__) -REQUIREMENTS = ['pygatt==3.2.0'] +REQUIREMENTS = ['pygatt[GATTTOOL]==3.2.0'] BLE_PREFIX = 'BLE_' MIN_SEEN_NEW = 5 diff --git a/homeassistant/components/sensor/skybeacon.py b/homeassistant/components/sensor/skybeacon.py index 441053a7e7e..6960999306d 100644 --- a/homeassistant/components/sensor/skybeacon.py +++ b/homeassistant/components/sensor/skybeacon.py @@ -16,7 +16,7 @@ from homeassistant.const import ( import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity -REQUIREMENTS = ['pygatt==3.2.0'] +REQUIREMENTS = ['pygatt[GATTTOOL]==3.2.0'] _LOGGER = logging.getLogger(__name__) diff --git a/requirements_all.txt b/requirements_all.txt index 7a3cca7a7ee..20b773d0a59 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1003,7 +1003,7 @@ pyfttt==0.3 # homeassistant.components.device_tracker.bluetooth_le_tracker # homeassistant.components.sensor.skybeacon -pygatt==3.2.0 +pygatt[GATTTOOL]==3.2.0 # homeassistant.components.cover.gogogate2 pygogogate2==0.1.1 From 4b7d944a74cb206fd138a068ddab2c035be09407 Mon Sep 17 00:00:00 2001 From: Sebastian Muszynski Date: Wed, 23 Jan 2019 05:04:13 +0100 Subject: [PATCH 22/37] Fix xiaomi speed attribute name clash (#20312) --- homeassistant/components/fan/xiaomi_miio.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/fan/xiaomi_miio.py b/homeassistant/components/fan/xiaomi_miio.py index d3b52622a95..e0d51279bbf 100644 --- a/homeassistant/components/fan/xiaomi_miio.py +++ b/homeassistant/components/fan/xiaomi_miio.py @@ -111,7 +111,7 @@ ATTR_TRANS_LEVEL = 'trans_level' ATTR_HARDWARE_VERSION = 'hardware_version' # Air Humidifier CA -ATTR_SPEED = 'speed' +ATTR_MOTOR_SPEED = 'motor_speed' ATTR_DEPTH = 'depth' ATTR_DRY = 'dry' @@ -223,7 +223,7 @@ AVAILABLE_ATTRIBUTES_AIRHUMIDIFIER = { AVAILABLE_ATTRIBUTES_AIRHUMIDIFIER_CA = { **AVAILABLE_ATTRIBUTES_AIRHUMIDIFIER_COMMON, - ATTR_SPEED: 'speed', + ATTR_MOTOR_SPEED: 'speed', ATTR_DEPTH: 'depth', ATTR_DRY: 'dry', } From 80aa2075c629eb2bcac73d780bf9549e02ff97f3 Mon Sep 17 00:00:00 2001 From: rolfberkenbosch <30292281+rolfberkenbosch@users.noreply.github.com> Date: Tue, 22 Jan 2019 17:24:40 +0100 Subject: [PATCH 23/37] Update locationsharinglib to version 3.0.11 (#20322) * Update google_maps.py There are known bug in locationsharinglib version 3.0.9, the developer has fixed this in 3.0.11. (https://github.com/costastf/locationsharinglib/issues/42) * Update requirements_all.txt --- homeassistant/components/device_tracker/google_maps.py | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/device_tracker/google_maps.py b/homeassistant/components/device_tracker/google_maps.py index 1f95414541c..c324f3c2757 100644 --- a/homeassistant/components/device_tracker/google_maps.py +++ b/homeassistant/components/device_tracker/google_maps.py @@ -19,7 +19,7 @@ from homeassistant.helpers.event import track_time_interval from homeassistant.helpers.typing import ConfigType from homeassistant.util import slugify, dt as dt_util -REQUIREMENTS = ['locationsharinglib==3.0.9'] +REQUIREMENTS = ['locationsharinglib==3.0.11'] _LOGGER = logging.getLogger(__name__) diff --git a/requirements_all.txt b/requirements_all.txt index 20b773d0a59..4f96d15c61f 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -625,7 +625,7 @@ liveboxplaytv==2.0.2 lmnotify==0.0.4 # homeassistant.components.device_tracker.google_maps -locationsharinglib==3.0.9 +locationsharinglib==3.0.11 # homeassistant.components.logi_circle logi_circle==0.1.7 From 4662ab215cb20d3ee02d2fa4efac6781d2b01ec7 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 22 Jan 2019 14:07:17 -0800 Subject: [PATCH 24/37] Fix invalid entity ID in entity registry (#20328) --- homeassistant/helpers/entity_registry.py | 11 ++++++-- tests/helpers/test_entity_registry.py | 34 ++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/homeassistant/helpers/entity_registry.py b/homeassistant/helpers/entity_registry.py index 8216681496b..82530708838 100644 --- a/homeassistant/helpers/entity_registry.py +++ b/homeassistant/helpers/entity_registry.py @@ -122,8 +122,15 @@ class EntityRegistry: entity_id = self.async_get_entity_id(domain, platform, unique_id) if entity_id: return self._async_update_entity( - entity_id, config_entry_id=config_entry_id, - device_id=device_id) + entity_id, + config_entry_id=config_entry_id, + device_id=device_id, + # When we changed our slugify algorithm, we invalidated some + # stored entity IDs with either a __ or ending in _. + # Fix introduced in 0.86 (Jan 23, 2018). Next line can be + # removed when we release 1.0 or in 2019. + new_entity_id='.'.join(slugify(part) for part + in entity_id.split('.', 1))) entity_id = self.async_generate_entity_id( domain, suggested_object_id or '{}_{}'.format(platform, unique_id), diff --git a/tests/helpers/test_entity_registry.py b/tests/helpers/test_entity_registry.py index a8c9086b2d2..ef7b4a60ee2 100644 --- a/tests/helpers/test_entity_registry.py +++ b/tests/helpers/test_entity_registry.py @@ -4,6 +4,7 @@ from unittest.mock import patch import pytest +from homeassistant.core import valid_entity_id from homeassistant.helpers import entity_registry from tests.common import mock_registry, flush_store @@ -222,3 +223,36 @@ async def test_migration(hass): assert entry.name == 'Test Name' assert entry.disabled_by == 'hass' assert entry.config_entry_id == 'test-config-id' + + +async def test_loading_invalid_entity_id(hass, hass_storage): + """Test we autofix invalid entity IDs.""" + hass_storage[entity_registry.STORAGE_KEY] = { + 'version': entity_registry.STORAGE_VERSION, + 'data': { + 'entities': [ + { + 'entity_id': 'test.invalid__middle', + 'platform': 'super_platform', + 'unique_id': 'id-invalid-middle', + 'name': 'registry override', + }, { + 'entity_id': 'test.invalid_end_', + 'platform': 'super_platform', + 'unique_id': 'id-invalid-end', + } + ] + } + } + + registry = await entity_registry.async_get_registry(hass) + + entity_invalid_middle = registry.async_get_or_create( + 'test', 'super_platform', 'id-invalid-middle') + + assert valid_entity_id(entity_invalid_middle.entity_id) + + entity_invalid_end = registry.async_get_or_create( + 'test', 'super_platform', 'id-invalid-end') + + assert valid_entity_id(entity_invalid_end.entity_id) From cdcc535ae199280c61360bea3ed9dd2e816d1e5d Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 23 Jan 2019 10:48:55 -0800 Subject: [PATCH 25/37] Version bump to 0.86.0 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 0191c09551b..ef9f7b38384 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -2,7 +2,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 86 -PATCH_VERSION = '0b3' +PATCH_VERSION = '0' __short_version__ = '{}.{}'.format(MAJOR_VERSION, MINOR_VERSION) __version__ = '{}.{}'.format(__short_version__, PATCH_VERSION) REQUIRED_PYTHON_VER = (3, 5, 3) From bbdb7a6f4c201d8785f72b81b2f99e361e0d1a31 Mon Sep 17 00:00:00 2001 From: Kevin Fronczak Date: Wed, 23 Jan 2019 11:37:21 -0800 Subject: [PATCH 26/37] Hotfix for blink initialization failure. Fixes #20335 (#20351) --- homeassistant/components/blink/__init__.py | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/blink/__init__.py b/homeassistant/components/blink/__init__.py index ac2a4574b9c..57500fcc8a6 100644 --- a/homeassistant/components/blink/__init__.py +++ b/homeassistant/components/blink/__init__.py @@ -15,7 +15,7 @@ from homeassistant.const import ( CONF_BINARY_SENSORS, CONF_SENSORS, CONF_FILENAME, CONF_MONITORED_CONDITIONS, TEMP_FAHRENHEIT) -REQUIREMENTS = ['blinkpy==0.11.1'] +REQUIREMENTS = ['blinkpy==0.11.2'] _LOGGER = logging.getLogger(__name__) diff --git a/requirements_all.txt b/requirements_all.txt index 4f96d15c61f..549b8fe3503 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -196,7 +196,7 @@ bellows==0.7.0 bimmer_connected==0.5.3 # homeassistant.components.blink -blinkpy==0.11.1 +blinkpy==0.11.2 # homeassistant.components.light.blinksticklight blinkstick==1.1.8 From 2b542b77895b37a5f434286455caad846afdcde0 Mon Sep 17 00:00:00 2001 From: Eliseo Martelli Date: Thu, 24 Jan 2019 02:05:16 +0100 Subject: [PATCH 27/37] [FIX] Time reporting incorrect in sensor.gtt (#20362) * quick fix * remove print statement * fixes * remove lambda * added pylint disable * should be fine now --- homeassistant/components/sensor/gtt.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/sensor/gtt.py b/homeassistant/components/sensor/gtt.py index f0e141f3549..a64c743381d 100644 --- a/homeassistant/components/sensor/gtt.py +++ b/homeassistant/components/sensor/gtt.py @@ -79,8 +79,7 @@ class GttSensor(Entity): def update(self): """Update device state.""" self.data.get_data() - next_time = datetime.strptime( - self.data.state_bus['time'][0]['run'], "%H:%M") + next_time = get_datetime(self.data.state_bus) self._state = next_time.isoformat() @@ -99,8 +98,7 @@ class GttData: def get_data(self): """Get the data from the api.""" self.bus_list = self._pygtt.get_by_stop(self._stop) - self.bus_list.sort(key=lambda b: - datetime.strptime(b['time'][0]['run'], "%H:%M")) + self.bus_list.sort(key=get_datetime) if self._bus_name is not None: self.state_bus = self.get_bus_by_name() @@ -113,3 +111,13 @@ class GttData: for bus in self.bus_list: if bus['bus_name'] == self._bus_name: return bus + + +def get_datetime(bus): + """Get the datetime from a bus.""" + bustime = datetime.strptime(bus['time'][0]['run'], "%H:%M") + now = datetime.now() + bustime = bustime.replace(year=now.year, month=now.month, day=now.day) + if bustime < now: + bustime = bustime + timedelta(days=1) + return bustime From 65d9460e09fff6cf40ae9f0c5ea09d450a91e0fe Mon Sep 17 00:00:00 2001 From: Diogo Gomes Date: Thu, 24 Jan 2019 05:14:21 +0000 Subject: [PATCH 28/37] Fix error when API doesn't return a forecast. (#20365) * add guard * wrong logic --- homeassistant/components/weather/ipma.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/homeassistant/components/weather/ipma.py b/homeassistant/components/weather/ipma.py index a2f5058ac1e..fda0fef4f25 100644 --- a/homeassistant/components/weather/ipma.py +++ b/homeassistant/components/weather/ipma.py @@ -116,6 +116,9 @@ class IPMAWeather(WeatherEntity): @property def condition(self): """Return the current condition.""" + if not self._forecast: + return + return next((k for k, v in CONDITION_CLASSES.items() if self._forecast[0].idWeatherType in v), None) From 3c32bfda95a3e82b74655a56f5818a33817cdf14 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 23 Jan 2019 21:12:38 -0800 Subject: [PATCH 29/37] Fix restore state crashing invalid entity ID (#20367) --- homeassistant/helpers/restore_state.py | 6 +++-- tests/helpers/test_restore_state.py | 34 +++++++++++++++++++++++++- 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/homeassistant/helpers/restore_state.py b/homeassistant/helpers/restore_state.py index 33b612b555a..355555ec9dc 100644 --- a/homeassistant/helpers/restore_state.py +++ b/homeassistant/helpers/restore_state.py @@ -4,7 +4,8 @@ import logging from datetime import timedelta, datetime from typing import Any, Dict, List, Set, Optional # noqa pylint_disable=unused-import -from homeassistant.core import HomeAssistant, callback, State, CoreState +from homeassistant.core import ( + HomeAssistant, callback, State, CoreState, valid_entity_id) from homeassistant.const import ( EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP) import homeassistant.util.dt as dt_util @@ -80,7 +81,8 @@ class RestoreStateData(): else: data.last_states = { item['state']['entity_id']: StoredState.from_dict(item) - for item in stored_states} + for item in stored_states + if valid_entity_id(item['state']['entity_id'])} _LOGGER.debug( 'Created cache with %s', list(data.last_states)) diff --git a/tests/helpers/test_restore_state.py b/tests/helpers/test_restore_state.py index b13bc87421b..bc2ab6937c3 100644 --- a/tests/helpers/test_restore_state.py +++ b/tests/helpers/test_restore_state.py @@ -6,7 +6,8 @@ from homeassistant.core import CoreState, State from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.entity import Entity from homeassistant.helpers.restore_state import ( - RestoreStateData, RestoreEntity, StoredState, DATA_RESTORE_STATE_TASK) + RestoreStateData, RestoreEntity, StoredState, DATA_RESTORE_STATE_TASK, + STORAGE_KEY) from homeassistant.util import dt as dt_util from asynctest import patch @@ -218,3 +219,34 @@ async def test_state_saved_on_remove(hass): # We should store the input boolean state when it is removed assert data.last_states['input_boolean.b0'].state.state == 'on' + + +async def test_restoring_invalid_entity_id(hass, hass_storage): + """Test restoring invalid entity IDs.""" + entity = RestoreEntity() + entity.hass = hass + entity.entity_id = 'test.invalid__entity_id' + now = dt_util.utcnow().isoformat() + hass_storage[STORAGE_KEY] = { + 'version': 1, + 'key': STORAGE_KEY, + 'data': [ + { + 'state': { + 'entity_id': 'test.invalid__entity_id', + 'state': 'off', + 'attributes': {}, + 'last_changed': now, + 'last_updated': now, + 'context': { + 'id': '3c2243ff5f30447eb12e7348cfd5b8ff', + 'user_id': None + } + }, + 'last_seen': dt_util.utcnow().isoformat() + } + ] + } + + state = await entity.async_get_last_state() + assert state is None From 2b2809a4c64fe328e20913fe589e3a98c2d48135 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 23 Jan 2019 21:13:55 -0800 Subject: [PATCH 30/37] Calling save before load would crash Lovelace storage (#20368) --- homeassistant/components/lovelace/__init__.py | 2 ++ tests/components/lovelace/test_init.py | 21 +++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/homeassistant/components/lovelace/__init__.py b/homeassistant/components/lovelace/__init__.py index e6f122bce19..c3254d84a73 100644 --- a/homeassistant/components/lovelace/__init__.py +++ b/homeassistant/components/lovelace/__init__.py @@ -101,6 +101,8 @@ class LovelaceStorage: async def async_save(self, config): """Save config.""" + if self._data is None: + self._data = {'config': None} self._data['config'] = config await self._store.async_save(self._data) diff --git a/tests/components/lovelace/test_init.py b/tests/components/lovelace/test_init.py index 15548b28cfb..20490f8c0cd 100644 --- a/tests/components/lovelace/test_init.py +++ b/tests/components/lovelace/test_init.py @@ -50,6 +50,27 @@ async def test_lovelace_from_storage(hass, hass_ws_client, hass_storage): } +async def test_lovelace_from_storage_save_before_load(hass, hass_ws_client, + hass_storage): + """Test we can load lovelace config from storage.""" + assert await async_setup_component(hass, 'lovelace', {}) + client = await hass_ws_client(hass) + + # Store new config + await client.send_json({ + 'id': 6, + 'type': 'lovelace/config/save', + 'config': { + 'yo': 'hello' + } + }) + response = await client.receive_json() + assert response['success'] + assert hass_storage[lovelace.STORAGE_KEY]['data'] == { + 'config': {'yo': 'hello'} + } + + async def test_lovelace_from_yaml(hass, hass_ws_client): """Test we load lovelace config from yaml.""" assert await async_setup_component(hass, 'lovelace', { From 761385dea1d38099f1f3668074a32a1aee304305 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 23 Jan 2019 21:14:52 -0800 Subject: [PATCH 31/37] Bumped version to 0.86.1 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index ef9f7b38384..aaac6fecb73 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -2,7 +2,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 86 -PATCH_VERSION = '0' +PATCH_VERSION = '1' __short_version__ = '{}.{}'.format(MAJOR_VERSION, MINOR_VERSION) __version__ = '{}.{}'.format(__short_version__, PATCH_VERSION) REQUIRED_PYTHON_VER = (3, 5, 3) From ebcae2503c95d3361ebc2d716bb169fab91fb8fa Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Thu, 24 Jan 2019 23:46:55 +0100 Subject: [PATCH 32/37] Philips Hue - Remove unnessesary warning (#20394) for white hue bulbs and bulbs from other brands like Ikea and Innr, this warning will be issued while this is not really a problem. So just remove the warning. --- homeassistant/components/hue/light.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/homeassistant/components/hue/light.py b/homeassistant/components/hue/light.py index 7a1449e00c6..89b731cb0e5 100644 --- a/homeassistant/components/hue/light.py +++ b/homeassistant/components/hue/light.py @@ -228,9 +228,6 @@ class HueLight(Light): self.is_philips = light.manufacturername == 'Philips' self.gamut_typ = self.light.colorgamuttype self.gamut = self.light.colorgamut - if not self.gamut: - err_msg = 'Can not get color gamut of light "%s"' - _LOGGER.warning(err_msg, self.name) @property def unique_id(self): From d7859b590029958d49e63a943ece226d096fd206 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 24 Jan 2019 17:53:01 -0800 Subject: [PATCH 33/37] history allowed to load states with invalid entity IDs (#20399) --- homeassistant/components/recorder/models.py | 3 +++ homeassistant/core.py | 7 +++++-- tests/components/recorder/test_models.py | 9 +++++++++ 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/recorder/models.py b/homeassistant/components/recorder/models.py index 7a655c29434..d1be17b83d5 100644 --- a/homeassistant/components/recorder/models.py +++ b/homeassistant/components/recorder/models.py @@ -131,6 +131,9 @@ class States(Base): # type: ignore _process_timestamp(self.last_changed), _process_timestamp(self.last_updated), context=context, + # Temp, because database can still store invalid entity IDs + # Remove with 1.0 or in 2020. + temp_invalid_id_bypass=True ) except ValueError: # When json.loads fails diff --git a/homeassistant/core.py b/homeassistant/core.py index 02b474e3cd2..aea37f74898 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -663,11 +663,14 @@ class State: attributes: Optional[Dict] = None, last_changed: Optional[datetime.datetime] = None, last_updated: Optional[datetime.datetime] = None, - context: Optional[Context] = None) -> None: + context: Optional[Context] = None, + # Temp, because database can still store invalid entity IDs + # Remove with 1.0 or in 2020. + temp_invalid_id_bypass: Optional[bool] = False) -> None: """Initialize a new state.""" state = str(state) - if not valid_entity_id(entity_id): + if not valid_entity_id(entity_id) and not temp_invalid_id_bypass: raise InvalidEntityFormatError(( "Invalid entity id encountered: {}. " "Format should be .").format(entity_id)) diff --git a/tests/components/recorder/test_models.py b/tests/components/recorder/test_models.py index 3d1beb3a642..b56a7632df3 100644 --- a/tests/components/recorder/test_models.py +++ b/tests/components/recorder/test_models.py @@ -142,3 +142,12 @@ class TestRecorderRuns(unittest.TestCase): assert sorted(run.entity_ids()) == ['sensor.humidity', 'sensor.lux'] assert run.entity_ids(in_run2) == ['sensor.humidity'] + + +def test_states_from_native_invalid_entity_id(): + """Test loading a state from an invalid entity ID.""" + event = States() + event.entity_id = "test.invalid__id" + event.attributes = "{}" + state = event.to_native() + assert state.entity_id == 'test.invalid__id' From 259915eee97a2b767c4628622441f36d3a396a89 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 24 Jan 2019 19:34:10 -0800 Subject: [PATCH 34/37] Bumped version to 0.86.2 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index aaac6fecb73..64b04ebe13c 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -2,7 +2,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 86 -PATCH_VERSION = '1' +PATCH_VERSION = '2' __short_version__ = '{}.{}'.format(MAJOR_VERSION, MINOR_VERSION) __version__ = '{}.{}'.format(__short_version__, PATCH_VERSION) REQUIRED_PYTHON_VER = (3, 5, 3) From 672a896124b48eb4e71f51757a027be22fa8f4c0 Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Sat, 26 Jan 2019 21:50:34 +0100 Subject: [PATCH 35/37] Philips Hue, include debug message for color gamut (#20455) --- homeassistant/components/hue/light.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/hue/light.py b/homeassistant/components/hue/light.py index 89b731cb0e5..a10b42fbeee 100644 --- a/homeassistant/components/hue/light.py +++ b/homeassistant/components/hue/light.py @@ -228,6 +228,7 @@ class HueLight(Light): self.is_philips = light.manufacturername == 'Philips' self.gamut_typ = self.light.colorgamuttype self.gamut = self.light.colorgamut + _LOGGER.debug("Color gamut of %s: %s", self.name, str(self.gamut)) @property def unique_id(self): From 6330bb100485766ffeb8d7d94fc19125962596f5 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 26 Jan 2019 14:09:41 -0800 Subject: [PATCH 36/37] Warn for old slugs/entity ids (#20478) * Warn for old slugs/entity ids * add comments * Lint * LInt * Lint * Lint --- homeassistant/bootstrap.py | 29 +++++++++++++++++ homeassistant/helpers/config_validation.py | 36 +++++++++++++++++++++- tests/helpers/test_config_validation.py | 28 +++++++++++++++++ 3 files changed, 92 insertions(+), 1 deletion(-) diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index c764bfe8c21..5dd62005609 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -18,6 +18,7 @@ from homeassistant.util.logging import AsyncHandler from homeassistant.util.package import async_get_user_site, is_virtual_env from homeassistant.util.yaml import clear_secret_cache from homeassistant.exceptions import HomeAssistantError +from homeassistant.helpers import config_validation as cv _LOGGER = logging.getLogger(__name__) @@ -153,6 +154,34 @@ async def async_from_config_dict(config: Dict[str, Any], stop = time() _LOGGER.info("Home Assistant initialized in %.2fs", stop-start) + # TEMP: warn users for invalid slugs + # Remove after 0.94 or 1.0 + if cv.INVALID_SLUGS_FOUND or cv.INVALID_ENTITY_IDS_FOUND: + msg = [] + + if cv.INVALID_ENTITY_IDS_FOUND: + msg.append( + "Your configuration contains invalid entity ID references. " + "Please find and update the following. " + "This will become a breaking change." + ) + msg.append('\n'.join('- {} -> {}'.format(*item) + for item + in cv.INVALID_ENTITY_IDS_FOUND.items())) + + if cv.INVALID_SLUGS_FOUND: + msg.append( + "Your configuration contains invalid slugs. " + "Please find and update the following. " + "This will become a breaking change." + ) + msg.append('\n'.join('- {} -> {}'.format(*item) + for item in cv.INVALID_SLUGS_FOUND.items())) + + hass.components.persistent_notification.async_create( + '\n\n'.join(msg), "Config Warning", "config_warning" + ) + return hass diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py index 475135b4cce..1edf52cbb7f 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -26,6 +26,13 @@ from homeassistant.helpers import template as template_helper # pylint: disable=invalid-name TIME_PERIOD_ERROR = "offset {} should be format 'HH:MM' or 'HH:MM:SS'" +OLD_SLUG_VALIDATION = r'^[a-z0-9_]+$' +OLD_ENTITY_ID_VALIDATION = r"^(\w+)\.(\w+)$" +# Keep track of invalid slugs and entity ids found so we can create a +# persistent notification. Rare temporary exception to use a global. +INVALID_SLUGS_FOUND = {} +INVALID_ENTITY_IDS_FOUND = {} + # Home Assistant types byte = vol.All(vol.Coerce(int), vol.Range(min=0, max=255)) @@ -149,6 +156,18 @@ def entity_id(value: Any) -> str: value = string(value).lower() if valid_entity_id(value): return value + elif re.match(OLD_ENTITY_ID_VALIDATION, value): + # To ease the breaking change, we allow old slugs for now + # Remove after 0.94 or 1.0 + fixed = '.'.join(util_slugify(part) for part in value.split('.', 1)) + INVALID_ENTITY_IDS_FOUND[value] = fixed + logging.getLogger(__name__).warning( + "Found invalid entity_id %s, please update with %s. This " + "will become a breaking change.", + value, fixed + ) + return value + raise vol.Invalid('Entity ID {} is an invalid entity id'.format(value)) @@ -333,7 +352,22 @@ def schema_with_slug_keys(value_schema: Union[T, Callable]) -> Callable: raise vol.Invalid('expected dictionary') for key in value.keys(): - slug(key) + try: + slug(key) + except vol.Invalid: + # To ease the breaking change, we allow old slugs for now + # Remove after 0.94 or 1.0 + if re.match(OLD_SLUG_VALIDATION, key): + fixed = util_slugify(key) + INVALID_SLUGS_FOUND[key] = fixed + logging.getLogger(__name__).warning( + "Found invalid slug %s, please update with %s. This " + "will be come a breaking change.", + key, fixed + ) + else: + raise + return schema(value) return verify diff --git a/tests/helpers/test_config_validation.py b/tests/helpers/test_config_validation.py index 03dd3cfe55a..1bae84b5320 100644 --- a/tests/helpers/test_config_validation.py +++ b/tests/helpers/test_config_validation.py @@ -602,3 +602,31 @@ def test_comp_entity_ids(): for invalid in (['light.kitchen', 'not-entity-id'], '*', ''): with pytest.raises(vol.Invalid): schema(invalid) + + +def test_schema_with_slug_keys_allows_old_slugs(caplog): + """Test schema with slug keys allowing old slugs.""" + schema = cv.schema_with_slug_keys(str) + + with patch.dict(cv.INVALID_SLUGS_FOUND, clear=True): + for value in ('_world', 'wow__yeah'): + caplog.clear() + # Will raise if not allowing old slugs + schema({value: 'yo'}) + assert "Found invalid slug {}".format(value) in caplog.text + + assert len(cv.INVALID_SLUGS_FOUND) == 2 + + +def test_entity_id_allow_old_validation(caplog): + """Test schema allowing old entity_ids.""" + schema = vol.Schema(cv.entity_id) + + with patch.dict(cv.INVALID_ENTITY_IDS_FOUND, clear=True): + for value in ('hello.__world', 'great.wow__yeah'): + caplog.clear() + # Will raise if not allowing old entity ID + schema(value) + assert "Found invalid entity_id {}".format(value) in caplog.text + + assert len(cv.INVALID_ENTITY_IDS_FOUND) == 2 From dcfe7b2280b4634aefb58f395f2739a31cfd9739 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 26 Jan 2019 14:11:44 -0800 Subject: [PATCH 37/37] Bumped version to 0.86.3 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 64b04ebe13c..dca028e0681 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -2,7 +2,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 86 -PATCH_VERSION = '2' +PATCH_VERSION = '3' __short_version__ = '{}.{}'.format(MAJOR_VERSION, MINOR_VERSION) __version__ = '{}.{}'.format(__short_version__, PATCH_VERSION) REQUIRED_PYTHON_VER = (3, 5, 3)