From dd7890c848b4e4b04fa771e1abbda60da207e6fc Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 16 Jul 2018 08:52:37 +0200 Subject: [PATCH 001/113] Version bump to 0.75.0.dev0 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 182367f3890c..a84c278350f5 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ # coding: utf-8 """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 -MINOR_VERSION = 74 +MINOR_VERSION = 75 PATCH_VERSION = '0.dev0' __short_version__ = '{}.{}'.format(MAJOR_VERSION, MINOR_VERSION) __version__ = '{}.{}'.format(__short_version__, PATCH_VERSION) From ad4cba70a0f27b8fb6b263d6d9cb04fcde237c61 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 16 Jul 2018 10:32:07 +0200 Subject: [PATCH 002/113] Extract SSL context creation to helper (#15483) * Extract SSL context creation to helper * Lint --- homeassistant/components/http/__init__.py | 20 +--------- homeassistant/helpers/aiohttp_client.py | 7 +--- homeassistant/util/ssl.py | 46 +++++++++++++++++++++++ 3 files changed, 50 insertions(+), 23 deletions(-) create mode 100644 homeassistant/util/ssl.py diff --git a/homeassistant/components/http/__init__.py b/homeassistant/components/http/__init__.py index 37a6805dfb58..c8eba41e66b0 100644 --- a/homeassistant/components/http/__init__.py +++ b/homeassistant/components/http/__init__.py @@ -19,6 +19,7 @@ import homeassistant.helpers.config_validation as cv import homeassistant.remote as rem import homeassistant.util as hass_util from homeassistant.util.logging import HideSensitiveDataFilter +from homeassistant.util import ssl as ssl_util from .auth import setup_auth from .ban import setup_bans @@ -49,21 +50,6 @@ CONF_TRUSTED_NETWORKS = 'trusted_networks' CONF_LOGIN_ATTEMPTS_THRESHOLD = 'login_attempts_threshold' CONF_IP_BAN_ENABLED = 'ip_ban_enabled' -# TLS configuration follows the best-practice guidelines specified here: -# https://wiki.mozilla.org/Security/Server_Side_TLS -# Modern guidelines are followed. -SSL_VERSION = ssl.PROTOCOL_TLS # pylint: disable=no-member -SSL_OPTS = ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3 | \ - ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1 | \ - ssl.OP_CIPHER_SERVER_PREFERENCE -if hasattr(ssl, 'OP_NO_COMPRESSION'): - SSL_OPTS |= ssl.OP_NO_COMPRESSION -CIPHERS = "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:" \ - "ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:" \ - "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:" \ - "ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:" \ - "ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256" - _LOGGER = logging.getLogger(__name__) DEFAULT_SERVER_HOST = '0.0.0.0' @@ -300,9 +286,7 @@ class HomeAssistantHTTP(object): if self.ssl_certificate: try: - context = ssl.SSLContext(SSL_VERSION) - context.options |= SSL_OPTS - context.set_ciphers(CIPHERS) + context = ssl_util.server_context() context.load_cert_chain(self.ssl_certificate, self.ssl_key) except OSError as error: _LOGGER.error("Could not read SSL certificate from %s: %s", diff --git a/homeassistant/helpers/aiohttp_client.py b/homeassistant/helpers/aiohttp_client.py index 5ee2cd560819..71f3374f0c03 100644 --- a/homeassistant/helpers/aiohttp_client.py +++ b/homeassistant/helpers/aiohttp_client.py @@ -1,6 +1,5 @@ """Helper for aiohttp webclient stuff.""" import asyncio -import ssl import sys import aiohttp @@ -8,11 +7,11 @@ from aiohttp.hdrs import USER_AGENT, CONTENT_TYPE from aiohttp import web from aiohttp.web_exceptions import HTTPGatewayTimeout, HTTPBadGateway import async_timeout -import certifi from homeassistant.core import callback from homeassistant.const import EVENT_HOMEASSISTANT_CLOSE, __version__ from homeassistant.loader import bind_hass +from homeassistant.util import ssl as ssl_util DATA_CONNECTOR = 'aiohttp_connector' DATA_CONNECTOR_NOTVERIFY = 'aiohttp_connector_notverify' @@ -154,9 +153,7 @@ def _async_get_connector(hass, verify_ssl=True): return hass.data[key] if verify_ssl: - ssl_context = ssl.SSLContext(ssl.PROTOCOL_SSLv23) - ssl_context.load_verify_locations(cafile=certifi.where(), - capath=None) + ssl_context = ssl_util.client_context() else: ssl_context = False diff --git a/homeassistant/util/ssl.py b/homeassistant/util/ssl.py new file mode 100644 index 000000000000..fc02009b7af7 --- /dev/null +++ b/homeassistant/util/ssl.py @@ -0,0 +1,46 @@ +"""Helper to create SSL contexts.""" +import ssl + +import certifi + + +def client_context(): + """Return an SSL context for making requests.""" + context = _get_context() + context.verify_mode = ssl.CERT_REQUIRED + context.check_hostname = True + context.load_verify_locations(cafile=certifi.where(), capath=None) + return context + + +def server_context(): + """Return an SSL context for being a server.""" + context = _get_context() + context.options |= ssl.OP_CIPHER_SERVER_PREFERENCE + return context + + +def _get_context(): + """Return an SSL context following the Mozilla recommendations. + + TLS configuration follows the best-practice guidelines specified here: + https://wiki.mozilla.org/Security/Server_Side_TLS + Modern guidelines are followed. + """ + context = ssl.SSLContext(ssl.PROTOCOL_TLS) # pylint: disable=no-member + + context.options |= ( + ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3 | + ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1 + ) + if hasattr(ssl, 'OP_NO_COMPRESSION'): + context.options |= ssl.OP_NO_COMPRESSION + + context.set_ciphers( + "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:" + "ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:" + "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:" + "ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:" + "ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256" + ) + return context From 0b2aff61bbd8d3966fdd9c718e8f49ecc171edae Mon Sep 17 00:00:00 2001 From: squirtbrnr <35356217+squirtbrnr@users.noreply.github.com> Date: Mon, 16 Jul 2018 23:50:56 -0500 Subject: [PATCH 003/113] Delay setup of waze travel time component (#15455) * delay setup of component Copied the necessary lines of code from the google travel time component to fix the setup delay in waze travel time component. Previously it was only watching the homeassistant start event on the bus, but doing nothing with it. * Update waze_travel_time.py * Update waze_travel_time.py --- homeassistant/components/sensor/waze_travel_time.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/sensor/waze_travel_time.py b/homeassistant/components/sensor/waze_travel_time.py index 0b059379c118..79b14c94b881 100644 --- a/homeassistant/components/sensor/waze_travel_time.py +++ b/homeassistant/components/sensor/waze_travel_time.py @@ -64,8 +64,9 @@ def setup_platform(hass, config, add_devices, discovery_info=None): sensor = WazeTravelTime(name, origin, destination, region, incl_filter, excl_filter) - add_devices([sensor], True) + add_devices([sensor]) + # Wait until start event is sent to load this component. hass.bus.listen_once(EVENT_HOMEASSISTANT_START, sensor.update) From 7eb5cd1267cebda086ad7a26a99b27353796b623 Mon Sep 17 00:00:00 2001 From: Luke Fritz Date: Tue, 17 Jul 2018 00:56:50 -0500 Subject: [PATCH 004/113] Bump pyarlo==0.2.0, fixes #15486 (#15503) --- homeassistant/components/arlo.py | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/arlo.py b/homeassistant/components/arlo.py index 475e43e55a40..c6a414b9d912 100644 --- a/homeassistant/components/arlo.py +++ b/homeassistant/components/arlo.py @@ -16,7 +16,7 @@ from homeassistant.const import ( from homeassistant.helpers.event import track_time_interval from homeassistant.helpers.dispatcher import dispatcher_send -REQUIREMENTS = ['pyarlo==0.1.9'] +REQUIREMENTS = ['pyarlo==0.2.0'] _LOGGER = logging.getLogger(__name__) diff --git a/requirements_all.txt b/requirements_all.txt index cde7060df74f..c6a629c1278c 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -728,7 +728,7 @@ pyairvisual==2.0.1 pyalarmdotcom==0.3.2 # homeassistant.components.arlo -pyarlo==0.1.9 +pyarlo==0.2.0 # homeassistant.components.notify.xmpp pyasn1-modules==0.1.5 From 8797cb78a901ba8b2d262408bde9144920ce17e6 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 17 Jul 2018 09:24:51 +0200 Subject: [PATCH 005/113] Add current user WS command (#15485) --- homeassistant/auth/__init__.py | 5 ++++ homeassistant/components/auth/__init__.py | 29 +++++++++++++++++++ homeassistant/components/frontend/__init__.py | 2 +- tests/components/auth/test_init.py | 27 +++++++++++++++++ 4 files changed, 62 insertions(+), 1 deletion(-) diff --git a/homeassistant/auth/__init__.py b/homeassistant/auth/__init__.py index 9f342a50407b..cc2f244efb4f 100644 --- a/homeassistant/auth/__init__.py +++ b/homeassistant/auth/__init__.py @@ -194,9 +194,14 @@ class AuthManager: tkn = self._access_tokens.get(token) if tkn is None: + _LOGGER.debug('Attempt to get non-existing access token') return None if tkn.expired or not tkn.refresh_token.user.is_active: + if tkn.expired: + _LOGGER.debug('Attempt to get expired access token') + else: + _LOGGER.debug('Attempt to get access token for inactive user') self._access_tokens.pop(token) return None diff --git a/homeassistant/components/auth/__init__.py b/homeassistant/components/auth/__init__.py index 6518c2bcc1c7..84287c2e425a 100644 --- a/homeassistant/components/auth/__init__.py +++ b/homeassistant/components/auth/__init__.py @@ -113,6 +113,7 @@ from homeassistant import data_entry_flow from homeassistant.core import callback from homeassistant.helpers.data_entry_flow import ( FlowManagerIndexView, FlowManagerResourceView) +from homeassistant.components import websocket_api from homeassistant.components.http.view import HomeAssistantView from homeassistant.components.http.data_validator import RequestDataValidator from homeassistant.util import dt as dt_util @@ -122,6 +123,12 @@ from . import indieauth DOMAIN = 'auth' DEPENDENCIES = ['http'] + +WS_TYPE_CURRENT_USER = 'auth/current_user' +SCHEMA_WS_CURRENT_USER = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ + vol.Required('type'): WS_TYPE_CURRENT_USER, +}) + _LOGGER = logging.getLogger(__name__) @@ -136,6 +143,11 @@ async def async_setup(hass, config): hass.http.register_view(GrantTokenView(retrieve_credentials)) hass.http.register_view(LinkUserView(retrieve_credentials)) + hass.components.websocket_api.async_register_command( + WS_TYPE_CURRENT_USER, websocket_current_user, + SCHEMA_WS_CURRENT_USER + ) + return True @@ -383,3 +395,20 @@ def _create_cred_store(): return None return store_credentials, retrieve_credentials + + +@callback +def websocket_current_user(hass, connection, msg): + """Return the current user.""" + user = connection.request.get('hass_user') + + if user is None: + connection.to_write.put_nowait(websocket_api.error_message( + msg['id'], 'no_user', 'Not authenticated as a user')) + return + + connection.to_write.put_nowait(websocket_api.result_message(msg['id'], { + 'id': user.id, + 'name': user.name, + 'is_owner': user.is_owner, + })) diff --git a/homeassistant/components/frontend/__init__.py b/homeassistant/components/frontend/__init__.py index 61def9075f8a..89233b6c5181 100644 --- a/homeassistant/components/frontend/__init__.py +++ b/homeassistant/components/frontend/__init__.py @@ -257,7 +257,7 @@ async def async_setup(hass, config): await asyncio.wait( [async_register_built_in_panel(hass, panel) for panel in ( 'dev-event', 'dev-info', 'dev-service', 'dev-state', - 'dev-template', 'dev-mqtt', 'kiosk', 'lovelace')], + 'dev-template', 'dev-mqtt', 'kiosk', 'lovelace', 'profile')], loop=hass.loop) hass.data[DATA_FINALIZE_PANEL] = async_finalize_panel diff --git a/tests/components/auth/test_init.py b/tests/components/auth/test_init.py index 5f3a2d6478c8..46b88e46b4dc 100644 --- a/tests/components/auth/test_init.py +++ b/tests/components/auth/test_init.py @@ -2,6 +2,7 @@ from datetime import timedelta from unittest.mock import patch +from homeassistant.setup import async_setup_component from homeassistant.util.dt import utcnow from homeassistant.components import auth @@ -66,3 +67,29 @@ def test_credential_store_expiration(): with patch('homeassistant.util.dt.utcnow', return_value=now + timedelta(minutes=9, seconds=59)): assert retrieve(client_id, code) == credentials + + +async def test_ws_current_user(hass, hass_ws_client, hass_access_token): + """Test the current user command.""" + assert await async_setup_component(hass, 'auth', { + 'http': { + 'api_password': 'bla' + } + }) + with patch('homeassistant.auth.AuthManager.active', return_value=True): + client = await hass_ws_client(hass, hass_access_token) + + await client.send_json({ + 'id': 5, + 'type': auth.WS_TYPE_CURRENT_USER, + }) + + result = await client.receive_json() + assert result['success'], result + + user = hass_access_token.refresh_token.user + user_dict = result['result'] + + assert user_dict['name'] == user.name + assert user_dict['id'] == user.id + assert user_dict['is_owner'] == user.is_owner From db3cdb288e2435525719b18de76ae7eadc046a81 Mon Sep 17 00:00:00 2001 From: Matthew Garrett Date: Tue, 17 Jul 2018 01:06:06 -0700 Subject: [PATCH 006/113] Update HomeKit module code (#15502) This fixes a bunch of bugs, including issues with concurrency in devices that present multiple accessories, devices that insist on the TLV entries being in the order that Apple use, and handling devices that send headers and data in separate chunks. This should improve compatibility with a whole bunch of HomeKit devices. --- homeassistant/components/homekit_controller/__init__.py | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/homekit_controller/__init__.py b/homeassistant/components/homekit_controller/__init__.py index 237a6d219f0c..5e24fe823406 100644 --- a/homeassistant/components/homekit_controller/__init__.py +++ b/homeassistant/components/homekit_controller/__init__.py @@ -14,7 +14,7 @@ from homeassistant.components.discovery import SERVICE_HOMEKIT from homeassistant.helpers import discovery from homeassistant.helpers.entity import Entity -REQUIREMENTS = ['homekit==0.6'] +REQUIREMENTS = ['homekit==0.10'] DOMAIN = 'homekit_controller' HOMEKIT_DIR = '.homekit' diff --git a/requirements_all.txt b/requirements_all.txt index c6a629c1278c..cea2ddf9be25 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -418,7 +418,7 @@ holidays==0.9.5 home-assistant-frontend==20180716.0 # homeassistant.components.homekit_controller -# homekit==0.6 +# homekit==0.10 # homeassistant.components.homematicip_cloud homematicip==0.9.8 From b0a3207454882e8aa284724c739bad1ff7b99a8b Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 17 Jul 2018 10:49:15 +0200 Subject: [PATCH 007/113] Add onboarding support (#15492) * Add onboarding support * Lint * Address comments * Mark user step as done if owner user already created --- homeassistant/auth/providers/homeassistant.py | 3 + homeassistant/components/frontend/__init__.py | 14 +- .../components/onboarding/__init__.py | 56 +++++++ homeassistant/components/onboarding/const.py | 7 + homeassistant/components/onboarding/views.py | 106 ++++++++++++++ tests/components/onboarding/__init__.py | 11 ++ tests/components/onboarding/test_init.py | 77 ++++++++++ tests/components/onboarding/test_views.py | 137 ++++++++++++++++++ 8 files changed, 409 insertions(+), 2 deletions(-) create mode 100644 homeassistant/components/onboarding/__init__.py create mode 100644 homeassistant/components/onboarding/const.py create mode 100644 homeassistant/components/onboarding/views.py create mode 100644 tests/components/onboarding/__init__.py create mode 100644 tests/components/onboarding/test_init.py create mode 100644 tests/components/onboarding/test_views.py diff --git a/homeassistant/auth/providers/homeassistant.py b/homeassistant/auth/providers/homeassistant.py index 17a56bc5f423..b359f67d77fd 100644 --- a/homeassistant/auth/providers/homeassistant.py +++ b/homeassistant/auth/providers/homeassistant.py @@ -150,6 +150,9 @@ class HassAuthProvider(AuthProvider): async def async_initialize(self): """Initialize the auth provider.""" + if self.data is not None: + return + self.data = Data(self.hass) await self.data.async_load() diff --git a/homeassistant/components/frontend/__init__.py b/homeassistant/components/frontend/__init__.py index 89233b6c5181..958247cadc5f 100644 --- a/homeassistant/components/frontend/__init__.py +++ b/homeassistant/components/frontend/__init__.py @@ -29,7 +29,7 @@ from homeassistant.util.yaml import load_yaml REQUIREMENTS = ['home-assistant-frontend==20180716.0'] DOMAIN = 'frontend' -DEPENDENCIES = ['api', 'websocket_api', 'http', 'system_log'] +DEPENDENCIES = ['api', 'websocket_api', 'http', 'system_log', 'onboarding'] CONF_THEMES = 'themes' CONF_EXTRA_HTML_URL = 'extra_html_url' @@ -377,6 +377,16 @@ class IndexView(HomeAssistantView): latest = self.repo_path is not None or \ _is_latest(self.js_option, request) + if not hass.components.onboarding.async_is_onboarded(): + if latest: + location = '/frontend_latest/onboarding.html' + else: + location = '/frontend_es5/onboarding.html' + + return web.Response(status=302, headers={ + 'location': location + }) + no_auth = '1' if hass.config.api.api_password and not request[KEY_AUTHENTICATED]: # do not try to auto connect on load @@ -480,7 +490,7 @@ def websocket_get_translations(hass, connection, msg): Async friendly. """ async def send_translations(): - """Send a camera still.""" + """Send a translation.""" resources = await async_get_translations(hass, msg['language']) connection.send_message_outside(websocket_api.result_message( msg['id'], { diff --git a/homeassistant/components/onboarding/__init__.py b/homeassistant/components/onboarding/__init__.py new file mode 100644 index 000000000000..6dea5919f096 --- /dev/null +++ b/homeassistant/components/onboarding/__init__.py @@ -0,0 +1,56 @@ +"""Component to help onboard new users.""" +from homeassistant.core import callback +from homeassistant.loader import bind_hass + +from .const import STEPS, STEP_USER, DOMAIN + +DEPENDENCIES = ['http'] +STORAGE_KEY = DOMAIN +STORAGE_VERSION = 1 + + +@bind_hass +@callback +def async_is_onboarded(hass): + """Return if Home Assistant has been onboarded.""" + # Temporarily: if auth not active, always set onboarded=True + if not hass.auth.active: + return True + + return hass.data.get(DOMAIN, True) + + +async def async_setup(hass, config): + """Set up the onboard component.""" + store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY) + data = await store.async_load() + + if data is None: + data = { + 'done': [] + } + + if STEP_USER not in data['done']: + # Users can already have created an owner account via the command line + # If so, mark the user step as done. + has_owner = False + + for user in await hass.auth.async_get_users(): + if user.is_owner: + has_owner = True + break + + if has_owner: + data['done'].append(STEP_USER) + await store.async_save(data) + + if set(data['done']) == set(STEPS): + return True + + hass.data[DOMAIN] = False + + from . import views + + await views.async_setup(hass, data, store) + + return True diff --git a/homeassistant/components/onboarding/const.py b/homeassistant/components/onboarding/const.py new file mode 100644 index 000000000000..3aa106ac18c4 --- /dev/null +++ b/homeassistant/components/onboarding/const.py @@ -0,0 +1,7 @@ +"""Constants for the onboarding component.""" +DOMAIN = 'onboarding' +STEP_USER = 'user' + +STEPS = [ + STEP_USER +] diff --git a/homeassistant/components/onboarding/views.py b/homeassistant/components/onboarding/views.py new file mode 100644 index 000000000000..1a536a1bc43c --- /dev/null +++ b/homeassistant/components/onboarding/views.py @@ -0,0 +1,106 @@ +"""Onboarding views.""" +import asyncio + +import voluptuous as vol + +from homeassistant.core import callback +from homeassistant.components.http.view import HomeAssistantView +from homeassistant.components.http.data_validator import RequestDataValidator + +from .const import DOMAIN, STEPS, STEP_USER + + +async def async_setup(hass, data, store): + """Setup onboarding.""" + hass.http.register_view(OnboardingView(data, store)) + hass.http.register_view(UserOnboardingView(data, store)) + + +class OnboardingView(HomeAssistantView): + """Returns the onboarding status.""" + + requires_auth = False + url = '/api/onboarding' + name = 'api:onboarding' + + def __init__(self, data, store): + """Initialize the onboarding view.""" + self._store = store + self._data = data + + async def get(self, request): + """Return the onboarding status.""" + return self.json([ + { + 'step': key, + 'done': key in self._data['done'], + } for key in STEPS + ]) + + +class _BaseOnboardingView(HomeAssistantView): + """Base class for onboarding.""" + + requires_auth = False + step = None + + def __init__(self, data, store): + """Initialize the onboarding view.""" + self._store = store + self._data = data + self._lock = asyncio.Lock() + + @callback + def _async_is_done(self): + """Return if this step is done.""" + return self.step in self._data['done'] + + async def _async_mark_done(self, hass): + """Mark step as done.""" + self._data['done'].append(self.step) + await self._store.async_save(self._data) + + hass.data[DOMAIN] = len(self._data) == len(STEPS) + + +class UserOnboardingView(_BaseOnboardingView): + """View to handle onboarding.""" + + url = '/api/onboarding/users' + name = 'api:onboarding:users' + step = STEP_USER + + @RequestDataValidator(vol.Schema({ + vol.Required('name'): str, + vol.Required('username'): str, + vol.Required('password'): str, + })) + async def post(self, request, data): + """Return the manifest.json.""" + hass = request.app['hass'] + + async with self._lock: + if self._async_is_done(): + return self.json_message('User step already done', 403) + + provider = _async_get_hass_provider(hass) + await provider.async_initialize() + + user = await hass.auth.async_create_user(data['name']) + await hass.async_add_executor_job( + provider.data.add_auth, data['username'], data['password']) + credentials = await provider.async_get_or_create_credentials({ + 'username': data['username'] + }) + await hass.auth.async_link_user(user, credentials) + await self._async_mark_done(hass) + + +@callback +def _async_get_hass_provider(hass): + """Get the Home Assistant auth provider.""" + for prv in hass.auth.auth_providers: + if prv.type == 'homeassistant': + return prv + + raise RuntimeError('No Home Assistant provider found') diff --git a/tests/components/onboarding/__init__.py b/tests/components/onboarding/__init__.py new file mode 100644 index 000000000000..62c6dc929a10 --- /dev/null +++ b/tests/components/onboarding/__init__.py @@ -0,0 +1,11 @@ +"""Tests for the onboarding component.""" + +from homeassistant.components import onboarding + + +def mock_storage(hass_storage, data): + """Mock the onboarding storage.""" + hass_storage[onboarding.STORAGE_KEY] = { + 'version': onboarding.STORAGE_VERSION, + 'data': data + } diff --git a/tests/components/onboarding/test_init.py b/tests/components/onboarding/test_init.py new file mode 100644 index 000000000000..57a81a78da34 --- /dev/null +++ b/tests/components/onboarding/test_init.py @@ -0,0 +1,77 @@ +"""Tests for the init.""" +from unittest.mock import patch, Mock + +from homeassistant.setup import async_setup_component +from homeassistant.components import onboarding + +from tests.common import mock_coro, MockUser + +from . import mock_storage + +# Temporarily: if auth not active, always set onboarded=True + + +async def test_not_setup_views_if_onboarded(hass, hass_storage): + """Test if onboarding is done, we don't setup views.""" + mock_storage(hass_storage, { + 'done': onboarding.STEPS + }) + + with patch( + 'homeassistant.components.onboarding.views.async_setup' + ) as mock_setup: + assert await async_setup_component(hass, 'onboarding', {}) + + assert len(mock_setup.mock_calls) == 0 + assert onboarding.DOMAIN not in hass.data + assert onboarding.async_is_onboarded(hass) + + +async def test_setup_views_if_not_onboarded(hass): + """Test if onboarding is not done, we setup views.""" + with patch( + 'homeassistant.components.onboarding.views.async_setup', + return_value=mock_coro() + ) as mock_setup: + assert await async_setup_component(hass, 'onboarding', {}) + + assert len(mock_setup.mock_calls) == 1 + assert onboarding.DOMAIN in hass.data + + with patch('homeassistant.auth.AuthManager.active', return_value=True): + assert not onboarding.async_is_onboarded(hass) + + +async def test_is_onboarded(): + """Test the is onboarded function.""" + hass = Mock() + hass.data = {} + + with patch('homeassistant.auth.AuthManager.active', return_value=False): + assert onboarding.async_is_onboarded(hass) + + with patch('homeassistant.auth.AuthManager.active', return_value=True): + assert onboarding.async_is_onboarded(hass) + + hass.data[onboarding.DOMAIN] = True + assert onboarding.async_is_onboarded(hass) + + hass.data[onboarding.DOMAIN] = False + assert not onboarding.async_is_onboarded(hass) + + +async def test_having_owner_finishes_user_step(hass, hass_storage): + """If owner user already exists, mark user step as complete.""" + MockUser(is_owner=True).add_to_hass(hass) + + with patch( + 'homeassistant.components.onboarding.views.async_setup' + ) as mock_setup, patch.object(onboarding, 'STEPS', [onboarding.STEP_USER]): + assert await async_setup_component(hass, 'onboarding', {}) + + assert len(mock_setup.mock_calls) == 0 + assert onboarding.DOMAIN not in hass.data + assert onboarding.async_is_onboarded(hass) + + done = hass_storage[onboarding.STORAGE_KEY]['data']['done'] + assert onboarding.STEP_USER in done diff --git a/tests/components/onboarding/test_views.py b/tests/components/onboarding/test_views.py new file mode 100644 index 000000000000..d6a4030190de --- /dev/null +++ b/tests/components/onboarding/test_views.py @@ -0,0 +1,137 @@ +"""Test the onboarding views.""" +import asyncio +from unittest.mock import patch + +import pytest + +from homeassistant.setup import async_setup_component +from homeassistant.components import onboarding +from homeassistant.components.onboarding import views + +from tests.common import register_auth_provider + +from . import mock_storage + + +@pytest.fixture(autouse=True) +def auth_active(hass): + """Ensure auth is always active.""" + hass.loop.run_until_complete(register_auth_provider(hass, { + 'type': 'homeassistant' + })) + + +async def test_onboarding_progress(hass, hass_storage, aiohttp_client): + """Test fetching progress.""" + mock_storage(hass_storage, { + 'done': ['hello'] + }) + + assert await async_setup_component(hass, 'onboarding', {}) + client = await aiohttp_client(hass.http.app) + + with patch.object(views, 'STEPS', ['hello', 'world']): + resp = await client.get('/api/onboarding') + + assert resp.status == 200 + data = await resp.json() + assert len(data) == 2 + assert data[0] == { + 'step': 'hello', + 'done': True + } + assert data[1] == { + 'step': 'world', + 'done': False + } + + +async def test_onboarding_user_already_done(hass, hass_storage, + aiohttp_client): + """Test creating a new user when user step already done.""" + mock_storage(hass_storage, { + 'done': [views.STEP_USER] + }) + + with patch.object(onboarding, 'STEPS', ['hello', 'world']): + assert await async_setup_component(hass, 'onboarding', {}) + + client = await aiohttp_client(hass.http.app) + + resp = await client.post('/api/onboarding/users', json={ + 'name': 'Test Name', + 'username': 'test-user', + 'password': 'test-pass', + }) + + assert resp.status == 403 + + +async def test_onboarding_user(hass, hass_storage, aiohttp_client): + """Test creating a new user.""" + mock_storage(hass_storage, { + 'done': ['hello'] + }) + + assert await async_setup_component(hass, 'onboarding', {}) + + client = await aiohttp_client(hass.http.app) + + resp = await client.post('/api/onboarding/users', json={ + 'name': 'Test Name', + 'username': 'test-user', + 'password': 'test-pass', + }) + + assert resp.status == 200 + users = await hass.auth.async_get_users() + assert len(users) == 1 + user = users[0] + assert user.name == 'Test Name' + assert len(user.credentials) == 1 + assert user.credentials[0].data['username'] == 'test-user' + + +async def test_onboarding_user_invalid_name(hass, hass_storage, + aiohttp_client): + """Test not providing name.""" + mock_storage(hass_storage, { + 'done': ['hello'] + }) + + assert await async_setup_component(hass, 'onboarding', {}) + + client = await aiohttp_client(hass.http.app) + + resp = await client.post('/api/onboarding/users', json={ + 'username': 'test-user', + 'password': 'test-pass', + }) + + assert resp.status == 400 + + +async def test_onboarding_user_race(hass, hass_storage, aiohttp_client): + """Test race condition on creating new user.""" + mock_storage(hass_storage, { + 'done': ['hello'] + }) + + assert await async_setup_component(hass, 'onboarding', {}) + + client = await aiohttp_client(hass.http.app) + + resp1 = client.post('/api/onboarding/users', json={ + 'name': 'Test 1', + 'username': '1-user', + 'password': '1-pass', + }) + resp2 = client.post('/api/onboarding/users', json={ + 'name': 'Test 2', + 'username': '2-user', + 'password': '2-pass', + }) + + res1, res2 = await asyncio.gather(resp1, resp2) + + assert sorted([res1.status, res2.status]) == [200, 403] From d2f4bce6c0b36a3b5903c51878637015c45f765e Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 17 Jul 2018 10:57:05 +0200 Subject: [PATCH 008/113] Bump frontend to 20180717.0 --- homeassistant/components/frontend/__init__.py | 2 +- homeassistant/components/onboarding/views.py | 1 + requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/frontend/__init__.py b/homeassistant/components/frontend/__init__.py index 958247cadc5f..141da89f3595 100644 --- a/homeassistant/components/frontend/__init__.py +++ b/homeassistant/components/frontend/__init__.py @@ -26,7 +26,7 @@ from homeassistant.helpers.translation import async_get_translations from homeassistant.loader import bind_hass from homeassistant.util.yaml import load_yaml -REQUIREMENTS = ['home-assistant-frontend==20180716.0'] +REQUIREMENTS = ['home-assistant-frontend==20180717.0'] DOMAIN = 'frontend' DEPENDENCIES = ['api', 'websocket_api', 'http', 'system_log', 'onboarding'] diff --git a/homeassistant/components/onboarding/views.py b/homeassistant/components/onboarding/views.py index 1a536a1bc43c..17d83003c489 100644 --- a/homeassistant/components/onboarding/views.py +++ b/homeassistant/components/onboarding/views.py @@ -92,6 +92,7 @@ class UserOnboardingView(_BaseOnboardingView): credentials = await provider.async_get_or_create_credentials({ 'username': data['username'] }) + await provider.data.async_save() await hass.auth.async_link_user(user, credentials) await self._async_mark_done(hass) diff --git a/requirements_all.txt b/requirements_all.txt index cea2ddf9be25..f524dc28a8b6 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -415,7 +415,7 @@ hole==0.3.0 holidays==0.9.5 # homeassistant.components.frontend -home-assistant-frontend==20180716.0 +home-assistant-frontend==20180717.0 # homeassistant.components.homekit_controller # homekit==0.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 08909b3bf6f0..ad3877392c7b 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -81,7 +81,7 @@ hbmqtt==0.9.2 holidays==0.9.5 # homeassistant.components.frontend -home-assistant-frontend==20180716.0 +home-assistant-frontend==20180717.0 # homeassistant.components.homematicip_cloud homematicip==0.9.8 From e31dd4404ed9167bb1ef209c93f80bd998c6e9bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Tue, 17 Jul 2018 20:34:29 +0300 Subject: [PATCH 009/113] Pylint 2 fixes (#15487) * pylint 2 inline disable syntax fixes * pylint 2 logging-not-lazy fixes * pylint 2 consider-using-in fixes * Revert pylint 2 inline disable syntax fixes addressing unused-imports Will have a go at removing more unused imports altogether first. --- .../components/binary_sensor/isy994.py | 2 +- homeassistant/components/camera/proxy.py | 2 +- homeassistant/components/camera/verisure.py | 3 +-- homeassistant/components/climate/daikin.py | 2 +- .../components/climate/radiotherm.py | 2 +- .../components/device_tracker/tomato.py | 2 +- .../components/device_tracker/ubus.py | 2 +- homeassistant/components/egardia.py | 4 ++-- homeassistant/components/fan/template.py | 22 +++++++------------ homeassistant/components/feedreader.py | 4 ++-- homeassistant/components/homekit/__init__.py | 2 +- .../components/homekit/type_sensors.py | 2 +- homeassistant/components/light/template.py | 6 ++--- .../components/media_player/apple_tv.py | 13 +++++------ .../components/media_player/bluesound.py | 4 ++-- .../components/media_player/pandora.py | 3 +-- .../components/media_player/samsungtv.py | 2 +- .../components/remote/xiaomi_miio.py | 4 ++-- homeassistant/components/sensor/arlo.py | 10 +++------ homeassistant/components/sensor/daikin.py | 2 +- .../components/sensor/fritzbox_callmonitor.py | 2 +- homeassistant/components/sensor/ios.py | 4 ++-- homeassistant/components/sensor/isy994.py | 3 +-- homeassistant/components/sensor/miflora.py | 2 +- homeassistant/components/sensor/mitemp_bt.py | 2 +- homeassistant/components/sensor/octoprint.py | 2 +- homeassistant/components/sensor/qnap.py | 2 +- .../components/sensor/synologydsm.py | 2 +- homeassistant/components/spc.py | 2 +- homeassistant/components/wink/__init__.py | 4 ++-- homeassistant/helpers/condition.py | 6 ++--- tests/components/emulated_hue/test_upnp.py | 2 +- tests/components/test_system_log.py | 2 +- 33 files changed, 55 insertions(+), 73 deletions(-) diff --git a/homeassistant/components/binary_sensor/isy994.py b/homeassistant/components/binary_sensor/isy994.py index deaa118f51cf..b6d582b77937 100644 --- a/homeassistant/components/binary_sensor/isy994.py +++ b/homeassistant/components/binary_sensor/isy994.py @@ -55,7 +55,7 @@ def setup_platform(hass, config: ConfigType, else: device_type = _detect_device_type(node) subnode_id = int(node.nid[-1]) - if (device_type == 'opening' or device_type == 'moisture'): + if device_type in ('opening', 'moisture'): # These sensors use an optional "negative" subnode 2 to snag # all state changes if subnode_id == 2: diff --git a/homeassistant/components/camera/proxy.py b/homeassistant/components/camera/proxy.py index 447f4e1e56a7..6fe891b6b241 100644 --- a/homeassistant/components/camera/proxy.py +++ b/homeassistant/components/camera/proxy.py @@ -69,7 +69,7 @@ def _resize_image(image, opts): img = Image.open(io.BytesIO(image)) imgfmt = str(img.format) - if imgfmt != 'PNG' and imgfmt != 'JPEG': + if imgfmt not in ('PNG', 'JPEG'): _LOGGER.debug("Image is of unsupported type: %s", imgfmt) return image diff --git a/homeassistant/components/camera/verisure.py b/homeassistant/components/camera/verisure.py index b637858303e7..554f877d0bd3 100644 --- a/homeassistant/components/camera/verisure.py +++ b/homeassistant/components/camera/verisure.py @@ -66,8 +66,7 @@ class VerisureSmartcam(Camera): if not image_ids: return new_image_id = image_ids[0] - if (new_image_id == '-1' or - self._image_id == new_image_id): + if new_image_id in ('-1', self._image_id): _LOGGER.debug("The image is the same, or loading image_id") return _LOGGER.debug("Download new image %s", new_image_id) diff --git a/homeassistant/components/climate/daikin.py b/homeassistant/components/climate/daikin.py index 2c49b25a39d9..50501025f0c2 100644 --- a/homeassistant/components/climate/daikin.py +++ b/homeassistant/components/climate/daikin.py @@ -145,7 +145,7 @@ class DaikinClimate(ClimateDevice): if value is None: _LOGGER.error("Invalid value requested for key %s", key) else: - if value == "-" or value == "--": + if value in ("-", "--"): value = None elif cast_to_float: try: diff --git a/homeassistant/components/climate/radiotherm.py b/homeassistant/components/climate/radiotherm.py index 032d85637ef5..b3043689f8cc 100644 --- a/homeassistant/components/climate/radiotherm.py +++ b/homeassistant/components/climate/radiotherm.py @@ -308,7 +308,7 @@ class RadioThermostat(ClimateDevice): def set_operation_mode(self, operation_mode): """Set operation mode (auto, cool, heat, off).""" - if operation_mode == STATE_OFF or operation_mode == STATE_AUTO: + if operation_mode in (STATE_OFF, STATE_AUTO): self.device.tmode = TEMP_MODE_TO_CODE[operation_mode] # Setting t_cool or t_heat automatically changes tmode. diff --git a/homeassistant/components/device_tracker/tomato.py b/homeassistant/components/device_tracker/tomato.py index 01ae2977f6d9..12e1cb0099ad 100644 --- a/homeassistant/components/device_tracker/tomato.py +++ b/homeassistant/components/device_tracker/tomato.py @@ -102,7 +102,7 @@ class TomatoDeviceScanner(DeviceScanner): for param, value in \ self.parse_api_pattern.findall(response.text): - if param == 'wldev' or param == 'dhcpd_lease': + if param in ('wldev', 'dhcpd_lease'): self.last_results[param] = \ json.loads(value.replace("'", '"')) return True diff --git a/homeassistant/components/device_tracker/ubus.py b/homeassistant/components/device_tracker/ubus.py index f265014657be..94e3b407d13a 100644 --- a/homeassistant/components/device_tracker/ubus.py +++ b/homeassistant/components/device_tracker/ubus.py @@ -56,7 +56,7 @@ def _refresh_on_access_denied(func): try: return func(self, *args, **kwargs) except PermissionError: - _LOGGER.warning("Invalid session detected." + + _LOGGER.warning("Invalid session detected." " Trying to refresh session_id and re-run RPC") self.session_id = _get_session_id( self.url, self.username, self.password) diff --git a/homeassistant/components/egardia.py b/homeassistant/components/egardia.py index f350ea56bb4a..b7da671bb158 100644 --- a/homeassistant/components/egardia.py +++ b/homeassistant/components/egardia.py @@ -81,7 +81,7 @@ def setup(hass, config): device = hass.data[EGARDIA_DEVICE] = egardiadevice.EgardiaDevice( host, port, username, password, '', version) except requests.exceptions.RequestException: - _LOGGER.error("An error occurred accessing your Egardia device. " + + _LOGGER.error("An error occurred accessing your Egardia device. " "Please check config.") return False except egardiadevice.UnauthorizedError: @@ -108,7 +108,7 @@ def setup(hass, config): hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, handle_stop_event) except IOError: - _LOGGER.error("Binding error occurred while starting " + + _LOGGER.error("Binding error occurred while starting " "EgardiaServer.") return False diff --git a/homeassistant/components/fan/template.py b/homeassistant/components/fan/template.py index a40437e719b9..e72d82eff087 100644 --- a/homeassistant/components/fan/template.py +++ b/homeassistant/components/fan/template.py @@ -250,8 +250,7 @@ class TemplateFan(FanEntity): await self._set_speed_script.async_run({ATTR_SPEED: speed}) else: _LOGGER.error( - 'Received invalid speed: %s. ' + - 'Expected: %s.', + 'Received invalid speed: %s. Expected: %s.', speed, self._speed_list) async def async_oscillate(self, oscillating: bool) -> None: @@ -265,8 +264,7 @@ class TemplateFan(FanEntity): {ATTR_OSCILLATING: oscillating}) else: _LOGGER.error( - 'Received invalid oscillating value: %s. ' + - 'Expected: %s.', + 'Received invalid oscillating value: %s. Expected: %s.', oscillating, ', '.join(_VALID_OSC)) async def async_set_direction(self, direction: str) -> None: @@ -280,8 +278,7 @@ class TemplateFan(FanEntity): {ATTR_DIRECTION: direction}) else: _LOGGER.error( - 'Received invalid direction: %s. ' + - 'Expected: %s.', + 'Received invalid direction: %s. Expected: %s.', direction, ', '.join(_VALID_DIRECTIONS)) async def async_added_to_hass(self): @@ -319,8 +316,7 @@ class TemplateFan(FanEntity): self._state = None else: _LOGGER.error( - 'Received invalid fan is_on state: %s. ' + - 'Expected: %s.', + 'Received invalid fan is_on state: %s. Expected: %s.', state, ', '.join(_VALID_STATES)) self._state = None @@ -340,8 +336,7 @@ class TemplateFan(FanEntity): self._speed = None else: _LOGGER.error( - 'Received invalid speed: %s. ' + - 'Expected: %s.', + 'Received invalid speed: %s. Expected: %s.', speed, self._speed_list) self._speed = None @@ -363,8 +358,8 @@ class TemplateFan(FanEntity): self._oscillating = None else: _LOGGER.error( - 'Received invalid oscillating: %s. ' + - 'Expected: True/False.', oscillating) + 'Received invalid oscillating: %s. Expected: True/False.', + oscillating) self._oscillating = None # Update direction if 'direction_template' is configured @@ -383,7 +378,6 @@ class TemplateFan(FanEntity): self._direction = None else: _LOGGER.error( - 'Received invalid direction: %s. ' + - 'Expected: %s.', + 'Received invalid direction: %s. Expected: %s.', direction, ', '.join(_VALID_DIRECTIONS)) self._direction = None diff --git a/homeassistant/components/feedreader.py b/homeassistant/components/feedreader.py index 73ab9e8123c5..56f153517cfb 100644 --- a/homeassistant/components/feedreader.py +++ b/homeassistant/components/feedreader.py @@ -189,7 +189,7 @@ class StoredData(object): with self._lock, open(self._data_file, 'rb') as myfile: self._data = pickle.load(myfile) or {} self._cache_outdated = False - except: # noqa: E722 # pylint: disable=bare-except + except: # noqa: E722 pylint: disable=bare-except _LOGGER.error("Error loading data from pickled file %s", self._data_file) @@ -207,7 +207,7 @@ class StoredData(object): feed_id, self._data_file) try: pickle.dump(self._data, myfile) - except: # noqa: E722 # pylint: disable=bare-except + except: # noqa: E722 pylint: disable=bare-except _LOGGER.error( "Error saving pickled data to %s", self._data_file) self._cache_outdated = True diff --git a/homeassistant/components/homekit/__init__.py b/homeassistant/components/homekit/__init__.py index cb9387fb2c0c..81f668809438 100644 --- a/homeassistant/components/homekit/__init__.py +++ b/homeassistant/components/homekit/__init__.py @@ -168,7 +168,7 @@ def get_accessory(hass, driver, state, aid, config): def generate_aid(entity_id): """Generate accessory aid with zlib adler32.""" aid = adler32(entity_id.encode('utf-8')) - if aid == 0 or aid == 1: + if aid in (0, 1): return None return aid diff --git a/homeassistant/components/homekit/type_sensors.py b/homeassistant/components/homekit/type_sensors.py index 373c1188f2d7..d4c2cb58209c 100644 --- a/homeassistant/components/homekit/type_sensors.py +++ b/homeassistant/components/homekit/type_sensors.py @@ -181,6 +181,6 @@ class BinarySensor(HomeAccessory): def update_state(self, new_state): """Update accessory after state change.""" state = new_state.state - detected = (state == STATE_ON) or (state == STATE_HOME) + detected = state in (STATE_ON, STATE_HOME) self.char_detected.set_value(detected) _LOGGER.debug('%s: Set to %d', self.entity_id, detected) diff --git a/homeassistant/components/light/template.py b/homeassistant/components/light/template.py index 38cac649a1a7..ad77b734fbb3 100644 --- a/homeassistant/components/light/template.py +++ b/homeassistant/components/light/template.py @@ -248,8 +248,7 @@ class LightTemplate(Light): self._state = state in ('true', STATE_ON) else: _LOGGER.error( - 'Received invalid light is_on state: %s. ' + - 'Expected: %s', + 'Received invalid light is_on state: %s. Expected: %s', state, ', '.join(_VALID_STATES)) self._state = None @@ -264,8 +263,7 @@ class LightTemplate(Light): self._brightness = int(brightness) else: _LOGGER.error( - 'Received invalid brightness : %s' + - 'Expected: 0-255', + 'Received invalid brightness : %s. Expected: 0-255', brightness) self._brightness = None diff --git a/homeassistant/components/media_player/apple_tv.py b/homeassistant/components/media_player/apple_tv.py index 37a50b39e950..97b9b64c7cb0 100644 --- a/homeassistant/components/media_player/apple_tv.py +++ b/homeassistant/components/media_player/apple_tv.py @@ -100,15 +100,14 @@ class AppleTvDevice(MediaPlayerDevice): if self._playing: from pyatv import const state = self._playing.play_state - if state == const.PLAY_STATE_IDLE or \ - state == const.PLAY_STATE_NO_MEDIA or \ - state == const.PLAY_STATE_LOADING: + if state in (const.PLAY_STATE_IDLE, const.PLAY_STATE_NO_MEDIA, + const.PLAY_STATE_LOADING): return STATE_IDLE elif state == const.PLAY_STATE_PLAYING: return STATE_PLAYING - elif state == const.PLAY_STATE_PAUSED or \ - state == const.PLAY_STATE_FAST_FORWARD or \ - state == const.PLAY_STATE_FAST_BACKWARD: + elif state in (const.PLAY_STATE_PAUSED, + const.PLAY_STATE_FAST_FORWARD, + const.PLAY_STATE_FAST_BACKWARD): # Catch fast forward/backward here so "play" is default action return STATE_PAUSED return STATE_STANDBY # Bad or unknown state? @@ -162,7 +161,7 @@ class AppleTvDevice(MediaPlayerDevice): def media_position_updated_at(self): """Last valid time of media position.""" state = self.state - if state == STATE_PLAYING or state == STATE_PAUSED: + if state in (STATE_PLAYING, STATE_PAUSED): return dt_util.utcnow() @asyncio.coroutine diff --git a/homeassistant/components/media_player/bluesound.py b/homeassistant/components/media_player/bluesound.py index 283c4af032e6..ec878e5c0437 100644 --- a/homeassistant/components/media_player/bluesound.py +++ b/homeassistant/components/media_player/bluesound.py @@ -528,9 +528,9 @@ class BluesoundPlayer(MediaPlayerDevice): return STATE_GROUPED status = self._status.get('state', None) - if status == 'pause' or status == 'stop': + if status in ('pause', 'stop'): return STATE_PAUSED - elif status == 'stream' or status == 'play': + elif status in ('stream', 'play'): return STATE_PLAYING return STATE_IDLE diff --git a/homeassistant/components/media_player/pandora.py b/homeassistant/components/media_player/pandora.py index 90638cd9dfce..30e307fd1172 100644 --- a/homeassistant/components/media_player/pandora.py +++ b/homeassistant/components/media_player/pandora.py @@ -294,8 +294,7 @@ class PandoraMediaPlayer(MediaPlayerDevice): time_remaining = int(cur_minutes) * 60 + int(cur_seconds) self._media_duration = int(total_minutes) * 60 + int(total_seconds) - if (time_remaining != self._time_remaining and - time_remaining != self._media_duration): + if time_remaining not in (self._time_remaining, self._media_duration): self._player_state = STATE_PLAYING elif self._player_state == STATE_PLAYING: self._player_state = STATE_PAUSED diff --git a/homeassistant/components/media_player/samsungtv.py b/homeassistant/components/media_player/samsungtv.py index c3de341d607f..55b3fb0ea4f5 100644 --- a/homeassistant/components/media_player/samsungtv.py +++ b/homeassistant/components/media_player/samsungtv.py @@ -153,7 +153,7 @@ class SamsungTVDevice(MediaPlayerDevice): def send_key(self, key): """Send a key to the tv and handles exceptions.""" if self._power_off_in_progress() \ - and not (key == 'KEY_POWER' or key == 'KEY_POWEROFF'): + and key not in ('KEY_POWER', 'KEY_POWEROFF'): _LOGGER.info("TV is powering off, not sending command: %s", key) return try: diff --git a/homeassistant/components/remote/xiaomi_miio.py b/homeassistant/components/remote/xiaomi_miio.py index 59a2dc861a62..eda09e3af644 100644 --- a/homeassistant/components/remote/xiaomi_miio.py +++ b/homeassistant/components/remote/xiaomi_miio.py @@ -232,13 +232,13 @@ class XiaomiMiioRemote(RemoteDevice): @asyncio.coroutine def async_turn_on(self, **kwargs): """Turn the device on.""" - _LOGGER.error("Device does not support turn_on, " + + _LOGGER.error("Device does not support turn_on, " "please use 'remote.send_command' to send commands.") @asyncio.coroutine def async_turn_off(self, **kwargs): """Turn the device off.""" - _LOGGER.error("Device does not support turn_off, " + + _LOGGER.error("Device does not support turn_off, " "please use 'remote.send_command' to send commands.") def _send_command(self, payload): diff --git a/homeassistant/components/sensor/arlo.py b/homeassistant/components/sensor/arlo.py index 609887e96902..eeb0c966ab44 100644 --- a/homeassistant/components/sensor/arlo.py +++ b/homeassistant/components/sensor/arlo.py @@ -56,9 +56,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): SENSOR_TYPES[sensor_type][0], arlo, sensor_type)) else: for camera in arlo.cameras: - if sensor_type == 'temperature' or \ - sensor_type == 'humidity' or \ - sensor_type == 'air_quality': + if sensor_type in ('temperature', 'humidity', 'air_quality'): continue name = '{0} {1}'.format( @@ -66,10 +64,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None): sensors.append(ArloSensor(name, camera, sensor_type)) for base_station in arlo.base_stations: - if ((sensor_type == 'temperature' or - sensor_type == 'humidity' or - sensor_type == 'air_quality') and - base_station.model_id == 'ABC1000'): + if sensor_type in ('temperature', 'humidity', 'air_quality') \ + and base_station.model_id == 'ABC1000': name = '{0} {1}'.format( SENSOR_TYPES[sensor_type][0], base_station.name) sensors.append(ArloSensor(name, base_station, sensor_type)) diff --git a/homeassistant/components/sensor/daikin.py b/homeassistant/components/sensor/daikin.py index e045043e09c7..2da5cb5cdf04 100644 --- a/homeassistant/components/sensor/daikin.py +++ b/homeassistant/components/sensor/daikin.py @@ -85,7 +85,7 @@ class DaikinClimateSensor(Entity): if value is None: _LOGGER.warning("Invalid value requested for key %s", key) else: - if value == "-" or value == "--": + if value in ("-", "--"): value = None elif cast_to_float: try: diff --git a/homeassistant/components/sensor/fritzbox_callmonitor.py b/homeassistant/components/sensor/fritzbox_callmonitor.py index b443bd56f03d..304489f99b76 100644 --- a/homeassistant/components/sensor/fritzbox_callmonitor.py +++ b/homeassistant/components/sensor/fritzbox_callmonitor.py @@ -70,7 +70,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): phonebook = FritzBoxPhonebook( host=host, port=port, username=username, password=password, phonebook_id=phonebook_id, prefixes=prefixes) - except: # noqa: E722 # pylint: disable=bare-except + except: # noqa: E722 pylint: disable=bare-except phonebook = None _LOGGER.warning("Phonebook with ID %s not found on Fritz!Box", phonebook_id) diff --git a/homeassistant/components/sensor/ios.py b/homeassistant/components/sensor/ios.py index 398c0b350ee3..1fd556b17c0c 100644 --- a/homeassistant/components/sensor/ios.py +++ b/homeassistant/components/sensor/ios.py @@ -86,8 +86,8 @@ class IOSSensor(Entity): battery_level = device_battery[ios.ATTR_BATTERY_LEVEL] charging = True icon_state = DEFAULT_ICON_STATE - if (battery_state == ios.ATTR_BATTERY_STATE_FULL or - battery_state == ios.ATTR_BATTERY_STATE_UNPLUGGED): + if battery_state in (ios.ATTR_BATTERY_STATE_FULL, + ios.ATTR_BATTERY_STATE_UNPLUGGED): charging = False icon_state = "{}-off".format(DEFAULT_ICON_STATE) elif battery_state == ios.ATTR_BATTERY_STATE_UNKNOWN: diff --git a/homeassistant/components/sensor/isy994.py b/homeassistant/components/sensor/isy994.py index 1048c04d43dc..3eabce9458c9 100644 --- a/homeassistant/components/sensor/isy994.py +++ b/homeassistant/components/sensor/isy994.py @@ -259,8 +259,7 @@ class ISYSensorDevice(ISYDevice): if len(self._node.uom) == 1: if self._node.uom[0] in UOM_FRIENDLY_NAME: friendly_name = UOM_FRIENDLY_NAME.get(self._node.uom[0]) - if friendly_name == TEMP_CELSIUS or \ - friendly_name == TEMP_FAHRENHEIT: + if friendly_name in (TEMP_CELSIUS, TEMP_FAHRENHEIT): friendly_name = self.hass.config.units.temperature_unit return friendly_name else: diff --git a/homeassistant/components/sensor/miflora.py b/homeassistant/components/sensor/miflora.py index f1f8adab062e..6f50a57b3ab0 100644 --- a/homeassistant/components/sensor/miflora.py +++ b/homeassistant/components/sensor/miflora.py @@ -62,7 +62,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): """Set up the MiFlora sensor.""" from miflora import miflora_poller try: - import bluepy.btle # noqa: F401 # pylint: disable=unused-variable + import bluepy.btle # noqa: F401 pylint: disable=unused-variable from btlewrap import BluepyBackend backend = BluepyBackend except ImportError: diff --git a/homeassistant/components/sensor/mitemp_bt.py b/homeassistant/components/sensor/mitemp_bt.py index 3628765293b4..249a69578db9 100644 --- a/homeassistant/components/sensor/mitemp_bt.py +++ b/homeassistant/components/sensor/mitemp_bt.py @@ -60,7 +60,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): """Set up the MiTempBt sensor.""" from mitemp_bt import mitemp_bt_poller try: - import bluepy.btle # noqa: F401 # pylint: disable=unused-variable + import bluepy.btle # noqa: F401 pylint: disable=unused-variable from btlewrap import BluepyBackend backend = BluepyBackend except ImportError: diff --git a/homeassistant/components/sensor/octoprint.py b/homeassistant/components/sensor/octoprint.py index 20d00267deef..df181c67c033 100644 --- a/homeassistant/components/sensor/octoprint.py +++ b/homeassistant/components/sensor/octoprint.py @@ -107,7 +107,7 @@ class OctoPrintSensor(Entity): def state(self): """Return the state of the sensor.""" sensor_unit = self.unit_of_measurement - if sensor_unit == TEMP_CELSIUS or sensor_unit == "%": + if sensor_unit in (TEMP_CELSIUS, "%"): # API sometimes returns null and not 0 if self._state is None: self._state = 0 diff --git a/homeassistant/components/sensor/qnap.py b/homeassistant/components/sensor/qnap.py index 3d9704875c9c..97cf948ff9fc 100644 --- a/homeassistant/components/sensor/qnap.py +++ b/homeassistant/components/sensor/qnap.py @@ -192,7 +192,7 @@ class QNAPStatsAPI(object): self.data["smart_drive_health"] = self._api.get_smart_disk_health() self.data["volumes"] = self._api.get_volumes() self.data["bandwidth"] = self._api.get_bandwidth() - except: # noqa: E722 # pylint: disable=bare-except + except: # noqa: E722 pylint: disable=bare-except _LOGGER.exception("Failed to fetch QNAP stats from the NAS") diff --git a/homeassistant/components/sensor/synologydsm.py b/homeassistant/components/sensor/synologydsm.py index e3c3a0cf5caa..e70dd482f1e4 100644 --- a/homeassistant/components/sensor/synologydsm.py +++ b/homeassistant/components/sensor/synologydsm.py @@ -140,7 +140,7 @@ class SynoApi(object): try: self._api = SynologyDSM(host, port, username, password, use_https=use_ssl) - except: # noqa: E722 # pylint: disable=bare-except + except: # noqa: E722 pylint: disable=bare-except _LOGGER.error("Error setting up Synology DSM") # Will be updated when update() gets called. diff --git a/homeassistant/components/spc.py b/homeassistant/components/spc.py index 9742bc25c634..52d4165f3faf 100644 --- a/homeassistant/components/spc.py +++ b/homeassistant/components/spc.py @@ -151,7 +151,7 @@ def _ws_process_message(message, async_callback, *args): "Unsuccessful websocket message delivered, ignoring: %s", message) try: yield from async_callback(message['data']['sia'], *args) - except: # noqa: E722 # pylint: disable=bare-except + except: # noqa: E722 pylint: disable=bare-except _LOGGER.exception("Exception in callback, ignoring") diff --git a/homeassistant/components/wink/__init__.py b/homeassistant/components/wink/__init__.py index 7c171d749672..a8acc4375466 100644 --- a/homeassistant/components/wink/__init__.py +++ b/homeassistant/components/wink/__init__.py @@ -452,7 +452,7 @@ def setup(hass, config): _man = siren.wink.device_manufacturer() if (service.service != SERVICE_SET_AUTO_SHUTOFF and service.service != SERVICE_ENABLE_SIREN and - (_man != 'dome' and _man != 'wink')): + _man not in ('dome', 'wink')): _LOGGER.error("Service only valid for Dome or Wink sirens") return @@ -487,7 +487,7 @@ def setup(hass, config): has_dome_or_wink_siren = False for siren in pywink.get_sirens(): _man = siren.device_manufacturer() - if _man == "dome" or _man == "wink": + if _man in ("dome", "wink"): has_dome_or_wink_siren = True _id = siren.object_id() + siren.name() if _id not in hass.data[DOMAIN]['unique_ids']: diff --git a/homeassistant/helpers/condition.py b/homeassistant/helpers/condition.py index 921b3bcf06b5..504c9d4b067b 100644 --- a/homeassistant/helpers/condition.py +++ b/homeassistant/helpers/condition.py @@ -246,13 +246,11 @@ def sun(hass, before=None, after=None, before_offset=None, after_offset=None): sunrise = get_astral_event_date(hass, 'sunrise', today) sunset = get_astral_event_date(hass, 'sunset', today) - if sunrise is None and (before == SUN_EVENT_SUNRISE or - after == SUN_EVENT_SUNRISE): + if sunrise is None and SUN_EVENT_SUNRISE in (before, after): # There is no sunrise today return False - if sunset is None and (before == SUN_EVENT_SUNSET or - after == SUN_EVENT_SUNSET): + if sunset is None and SUN_EVENT_SUNSET in (before, after): # There is no sunset today return False diff --git a/tests/components/emulated_hue/test_upnp.py b/tests/components/emulated_hue/test_upnp.py index 555802f9a2c7..8315de34e061 100644 --- a/tests/components/emulated_hue/test_upnp.py +++ b/tests/components/emulated_hue/test_upnp.py @@ -89,7 +89,7 @@ class TestEmulatedHue(unittest.TestCase): # Make sure the XML is parsable try: ET.fromstring(result.text) - except: # noqa: E722 # pylint: disable=bare-except + except: # noqa: E722 pylint: disable=bare-except self.fail('description.xml is not valid XML!') def test_create_username(self): diff --git a/tests/components/test_system_log.py b/tests/components/test_system_log.py index 59e99e5c1b5c..5d48fd881273 100644 --- a/tests/components/test_system_log.py +++ b/tests/components/test_system_log.py @@ -28,7 +28,7 @@ async def get_error_log(hass, aiohttp_client, expected_count): def _generate_and_log_exception(exception, log): try: raise Exception(exception) - except: # noqa: E722 # pylint: disable=bare-except + except: # noqa: E722 pylint: disable=bare-except _LOGGER.exception(log) From 2022d39339a2c1df9bef4be11635d374f736aa62 Mon Sep 17 00:00:00 2001 From: Jason Hu Date: Tue, 17 Jul 2018 10:36:33 -0700 Subject: [PATCH 010/113] Disallow use insecure_example auth provider in configuration.yml (#15504) * Disallow use insecure_example auth provider in configuration.yml * Add unit test for auth provider config validate --- homeassistant/config.py | 9 +++++++-- tests/test_config.py | 44 +++++++++++++++++++++++++++++++++++++++-- 2 files changed, 49 insertions(+), 4 deletions(-) diff --git a/homeassistant/config.py b/homeassistant/config.py index 2afa943ee506..d9206d622507 100644 --- a/homeassistant/config.py +++ b/homeassistant/config.py @@ -20,7 +20,7 @@ from homeassistant.const import ( CONF_TIME_ZONE, CONF_ELEVATION, CONF_UNIT_SYSTEM_METRIC, CONF_UNIT_SYSTEM_IMPERIAL, CONF_TEMPERATURE_UNIT, TEMP_CELSIUS, __version__, CONF_CUSTOMIZE, CONF_CUSTOMIZE_DOMAIN, CONF_CUSTOMIZE_GLOB, - CONF_WHITELIST_EXTERNAL_DIRS, CONF_AUTH_PROVIDERS) + CONF_WHITELIST_EXTERNAL_DIRS, CONF_AUTH_PROVIDERS, CONF_TYPE) from homeassistant.core import callback, DOMAIN as CONF_CORE from homeassistant.exceptions import HomeAssistantError from homeassistant.loader import get_component, get_platform @@ -160,7 +160,12 @@ CORE_CONFIG_SCHEMA = CUSTOMIZE_CONFIG_SCHEMA.extend({ vol.All(cv.ensure_list, [vol.IsDir()]), vol.Optional(CONF_PACKAGES, default={}): PACKAGES_CONFIG_SCHEMA, vol.Optional(CONF_AUTH_PROVIDERS): - vol.All(cv.ensure_list, [auth_providers.AUTH_PROVIDER_SCHEMA]) + vol.All(cv.ensure_list, + [auth_providers.AUTH_PROVIDER_SCHEMA.extend({ + CONF_TYPE: vol.NotIn(['insecure_example'], + 'The insecure_example auth provider' + ' is for testing only.') + })]) }) diff --git a/tests/test_config.py b/tests/test_config.py index 717a3f62ec9a..435d3a00ec21 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -7,7 +7,7 @@ import unittest.mock as mock from collections import OrderedDict import pytest -from voluptuous import MultipleInvalid +from voluptuous import MultipleInvalid, Invalid from homeassistant.core import DOMAIN, HomeAssistantError, Config import homeassistant.config as config_util @@ -15,7 +15,8 @@ from homeassistant.const import ( ATTR_FRIENDLY_NAME, ATTR_HIDDEN, ATTR_ASSUMED_STATE, CONF_LATITUDE, CONF_LONGITUDE, CONF_UNIT_SYSTEM, CONF_NAME, CONF_TIME_ZONE, CONF_ELEVATION, CONF_CUSTOMIZE, __version__, - CONF_UNIT_SYSTEM_METRIC, CONF_UNIT_SYSTEM_IMPERIAL, CONF_TEMPERATURE_UNIT) + CONF_UNIT_SYSTEM_METRIC, CONF_UNIT_SYSTEM_IMPERIAL, CONF_TEMPERATURE_UNIT, + CONF_AUTH_PROVIDERS) from homeassistant.util import location as location_util, dt as dt_util from homeassistant.util.yaml import SECRET_YAML from homeassistant.util.async_ import run_coroutine_threadsafe @@ -790,3 +791,42 @@ def test_merge_customize(hass): assert hass.data[config_util.DATA_CUSTOMIZE].get('b.b') == \ {'friendly_name': 'BB'} + + +async def test_auth_provider_config(hass): + """Test loading auth provider config onto hass object.""" + core_config = { + 'latitude': 60, + 'longitude': 50, + 'elevation': 25, + 'name': 'Huis', + CONF_UNIT_SYSTEM: CONF_UNIT_SYSTEM_IMPERIAL, + 'time_zone': 'GMT', + CONF_AUTH_PROVIDERS: [ + {'type': 'homeassistant'}, + {'type': 'legacy_api_password'}, + ] + } + if hasattr(hass, 'auth'): + del hass.auth + await config_util.async_process_ha_core_config(hass, core_config) + + assert len(hass.auth.auth_providers) == 2 + assert hass.auth.active is True + + +async def test_disallowed_auth_provider_config(hass): + """Test loading insecure example auth provider is disallowed.""" + core_config = { + 'latitude': 60, + 'longitude': 50, + 'elevation': 25, + 'name': 'Huis', + CONF_UNIT_SYSTEM: CONF_UNIT_SYSTEM_IMPERIAL, + 'time_zone': 'GMT', + CONF_AUTH_PROVIDERS: [ + {'type': 'insecure_example'}, + ] + } + with pytest.raises(Invalid): + await config_util.async_process_ha_core_config(hass, core_config) From 9292d9255cce3095ff22c096a37d4bdc27db03a5 Mon Sep 17 00:00:00 2001 From: huangyupeng Date: Wed, 18 Jul 2018 02:33:54 +0800 Subject: [PATCH 011/113] Add Tuya climate platform (#15500) * Add Tuya climate platform * fix as review required * fix as review required --- homeassistant/components/climate/tuya.py | 173 +++++++++++++++++++++++ homeassistant/components/tuya.py | 1 + 2 files changed, 174 insertions(+) create mode 100644 homeassistant/components/climate/tuya.py diff --git a/homeassistant/components/climate/tuya.py b/homeassistant/components/climate/tuya.py new file mode 100644 index 000000000000..9a114c243b68 --- /dev/null +++ b/homeassistant/components/climate/tuya.py @@ -0,0 +1,173 @@ +""" +Support for the Tuya climate devices. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/climate.tuya/ +""" + +from homeassistant.components.climate import ( + ATTR_TEMPERATURE, ENTITY_ID_FORMAT, STATE_AUTO, STATE_COOL, STATE_ECO, + STATE_ELECTRIC, STATE_FAN_ONLY, STATE_GAS, STATE_HEAT, STATE_HEAT_PUMP, + STATE_HIGH_DEMAND, STATE_PERFORMANCE, SUPPORT_FAN_MODE, SUPPORT_ON_OFF, + SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE, ClimateDevice) +from homeassistant.components.fan import SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH +from homeassistant.components.tuya import DATA_TUYA, TuyaDevice + +from homeassistant.const import ( + PRECISION_WHOLE, TEMP_CELSIUS, TEMP_FAHRENHEIT) + +DEPENDENCIES = ['tuya'] +DEVICE_TYPE = 'climate' + +HA_STATE_TO_TUYA = { + STATE_AUTO: 'auto', + STATE_COOL: 'cold', + STATE_ECO: 'eco', + STATE_ELECTRIC: 'electric', + STATE_FAN_ONLY: 'wind', + STATE_GAS: 'gas', + STATE_HEAT: 'hot', + STATE_HEAT_PUMP: 'heat_pump', + STATE_HIGH_DEMAND: 'high_demand', + STATE_PERFORMANCE: 'performance', +} + +TUYA_STATE_TO_HA = {value: key for key, value in HA_STATE_TO_TUYA.items()} + +FAN_MODES = {SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH} + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Set up Tuya Climate devices.""" + if discovery_info is None: + return + tuya = hass.data[DATA_TUYA] + dev_ids = discovery_info.get('dev_ids') + devices = [] + for dev_id in dev_ids: + device = tuya.get_device_by_id(dev_id) + if device is None: + continue + devices.append(TuyaClimateDevice(device)) + add_devices(devices) + + +class TuyaClimateDevice(TuyaDevice, ClimateDevice): + """Tuya climate devices,include air conditioner,heater.""" + + def __init__(self, tuya): + """Init climate device.""" + super().__init__(tuya) + self.entity_id = ENTITY_ID_FORMAT.format(tuya.object_id()) + self.operations = [] + + async def async_added_to_hass(self): + """Create operation list when add to hass.""" + await super().async_added_to_hass() + modes = self.tuya.operation_list() + if modes is None: + return + for mode in modes: + if mode in TUYA_STATE_TO_HA: + self.operations.append(TUYA_STATE_TO_HA[mode]) + + @property + def is_on(self): + """Return true if climate is on.""" + return self.tuya.state() + + @property + def precision(self): + """Return the precision of the system.""" + return PRECISION_WHOLE + + @property + def temperature_unit(self): + """Return the unit of measurement used by the platform.""" + unit = self.tuya.temperature_unit() + if unit == 'CELSIUS': + return TEMP_CELSIUS + elif unit == 'FAHRENHEIT': + return TEMP_FAHRENHEIT + return TEMP_CELSIUS + + @property + def current_operation(self): + """Return current operation ie. heat, cool, idle.""" + mode = self.tuya.current_operation() + if mode is None: + return None + return TUYA_STATE_TO_HA.get(mode) + + @property + def operation_list(self): + """Return the list of available operation modes.""" + return self.operations + + @property + def current_temperature(self): + """Return the current temperature.""" + return self.tuya.current_temperature() + + @property + def target_temperature(self): + """Return the temperature we try to reach.""" + return self.tuya.target_temperature() + + @property + def target_temperature_step(self): + """Return the supported step of target temperature.""" + return self.tuya.target_temperature_step() + + @property + def current_fan_mode(self): + """Return the fan setting.""" + return self.tuya.current_fan_mode() + + @property + def fan_list(self): + """Return the list of available fan modes.""" + return self.tuya.fan_list() + + def set_temperature(self, **kwargs): + """Set new target temperature.""" + if ATTR_TEMPERATURE in kwargs: + self.tuya.set_temperature(kwargs[ATTR_TEMPERATURE]) + + def set_fan_mode(self, fan_mode): + """Set new target fan mode.""" + self.tuya.set_fan_mode(fan_mode) + + def set_operation_mode(self, operation_mode): + """Set new target operation mode.""" + self.tuya.set_operation_mode(HA_STATE_TO_TUYA.get(operation_mode)) + + def turn_on(self): + """Turn device on.""" + self.tuya.turn_on() + + def turn_off(self): + """Turn device off.""" + self.tuya.turn_off() + + @property + def supported_features(self): + """Return the list of supported features.""" + supports = SUPPORT_ON_OFF + if self.tuya.support_target_temperature(): + supports = supports | SUPPORT_TARGET_TEMPERATURE + if self.tuya.support_mode(): + supports = supports | SUPPORT_OPERATION_MODE + if self.tuya.support_wind_speed(): + supports = supports | SUPPORT_FAN_MODE + return supports + + @property + def min_temp(self): + """Return the minimum temperature.""" + return self.tuya.min_temp() + + @property + def max_temp(self): + """Return the maximum temperature.""" + return self.tuya.max_temp() diff --git a/homeassistant/components/tuya.py b/homeassistant/components/tuya.py index c557774b5f1c..ccb5227aead4 100644 --- a/homeassistant/components/tuya.py +++ b/homeassistant/components/tuya.py @@ -33,6 +33,7 @@ SERVICE_FORCE_UPDATE = 'force_update' SERVICE_PULL_DEVICES = 'pull_devices' TUYA_TYPE_TO_HA = { + 'climate': 'climate', 'light': 'light', 'switch': 'switch', } From 4ab502a691b99a79097100e573587e5cb0f25151 Mon Sep 17 00:00:00 2001 From: Dario Iacampo Date: Tue, 17 Jul 2018 22:47:32 +0200 Subject: [PATCH 012/113] Support latest tplink Archer D9 Firmware version / Device Scanner (#15356) * Support latest tplink Archer D9 Firmware version / Device Scanner * tplink integration on pypi package * initialize the client only once * remove unnecessary instance attributes --- .../components/device_tracker/tplink.py | 70 ++++++++++++++++--- requirements_all.txt | 3 + 2 files changed, 65 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/device_tracker/tplink.py b/homeassistant/components/device_tracker/tplink.py index 5266b9c6f574..346f381db34b 100644 --- a/homeassistant/components/device_tracker/tplink.py +++ b/homeassistant/components/device_tracker/tplink.py @@ -22,6 +22,8 @@ from homeassistant.const import ( CONF_HOST, CONF_PASSWORD, CONF_USERNAME, HTTP_HEADER_X_REQUESTED_WITH) import homeassistant.helpers.config_validation as cv +REQUIREMENTS = ['tplink==0.2.1'] + _LOGGER = logging.getLogger(__name__) HTTP_HEADER_NO_CACHE = 'no-cache' @@ -34,10 +36,22 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def get_scanner(hass, config): - """Validate the configuration and return a TP-Link scanner.""" - for cls in [Tplink5DeviceScanner, Tplink4DeviceScanner, - Tplink3DeviceScanner, Tplink2DeviceScanner, - TplinkDeviceScanner]: + """ + Validate the configuration and return a TP-Link scanner. + + The default way of integrating devices is to use a pypi + + package, The TplinkDeviceScanner has been refactored + + to depend on a pypi package, the other implementations + + should be gradually migrated in the pypi package + + """ + for cls in [ + TplinkDeviceScanner, Tplink5DeviceScanner, Tplink4DeviceScanner, + Tplink3DeviceScanner, Tplink2DeviceScanner, Tplink1DeviceScanner + ]: scanner = cls(config[DOMAIN]) if scanner.success_init: return scanner @@ -46,6 +60,46 @@ def get_scanner(hass, config): class TplinkDeviceScanner(DeviceScanner): + """Queries the router for connected devices.""" + + def __init__(self, config): + """Initialize the scanner.""" + from tplink.tplink import TpLinkClient + host = config[CONF_HOST] + password = config[CONF_PASSWORD] + username = config[CONF_USERNAME] + + self.tplink_client = TpLinkClient( + password, host=host, username=username) + + self.last_results = {} + self.success_init = self._update_info() + + def scan_devices(self): + """Scan for new devices and return a list with found device IDs.""" + self._update_info() + return self.last_results.keys() + + def get_device_name(self, device): + """Get the name of the device.""" + return self.last_results.get(device) + + def _update_info(self): + """Ensure the information from the TP-Link router is up to date. + + Return boolean if scanning successful. + """ + _LOGGER.info("Loading wireless clients...") + result = self.tplink_client.get_connected_devices() + + if result: + self.last_results = result + return True + + return False + + +class Tplink1DeviceScanner(DeviceScanner): """This class queries a wireless router running TP-Link firmware.""" def __init__(self, config): @@ -94,7 +148,7 @@ class TplinkDeviceScanner(DeviceScanner): return False -class Tplink2DeviceScanner(TplinkDeviceScanner): +class Tplink2DeviceScanner(Tplink1DeviceScanner): """This class queries a router with newer version of TP-Link firmware.""" def scan_devices(self): @@ -147,7 +201,7 @@ class Tplink2DeviceScanner(TplinkDeviceScanner): return False -class Tplink3DeviceScanner(TplinkDeviceScanner): +class Tplink3DeviceScanner(Tplink1DeviceScanner): """This class queries the Archer C9 router with version 150811 or high.""" def __init__(self, config): @@ -256,7 +310,7 @@ class Tplink3DeviceScanner(TplinkDeviceScanner): self.sysauth = '' -class Tplink4DeviceScanner(TplinkDeviceScanner): +class Tplink4DeviceScanner(Tplink1DeviceScanner): """This class queries an Archer C7 router with TP-Link firmware 150427.""" def __init__(self, config): @@ -337,7 +391,7 @@ class Tplink4DeviceScanner(TplinkDeviceScanner): return True -class Tplink5DeviceScanner(TplinkDeviceScanner): +class Tplink5DeviceScanner(Tplink1DeviceScanner): """This class queries a TP-Link EAP-225 AP with newer TP-Link FW.""" def scan_devices(self): diff --git a/requirements_all.txt b/requirements_all.txt index f524dc28a8b6..a39a2cada31f 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1340,6 +1340,9 @@ toonlib==1.0.2 # homeassistant.components.alarm_control_panel.totalconnect total_connect_client==0.18 +# homeassistant.components.device_tracker.tplink +tplink==0.2.1 + # homeassistant.components.sensor.transmission # homeassistant.components.switch.transmission transmissionrpc==0.11 From 7d7c2104ead967a186ea9d7408e9f6399883809f Mon Sep 17 00:00:00 2001 From: lich Date: Wed, 18 Jul 2018 04:58:30 +0800 Subject: [PATCH 013/113] Customizable command timeout (#15442) * Customizable command timeout * Change string to int * update the tests. Do the same thing on the binary_sensor.command_line. --- .../components/binary_sensor/command_line.py | 8 +++++++- homeassistant/components/sensor/command_line.py | 13 ++++++++++--- tests/components/binary_sensor/test_command_line.py | 8 +++++--- tests/components/sensor/test_command_line.py | 9 +++++---- 4 files changed, 27 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/binary_sensor/command_line.py b/homeassistant/components/binary_sensor/command_line.py index 480786b2c2c8..c2045c2df5e0 100644 --- a/homeassistant/components/binary_sensor/command_line.py +++ b/homeassistant/components/binary_sensor/command_line.py @@ -25,6 +25,9 @@ DEFAULT_PAYLOAD_OFF = 'OFF' SCAN_INTERVAL = timedelta(seconds=60) +CONF_COMMAND_TIMEOUT = 'command_timeout' +DEFAULT_TIMEOUT = 15 + PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_COMMAND): cv.string, vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, @@ -32,6 +35,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Optional(CONF_PAYLOAD_ON, default=DEFAULT_PAYLOAD_ON): cv.string, vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA, vol.Optional(CONF_VALUE_TEMPLATE): cv.template, + vol.Optional( + CONF_COMMAND_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int, }) @@ -43,9 +48,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None): payload_on = config.get(CONF_PAYLOAD_ON) device_class = config.get(CONF_DEVICE_CLASS) value_template = config.get(CONF_VALUE_TEMPLATE) + command_timeout = config.get(CONF_COMMAND_TIMEOUT) if value_template is not None: value_template.hass = hass - data = CommandSensorData(hass, command) + data = CommandSensorData(hass, command, command_timeout) add_devices([CommandBinarySensor( hass, data, name, device_class, payload_on, payload_off, diff --git a/homeassistant/components/sensor/command_line.py b/homeassistant/components/sensor/command_line.py index 4a26a1dc9fc7..2003b1b41c94 100644 --- a/homeassistant/components/sensor/command_line.py +++ b/homeassistant/components/sensor/command_line.py @@ -27,11 +27,16 @@ DEFAULT_NAME = 'Command Sensor' SCAN_INTERVAL = timedelta(seconds=60) +CONF_COMMAND_TIMEOUT = 'command_timeout' +DEFAULT_TIMEOUT = 15 + PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_COMMAND): cv.string, vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, vol.Optional(CONF_UNIT_OF_MEASUREMENT): cv.string, vol.Optional(CONF_VALUE_TEMPLATE): cv.template, + vol.Optional( + CONF_COMMAND_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int, }) @@ -41,9 +46,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None): command = config.get(CONF_COMMAND) unit = config.get(CONF_UNIT_OF_MEASUREMENT) value_template = config.get(CONF_VALUE_TEMPLATE) + command_timeout = config.get(CONF_COMMAND_TIMEOUT) if value_template is not None: value_template.hass = hass - data = CommandSensorData(hass, command) + data = CommandSensorData(hass, command, command_timeout) add_devices([CommandSensor(hass, data, name, unit, value_template)], True) @@ -92,11 +98,12 @@ class CommandSensor(Entity): class CommandSensorData(object): """The class for handling the data retrieval.""" - def __init__(self, hass, command): + def __init__(self, hass, command, command_timeout): """Initialize the data object.""" self.value = None self.hass = hass self.command = command + self.timeout = command_timeout def update(self): """Get the latest data with a shell command.""" @@ -135,7 +142,7 @@ class CommandSensorData(object): try: _LOGGER.info("Running command: %s", command) return_value = subprocess.check_output( - command, shell=shell, timeout=15) + command, shell=shell, timeout=self.timeout) self.value = return_value.strip().decode('utf-8') except subprocess.CalledProcessError: _LOGGER.error("Command failed: %s", command) diff --git a/tests/components/binary_sensor/test_command_line.py b/tests/components/binary_sensor/test_command_line.py index d01b62e4c12c..07389c7c8a9c 100644 --- a/tests/components/binary_sensor/test_command_line.py +++ b/tests/components/binary_sensor/test_command_line.py @@ -24,7 +24,9 @@ class TestCommandSensorBinarySensor(unittest.TestCase): config = {'name': 'Test', 'command': 'echo 1', 'payload_on': '1', - 'payload_off': '0'} + 'payload_off': '0', + 'command_timeout': 15 + } devices = [] @@ -43,7 +45,7 @@ class TestCommandSensorBinarySensor(unittest.TestCase): def test_template(self): """Test setting the state with a template.""" - data = command_line.CommandSensorData(self.hass, 'echo 10') + data = command_line.CommandSensorData(self.hass, 'echo 10', 15) entity = command_line.CommandBinarySensor( self.hass, data, 'test', None, '1.0', '0', @@ -53,7 +55,7 @@ class TestCommandSensorBinarySensor(unittest.TestCase): def test_sensor_off(self): """Test setting the state with a template.""" - data = command_line.CommandSensorData(self.hass, 'echo 0') + data = command_line.CommandSensorData(self.hass, 'echo 0', 15) entity = command_line.CommandBinarySensor( self.hass, data, 'test', None, '1', '0', None) diff --git a/tests/components/sensor/test_command_line.py b/tests/components/sensor/test_command_line.py index bc073a04c476..3104ce897a1c 100644 --- a/tests/components/sensor/test_command_line.py +++ b/tests/components/sensor/test_command_line.py @@ -21,7 +21,8 @@ class TestCommandSensorSensor(unittest.TestCase): """Test sensor setup.""" config = {'name': 'Test', 'unit_of_measurement': 'in', - 'command': 'echo 5' + 'command': 'echo 5', + 'command_timeout': 15 } devices = [] @@ -41,7 +42,7 @@ class TestCommandSensorSensor(unittest.TestCase): def test_template(self): """Test command sensor with template.""" - data = command_line.CommandSensorData(self.hass, 'echo 50') + data = command_line.CommandSensorData(self.hass, 'echo 50', 15) entity = command_line.CommandSensor( self.hass, data, 'test', 'in', @@ -55,7 +56,7 @@ class TestCommandSensorSensor(unittest.TestCase): self.hass.states.set('sensor.test_state', 'Works') data = command_line.CommandSensorData( self.hass, - 'echo {{ states.sensor.test_state.state }}' + 'echo {{ states.sensor.test_state.state }}', 15 ) data.update() @@ -63,7 +64,7 @@ class TestCommandSensorSensor(unittest.TestCase): def test_bad_command(self): """Test bad command.""" - data = command_line.CommandSensorData(self.hass, 'asdfasdf') + data = command_line.CommandSensorData(self.hass, 'asdfasdf', 15) data.update() self.assertEqual(None, data.value) From 24d2261060aa5b43600a13f0f6401e54ecbffc69 Mon Sep 17 00:00:00 2001 From: Andrey Date: Wed, 18 Jul 2018 01:28:44 +0300 Subject: [PATCH 014/113] Add check_untyped_defs (#15510) * Add check_untyped_defs * Change to regular if-else --- homeassistant/__main__.py | 4 ++-- homeassistant/bootstrap.py | 2 +- homeassistant/config.py | 8 ++++--- homeassistant/config_entries.py | 3 ++- homeassistant/core.py | 35 ++++++++++++++++++------------- homeassistant/data_entry_flow.py | 4 ++-- homeassistant/monkey_patch.py | 4 ++-- homeassistant/setup.py | 5 +++-- homeassistant/util/__init__.py | 9 ++++---- homeassistant/util/async_.py | 14 +++++++++---- homeassistant/util/location.py | 10 +++++++-- homeassistant/util/logging.py | 2 +- homeassistant/util/unit_system.py | 3 ++- homeassistant/util/yaml.py | 2 +- mypy.ini | 9 ++++---- 15 files changed, 70 insertions(+), 44 deletions(-) diff --git a/homeassistant/__main__.py b/homeassistant/__main__.py index 496308598dce..32e26ad7f7b5 100644 --- a/homeassistant/__main__.py +++ b/homeassistant/__main__.py @@ -282,9 +282,9 @@ def setup_and_run_hass(config_dir: str, def open_browser(event): """Open the webinterface in a browser.""" - if hass.config.api is not None: + if hass.config.api is not None: # type: ignore import webbrowser - webbrowser.open(hass.config.api.base_url) + webbrowser.open(hass.config.api.base_url) # type: ignore run_callback_threadsafe( hass.loop, diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index a190aea9fa89..d832dda47547 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -293,7 +293,7 @@ def async_enable_logging(hass: core.HomeAssistant, async def async_stop_async_handler(event): """Cleanup async handler.""" - logging.getLogger('').removeHandler(async_handler) + logging.getLogger('').removeHandler(async_handler) # type: ignore await async_handler.async_close(blocking=True) hass.bus.async_listen_once( diff --git a/homeassistant/config.py b/homeassistant/config.py index d9206d622507..230a10498f34 100644 --- a/homeassistant/config.py +++ b/homeassistant/config.py @@ -653,7 +653,7 @@ def async_process_component_config(hass, config, domain): if hasattr(component, 'CONFIG_SCHEMA'): try: - config = component.CONFIG_SCHEMA(config) + config = component.CONFIG_SCHEMA(config) # type: ignore except vol.Invalid as ex: async_log_exception(ex, domain, config, hass) return None @@ -663,7 +663,8 @@ def async_process_component_config(hass, config, domain): for p_name, p_config in config_per_platform(config, domain): # Validate component specific platform schema try: - p_validated = component.PLATFORM_SCHEMA(p_config) + p_validated = component.PLATFORM_SCHEMA( # type: ignore + p_config) except vol.Invalid as ex: async_log_exception(ex, domain, config, hass) continue @@ -684,7 +685,8 @@ def async_process_component_config(hass, config, domain): if hasattr(platform, 'PLATFORM_SCHEMA'): # pylint: disable=no-member try: - p_validated = platform.PLATFORM_SCHEMA(p_validated) + p_validated = platform.PLATFORM_SCHEMA( # type: ignore + p_validated) except vol.Invalid as ex: async_log_exception(ex, '{}.{}'.format(domain, p_name), p_validated, hass) diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index 2e5613057f14..0fc66174c668 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -113,6 +113,7 @@ the flow from the config panel. import logging import uuid +from typing import Set # noqa pylint: disable=unused-import from homeassistant import data_entry_flow from homeassistant.core import callback @@ -279,7 +280,7 @@ class ConfigEntries: @callback def async_domains(self): """Return domains for which we have entries.""" - seen = set() + seen = set() # type: Set[ConfigEntry] result = [] for entry in self._entries: diff --git a/homeassistant/core.py b/homeassistant/core.py index 8b534bf17315..bfc3aed194f4 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -18,7 +18,8 @@ from time import monotonic from types import MappingProxyType from typing import ( # NOQA - Optional, Any, Callable, List, TypeVar, Dict, Coroutine) + Optional, Any, Callable, List, TypeVar, Dict, Coroutine, Set, + TYPE_CHECKING) from async_timeout import timeout import voluptuous as vol @@ -42,6 +43,11 @@ import homeassistant.util.dt as dt_util import homeassistant.util.location as location from homeassistant.util.unit_system import UnitSystem, METRIC_SYSTEM # NOQA +# Typing imports that create a circular dependency +# pylint: disable=using-constant-test,unused-import +if TYPE_CHECKING: + from homeassistant.config_entries import ConfigEntries # noqa + T = TypeVar('T') DOMAIN = 'homeassistant' @@ -93,7 +99,8 @@ def async_loop_exception_handler(loop, context): kwargs['exc_info'] = (type(exception), exception, exception.__traceback__) - _LOGGER.error("Error doing job: %s", context['message'], **kwargs) + _LOGGER.error( # type: ignore + "Error doing job: %s", context['message'], **kwargs) class CoreState(enum.Enum): @@ -126,7 +133,7 @@ class HomeAssistant(object): self.executor = ThreadPoolExecutor(**executor_opts) self.loop.set_default_executor(self.executor) self.loop.set_exception_handler(async_loop_exception_handler) - self._pending_tasks = [] + self._pending_tasks = [] # type: list self._track_task = True self.bus = EventBus(self) self.services = ServiceRegistry(self) @@ -135,10 +142,10 @@ class HomeAssistant(object): self.components = loader.Components(self) self.helpers = loader.Helpers(self) # This is a dictionary that any component can store any data on. - self.data = {} + self.data = {} # type: dict self.state = CoreState.not_running self.exit_code = 0 # type: int - self.config_entries = None + self.config_entries = None # type: Optional[ConfigEntries] @property def is_running(self) -> bool: @@ -217,7 +224,7 @@ class HomeAssistant(object): task = None if asyncio.iscoroutine(target): - task = self.loop.create_task(target) + task = self.loop.create_task(target) # type: ignore elif is_callback(target): self.loop.call_soon(target, *args) elif asyncio.iscoroutinefunction(target): @@ -252,7 +259,7 @@ class HomeAssistant(object): target: Callable[..., Any], *args: Any) -> asyncio.Future: """Add an executor job from within the event loop.""" - task = self.loop.run_in_executor( + task = self.loop.run_in_executor( # type: ignore None, target, *args) # type: asyncio.Future # If a task is scheduled @@ -652,7 +659,7 @@ class StateMachine(object): def __init__(self, bus, loop): """Initialize state machine.""" - self._states = {} + self._states = {} # type: Dict[str, State] self._bus = bus self._loop = loop @@ -819,7 +826,7 @@ class ServiceRegistry(object): def __init__(self, hass): """Initialize a service registry.""" - self._services = {} + self._services = {} # type: Dict[str, Dict[str, Service]] self._hass = hass self._async_unsub_call_event = None @@ -971,7 +978,7 @@ class ServiceRegistry(object): } if blocking: - fut = asyncio.Future(loop=self._hass.loop) + fut = asyncio.Future(loop=self._hass.loop) # type: asyncio.Future @callback def service_executed(event): @@ -1064,18 +1071,18 @@ class Config(object): self.skip_pip = False # type: bool # List of loaded components - self.components = set() + self.components = set() # type: set # Remote.API object pointing at local API self.api = None # Directory that holds the configuration - self.config_dir = None + self.config_dir = None # type: Optional[str] # List of allowed external dirs to access - self.whitelist_external_dirs = set() + self.whitelist_external_dirs = set() # type: Set[str] - def distance(self, lat: float, lon: float) -> float: + def distance(self, lat: float, lon: float) -> Optional[float]: """Calculate distance from Home Assistant. Async friendly. diff --git a/homeassistant/data_entry_flow.py b/homeassistant/data_entry_flow.py index e51ba4d97186..24dcb46bb681 100644 --- a/homeassistant/data_entry_flow.py +++ b/homeassistant/data_entry_flow.py @@ -1,7 +1,7 @@ """Classes to help gather user submissions.""" import logging import uuid - +from typing import Dict, Any # noqa pylint: disable=unused-import from .core import callback from .exceptions import HomeAssistantError @@ -38,7 +38,7 @@ class FlowManager: def __init__(self, hass, async_create_flow, async_finish_flow): """Initialize the flow manager.""" self.hass = hass - self._progress = {} + self._progress = {} # type: Dict[str, Any] self._async_create_flow = async_create_flow self._async_finish_flow = async_finish_flow diff --git a/homeassistant/monkey_patch.py b/homeassistant/monkey_patch.py index d5c629c9d344..17329fbddff4 100644 --- a/homeassistant/monkey_patch.py +++ b/homeassistant/monkey_patch.py @@ -34,7 +34,7 @@ def patch_weakref_tasks(): """No-op add.""" return - asyncio.tasks.Task._all_tasks = IgnoreCalls() + asyncio.tasks.Task._all_tasks = IgnoreCalls() # type: ignore try: del asyncio.tasks.Task.__del__ except: # noqa: E722 @@ -63,7 +63,7 @@ def disable_c_asyncio(): if fullname == self.PATH_TRIGGER: # We lint in Py35, exception is introduced in Py36 # pylint: disable=undefined-variable - raise ModuleNotFoundError() # noqa + raise ModuleNotFoundError() # type: ignore # noqa return None sys.path_hooks.append(AsyncioImportFinder) diff --git a/homeassistant/setup.py b/homeassistant/setup.py index 478320dca278..45482d5d14e7 100644 --- a/homeassistant/setup.py +++ b/homeassistant/setup.py @@ -163,8 +163,9 @@ async def _async_setup_component(hass: core.HomeAssistant, loader.set_component(hass, domain, None) return False - for entry in hass.config_entries.async_entries(domain): - await entry.async_setup(hass, component=component) + if hass.config_entries: + for entry in hass.config_entries.async_entries(domain): + await entry.async_setup(hass, component=component) hass.config.components.add(component.DOMAIN) # type: ignore diff --git a/homeassistant/util/__init__.py b/homeassistant/util/__init__.py index bbf0f7e11e23..c52bfa584daa 100644 --- a/homeassistant/util/__init__.py +++ b/homeassistant/util/__init__.py @@ -13,7 +13,8 @@ from functools import wraps from types import MappingProxyType from unicodedata import normalize -from typing import Any, Optional, TypeVar, Callable, KeysView, Union, Iterable +from typing import (Any, Optional, TypeVar, Callable, KeysView, Union, # noqa + Iterable, List, Mapping) from .dt import as_local, utcnow @@ -150,9 +151,9 @@ class OrderedSet(MutableSet): def __init__(self, iterable=None): """Initialize the set.""" - self.end = end = [] - end += [None, end, end] # sentinel node for doubly linked list - self.map = {} # key --> [key, prev, next] + self.end = end = [] # type: List[Any] + end += [None, end, end] # sentinel node for doubly linked list + self.map = {} # type: Mapping[List, Any] # key --> [key, prev, next] if iterable is not None: self |= iterable diff --git a/homeassistant/util/async_.py b/homeassistant/util/async_.py index b3aa370da2ed..334b4f454834 100644 --- a/homeassistant/util/async_.py +++ b/homeassistant/util/async_.py @@ -65,8 +65,14 @@ def _chain_future(source, destination): if not isinstance(destination, (Future, concurrent.futures.Future)): raise TypeError('A future is required for destination argument') # pylint: disable=protected-access - source_loop = source._loop if isinstance(source, Future) else None - dest_loop = destination._loop if isinstance(destination, Future) else None + if isinstance(source, Future): + source_loop = source._loop # type: ignore + else: + source_loop = None + if isinstance(destination, Future): + dest_loop = destination._loop # type: ignore + else: + dest_loop = None def _set_state(future, other): if isinstance(future, Future): @@ -102,7 +108,7 @@ def run_coroutine_threadsafe(coro, loop): if not coroutines.iscoroutine(coro): raise TypeError('A coroutine object is required') - future = concurrent.futures.Future() + future = concurrent.futures.Future() # type: concurrent.futures.Future def callback(): """Handle the call to the coroutine.""" @@ -150,7 +156,7 @@ def run_callback_threadsafe(loop, callback, *args): if ident is not None and ident == threading.get_ident(): raise RuntimeError('Cannot be called from within the event loop') - future = concurrent.futures.Future() + future = concurrent.futures.Future() # type: concurrent.futures.Future def run_callback(): """Run callback and store result.""" diff --git a/homeassistant/util/location.py b/homeassistant/util/location.py index e390b537d347..9fc87b24a9b7 100644 --- a/homeassistant/util/location.py +++ b/homeassistant/util/location.py @@ -49,12 +49,18 @@ def detect_location_info(): return LocationInfo(**data) -def distance(lat1, lon1, lat2, lon2): +def distance(lat1: Optional[float], lon1: Optional[float], + lat2: float, lon2: float) -> Optional[float]: """Calculate the distance in meters between two points. Async friendly. """ - return vincenty((lat1, lon1), (lat2, lon2)) * 1000 + if lat1 is None or lon1 is None: + return None + result = vincenty((lat1, lon1), (lat2, lon2)) + if result is None: + return None + return result * 1000 def elevation(latitude, longitude): diff --git a/homeassistant/util/logging.py b/homeassistant/util/logging.py index 10b434451848..65999a3a5c74 100644 --- a/homeassistant/util/logging.py +++ b/homeassistant/util/logging.py @@ -29,7 +29,7 @@ class AsyncHandler(object): """Initialize async logging handler wrapper.""" self.handler = handler self.loop = loop - self._queue = asyncio.Queue(loop=loop) + self._queue = asyncio.Queue(loop=loop) # type: asyncio.Queue self._thread = threading.Thread(target=self._process) # Delegate from handler diff --git a/homeassistant/util/unit_system.py b/homeassistant/util/unit_system.py index 4cc0fff96b94..ec17c1b796b9 100644 --- a/homeassistant/util/unit_system.py +++ b/homeassistant/util/unit_system.py @@ -1,6 +1,7 @@ """Unit system helper class and methods.""" import logging +from typing import Optional from numbers import Number from homeassistant.const import ( @@ -99,7 +100,7 @@ class UnitSystem(object): return temperature_util.convert(temperature, from_unit, self.temperature_unit) - def length(self, length: float, from_unit: str) -> float: + def length(self, length: Optional[float], from_unit: str) -> float: """Convert the given length to this unit system.""" if not isinstance(length, Number): raise TypeError('{} is not a numeric value.'.format(str(length))) diff --git a/homeassistant/util/yaml.py b/homeassistant/util/yaml.py index 298d52722a58..ddf7eb44601b 100644 --- a/homeassistant/util/yaml.py +++ b/homeassistant/util/yaml.py @@ -311,7 +311,7 @@ yaml.SafeLoader.add_constructor('!include_dir_merge_named', # pylint: disable=redefined-outer-name def represent_odict(dump, tag, mapping, flow_style=None): """Like BaseRepresenter.represent_mapping but does not issue the sort().""" - value = [] + value = [] # type: list node = yaml.MappingNode(tag, value, flow_style=flow_style) if dump.alias_key is not None: dump.represented_objects[dump.alias_key] = node diff --git a/mypy.ini b/mypy.ini index 3970ea72d471..5a597994d6bd 100644 --- a/mypy.ini +++ b/mypy.ini @@ -1,10 +1,11 @@ [mypy] -warn_redundant_casts = true -warn_unused_configs = true -ignore_missing_imports = true +check_untyped_defs = true follow_imports = silent -warn_unused_ignores = true +ignore_missing_imports = true +warn_redundant_casts = true warn_return_any = true +warn_unused_configs = true +warn_unused_ignores = true [mypy-homeassistant.util.yaml] warn_return_any = false From 2781796d9cf856f559df2d9b8bf1c7651afca573 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Wed, 18 Jul 2018 11:46:14 +0300 Subject: [PATCH 015/113] Remove some unused imports (#15529) --- homeassistant/__main__.py | 2 +- homeassistant/helpers/__init__.py | 5 ----- homeassistant/loader.py | 2 +- 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/homeassistant/__main__.py b/homeassistant/__main__.py index 32e26ad7f7b5..7a5345a1a73e 100644 --- a/homeassistant/__main__.py +++ b/homeassistant/__main__.py @@ -8,7 +8,7 @@ import subprocess import sys import threading -from typing import Optional, List, Dict, Any # noqa #pylint: disable=unused-import +from typing import List, Dict, Any # noqa pylint: disable=unused-import from homeassistant import monkey_patch diff --git a/homeassistant/helpers/__init__.py b/homeassistant/helpers/__init__.py index 54cd569acebc..ed489ed858b0 100644 --- a/homeassistant/helpers/__init__.py +++ b/homeassistant/helpers/__init__.py @@ -5,11 +5,6 @@ from typing import Any, Iterable, Tuple, Sequence, Dict from homeassistant.const import CONF_PLATFORM -# Typing Imports and TypeAlias -# pylint: disable=using-constant-test,unused-import -if False: - from logging import Logger # NOQA - # pylint: disable=invalid-name ConfigType = Dict[str, Any] diff --git a/homeassistant/loader.py b/homeassistant/loader.py index 52e6b1e77035..2b0f9ed18e45 100644 --- a/homeassistant/loader.py +++ b/homeassistant/loader.py @@ -17,7 +17,7 @@ import sys from types import ModuleType # pylint: disable=unused-import -from typing import Dict, List, Optional, Sequence, Set, TYPE_CHECKING # NOQA +from typing import Optional, Set, TYPE_CHECKING # NOQA from homeassistant.const import PLATFORM_FORMAT from homeassistant.util import OrderedSet From 98722e10fc0cf88d26c54d44b3426f19ac73adf7 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 18 Jul 2018 10:47:06 +0200 Subject: [PATCH 016/113] Decouple emulated hue from http server (#15530) --- .../components/emulated_hue/__init__.py | 58 ++++++++++--------- 1 file changed, 32 insertions(+), 26 deletions(-) diff --git a/homeassistant/components/emulated_hue/__init__.py b/homeassistant/components/emulated_hue/__init__.py index 6988e20fb5f0..ce94a560daee 100644 --- a/homeassistant/components/emulated_hue/__init__.py +++ b/homeassistant/components/emulated_hue/__init__.py @@ -6,6 +6,7 @@ https://home-assistant.io/components/emulated_hue/ """ import logging +from aiohttp import web import voluptuous as vol from homeassistant import util @@ -13,7 +14,6 @@ from homeassistant.const import ( EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, ) from homeassistant.components.http import REQUIREMENTS # NOQA -from homeassistant.components.http import HomeAssistantHTTP from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.deprecation import get_deprecated import homeassistant.helpers.config_validation as cv @@ -85,28 +85,17 @@ def setup(hass, yaml_config): """Activate the emulated_hue component.""" config = Config(hass, yaml_config.get(DOMAIN, {})) - server = HomeAssistantHTTP( - hass, - server_host=config.host_ip_addr, - server_port=config.listen_port, - api_password=None, - ssl_certificate=None, - ssl_peer_certificate=None, - ssl_key=None, - cors_origins=None, - use_x_forwarded_for=False, - trusted_proxies=[], - trusted_networks=[], - login_threshold=0, - is_ban_enabled=False - ) + app = web.Application() + app['hass'] = hass + handler = None + server = None - server.register_view(DescriptionXmlView(config)) - server.register_view(HueUsernameView) - server.register_view(HueAllLightsStateView(config)) - server.register_view(HueOneLightStateView(config)) - server.register_view(HueOneLightChangeView(config)) - server.register_view(HueGroupView(config)) + DescriptionXmlView(config).register(app.router) + HueUsernameView().register(app.router) + HueAllLightsStateView(config).register(app.router) + HueOneLightStateView(config).register(app.router) + HueOneLightChangeView(config).register(app.router) + HueGroupView(config).register(app.router) upnp_listener = UPNPResponderThread( config.host_ip_addr, config.listen_port, @@ -116,14 +105,31 @@ def setup(hass, yaml_config): async def stop_emulated_hue_bridge(event): """Stop the emulated hue bridge.""" upnp_listener.stop() - await server.stop() + if server: + server.close() + await server.wait_closed() + await app.shutdown() + if handler: + await handler.shutdown(10) + await app.cleanup() async def start_emulated_hue_bridge(event): """Start the emulated hue bridge.""" upnp_listener.start() - await server.start() - hass.bus.async_listen_once( - EVENT_HOMEASSISTANT_STOP, stop_emulated_hue_bridge) + nonlocal handler + nonlocal server + + handler = app.make_handler(loop=hass.loop) + + try: + server = await hass.loop.create_server( + handler, config.host_ip_addr, config.listen_port) + except OSError as error: + _LOGGER.error("Failed to create HTTP server at port %d: %s", + config.listen_port, error) + else: + hass.bus.async_listen_once( + EVENT_HOMEASSISTANT_STOP, stop_emulated_hue_bridge) hass.bus.listen_once(EVENT_HOMEASSISTANT_START, start_emulated_hue_bridge) From 058081b1f50d171d7fb91ae99ee576184ba664b4 Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Wed, 18 Jul 2018 10:54:54 +0200 Subject: [PATCH 017/113] Moon translate (#15498) * Translate moon * Create strings.moon.json * Update moon.py * Update strings.moon.json * Update test_moon.py --- homeassistant/components/sensor/moon.py | 16 ++++++++-------- .../components/sensor/strings.moon.json | 12 ++++++++++++ tests/components/sensor/test_moon.py | 4 ++-- 3 files changed, 22 insertions(+), 10 deletions(-) create mode 100644 homeassistant/components/sensor/strings.moon.json diff --git a/homeassistant/components/sensor/moon.py b/homeassistant/components/sensor/moon.py index 0c57c98c0af3..d909eb3c0f22 100644 --- a/homeassistant/components/sensor/moon.py +++ b/homeassistant/components/sensor/moon.py @@ -50,20 +50,20 @@ class MoonSensor(Entity): def state(self): """Return the state of the device.""" if self._state == 0: - return 'New moon' + return 'new_moon' elif self._state < 7: - return 'Waxing crescent' + return 'waxing_crescent' elif self._state == 7: - return 'First quarter' + return 'first_quarter' elif self._state < 14: - return 'Waxing gibbous' + return 'waxing_gibbous' elif self._state == 14: - return 'Full moon' + return 'full_moon' elif self._state < 21: - return 'Waning gibbous' + return 'waning_gibbous' elif self._state == 21: - return 'Last quarter' - return 'Waning crescent' + return 'last_quarter' + return 'waning_crescent' @property def icon(self): diff --git a/homeassistant/components/sensor/strings.moon.json b/homeassistant/components/sensor/strings.moon.json new file mode 100644 index 000000000000..97d96623d884 --- /dev/null +++ b/homeassistant/components/sensor/strings.moon.json @@ -0,0 +1,12 @@ +{ + "state": { + "new_moon": "New moon", + "waxing_crescent": "Waxing crescent", + "first_quarter": "First quarter", + "waxing_gibbous": "Waxing gibbous", + "full_moon": "Full moon", + "waning_gibbous": "Waning gibbous", + "last_quarter": "Last quarter", + "waning_crescent": "Waning crescent" + } +} diff --git a/tests/components/sensor/test_moon.py b/tests/components/sensor/test_moon.py index 334dd9a0bec0..9086df6e79b4 100644 --- a/tests/components/sensor/test_moon.py +++ b/tests/components/sensor/test_moon.py @@ -37,7 +37,7 @@ class TestMoonSensor(unittest.TestCase): assert setup_component(self.hass, 'sensor', config) state = self.hass.states.get('sensor.moon_day1') - self.assertEqual(state.state, 'Waxing crescent') + self.assertEqual(state.state, 'waxing_crescent') @patch('homeassistant.components.sensor.moon.dt_util.utcnow', return_value=DAY2) @@ -53,4 +53,4 @@ class TestMoonSensor(unittest.TestCase): assert setup_component(self.hass, 'sensor', config) state = self.hass.states.get('sensor.moon_day2') - self.assertEqual(state.state, 'Waning gibbous') + self.assertEqual(state.state, 'waning_gibbous') From bf17ed091781040001265babe4dc123d06e58443 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Wed, 18 Jul 2018 12:54:27 +0300 Subject: [PATCH 018/113] More pylint 2 fixes (#15516) * Pylint 2 useless-import-alias fixes * Pylint 2 chained-comparison fixes * Pylint 2 consider-using-get fixes * Pylint 2 len-as-condition fixes --- .../components/alarm_control_panel/demo.py | 2 +- .../components/alarm_control_panel/manual_mqtt.py | 2 +- .../components/alarm_control_panel/mqtt.py | 2 +- homeassistant/components/automation/mqtt.py | 2 +- homeassistant/components/bbb_gpio.py | 12 ++++++------ homeassistant/components/binary_sensor/bbb_gpio.py | 2 +- homeassistant/components/binary_sensor/modbus.py | 2 +- homeassistant/components/binary_sensor/mqtt.py | 2 +- homeassistant/components/binary_sensor/rpi_gpio.py | 2 +- homeassistant/components/binary_sensor/rpi_pfio.py | 2 +- homeassistant/components/binary_sensor/wemo.py | 2 +- homeassistant/components/calendar/__init__.py | 2 +- homeassistant/components/calendar/todoist.py | 2 +- homeassistant/components/camera/mqtt.py | 2 +- homeassistant/components/camera/nest.py | 2 +- homeassistant/components/camera/zoneminder.py | 2 +- homeassistant/components/climate/flexit.py | 2 +- homeassistant/components/climate/modbus.py | 2 +- homeassistant/components/climate/mqtt.py | 2 +- homeassistant/components/cover/mqtt.py | 2 +- homeassistant/components/cover/rfxtrx.py | 2 +- homeassistant/components/cover/rpi_gpio.py | 2 +- homeassistant/components/cover/scsgate.py | 2 +- homeassistant/components/demo.py | 2 +- .../components/device_tracker/__init__.py | 2 +- homeassistant/components/device_tracker/mqtt.py | 2 +- .../components/device_tracker/mqtt_json.py | 2 +- .../components/device_tracker/owntracks.py | 2 +- homeassistant/components/emulated_hue/hue_api.py | 6 +++--- homeassistant/components/fan/insteon_local.py | 7 ++----- homeassistant/components/fan/mqtt.py | 2 +- homeassistant/components/homekit/__init__.py | 2 +- homeassistant/components/homekit/util.py | 2 +- homeassistant/components/ifttt.py | 2 +- .../image_processing/microsoft_face_detect.py | 2 +- .../image_processing/microsoft_face_identify.py | 2 +- homeassistant/components/light/hue.py | 2 +- homeassistant/components/light/hyperion.py | 5 +---- homeassistant/components/light/insteon_local.py | 2 +- homeassistant/components/light/litejet.py | 2 +- homeassistant/components/light/lutron_caseta.py | 5 +---- homeassistant/components/light/mqtt.py | 2 +- homeassistant/components/light/mqtt_json.py | 2 +- homeassistant/components/light/mqtt_template.py | 2 +- homeassistant/components/light/rfxtrx.py | 2 +- homeassistant/components/light/scsgate.py | 2 +- homeassistant/components/light/wemo.py | 4 ++-- homeassistant/components/lock/mqtt.py | 2 +- homeassistant/components/mailbox/demo.py | 2 +- homeassistant/components/media_player/bluesound.py | 14 +++++++------- homeassistant/components/media_player/horizon.py | 2 +- homeassistant/components/media_player/kodi.py | 2 +- .../components/media_player/lg_netcast.py | 2 +- homeassistant/components/media_player/vizio.py | 2 +- homeassistant/components/media_player/webostv.py | 2 +- homeassistant/components/mqtt/discovery.py | 2 +- homeassistant/components/mysensors/gateway.py | 2 +- homeassistant/components/remote/harmony.py | 2 +- homeassistant/components/remote/itach.py | 2 +- homeassistant/components/remote/kira.py | 2 +- homeassistant/components/rpi_gpio.py | 12 ++++++------ homeassistant/components/scene/litejet.py | 2 +- homeassistant/components/sensor/arduino.py | 2 +- homeassistant/components/sensor/arwn.py | 2 +- homeassistant/components/sensor/dht.py | 4 ++-- homeassistant/components/sensor/filter.py | 2 +- .../components/sensor/google_travel_time.py | 2 +- homeassistant/components/sensor/google_wifi.py | 2 +- homeassistant/components/sensor/gtfs.py | 2 +- homeassistant/components/sensor/history_stats.py | 2 +- homeassistant/components/sensor/modbus.py | 2 +- homeassistant/components/sensor/mold_indicator.py | 2 +- homeassistant/components/sensor/mqtt.py | 2 +- homeassistant/components/sensor/mqtt_room.py | 2 +- homeassistant/components/sensor/pilight.py | 2 +- homeassistant/components/sensor/rfxtrx.py | 2 +- homeassistant/components/sensor/season.py | 2 +- homeassistant/components/sensor/tellstick.py | 2 +- homeassistant/components/sensor/thinkingcleaner.py | 2 +- homeassistant/components/sensor/toon.py | 2 +- .../components/sensor/waze_travel_time.py | 2 +- homeassistant/components/sensor/zabbix.py | 2 +- homeassistant/components/sensor/zoneminder.py | 2 +- homeassistant/components/snips.py | 2 +- homeassistant/components/switch/arduino.py | 2 +- homeassistant/components/switch/bbb_gpio.py | 2 +- homeassistant/components/switch/insteon_local.py | 2 +- homeassistant/components/switch/litejet.py | 2 +- homeassistant/components/switch/modbus.py | 2 +- homeassistant/components/switch/mqtt.py | 2 +- homeassistant/components/switch/pilight.py | 2 +- .../components/switch/pulseaudio_loopback.py | 2 +- homeassistant/components/switch/rfxtrx.py | 2 +- homeassistant/components/switch/rpi_gpio.py | 2 +- homeassistant/components/switch/rpi_pfio.py | 2 +- homeassistant/components/switch/scsgate.py | 2 +- homeassistant/components/switch/thinkingcleaner.py | 3 +-- homeassistant/components/switch/wemo.py | 2 +- homeassistant/components/switch/zoneminder.py | 2 +- homeassistant/components/thingspeak.py | 3 +-- homeassistant/components/vacuum/mqtt.py | 2 +- homeassistant/core.py | 4 ++-- homeassistant/helpers/service.py | 2 +- homeassistant/scripts/check_config.py | 2 +- 104 files changed, 126 insertions(+), 137 deletions(-) diff --git a/homeassistant/components/alarm_control_panel/demo.py b/homeassistant/components/alarm_control_panel/demo.py index c080a136c080..d2366e5836c9 100644 --- a/homeassistant/components/alarm_control_panel/demo.py +++ b/homeassistant/components/alarm_control_panel/demo.py @@ -5,7 +5,7 @@ For more details about this platform, please refer to the documentation https://home-assistant.io/components/demo/ """ import datetime -import homeassistant.components.alarm_control_panel.manual as manual +from homeassistant.components.alarm_control_panel import manual from homeassistant.const import ( STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_CUSTOM_BYPASS, STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_NIGHT, diff --git a/homeassistant/components/alarm_control_panel/manual_mqtt.py b/homeassistant/components/alarm_control_panel/manual_mqtt.py index 895f5edd5dae..e313f96fb7cb 100644 --- a/homeassistant/components/alarm_control_panel/manual_mqtt.py +++ b/homeassistant/components/alarm_control_panel/manual_mqtt.py @@ -19,7 +19,7 @@ from homeassistant.const import ( STATE_ALARM_DISARMED, STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED, CONF_PLATFORM, CONF_NAME, CONF_CODE, CONF_DELAY_TIME, CONF_PENDING_TIME, CONF_TRIGGER_TIME, CONF_DISARM_AFTER_TRIGGER) -import homeassistant.components.mqtt as mqtt +from homeassistant.components import mqtt from homeassistant.helpers.event import async_track_state_change from homeassistant.core import callback diff --git a/homeassistant/components/alarm_control_panel/mqtt.py b/homeassistant/components/alarm_control_panel/mqtt.py index 9f2a4176ed88..d6198301d765 100644 --- a/homeassistant/components/alarm_control_panel/mqtt.py +++ b/homeassistant/components/alarm_control_panel/mqtt.py @@ -12,7 +12,7 @@ import voluptuous as vol from homeassistant.core import callback import homeassistant.components.alarm_control_panel as alarm -import homeassistant.components.mqtt as mqtt +from homeassistant.components import mqtt from homeassistant.const import ( STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED, STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED, STATE_UNKNOWN, diff --git a/homeassistant/components/automation/mqtt.py b/homeassistant/components/automation/mqtt.py index 172a368225d6..60c33ca9b0ef 100644 --- a/homeassistant/components/automation/mqtt.py +++ b/homeassistant/components/automation/mqtt.py @@ -10,7 +10,7 @@ import json import voluptuous as vol from homeassistant.core import callback -import homeassistant.components.mqtt as mqtt +from homeassistant.components import mqtt from homeassistant.const import (CONF_PLATFORM, CONF_PAYLOAD) import homeassistant.helpers.config_validation as cv diff --git a/homeassistant/components/bbb_gpio.py b/homeassistant/components/bbb_gpio.py index f932f2399691..e3f327f1d5cf 100644 --- a/homeassistant/components/bbb_gpio.py +++ b/homeassistant/components/bbb_gpio.py @@ -19,7 +19,7 @@ DOMAIN = 'bbb_gpio' def setup(hass, config): """Set up the BeagleBone Black GPIO component.""" # pylint: disable=import-error - import Adafruit_BBIO.GPIO as GPIO + from Adafruit_BBIO import GPIO def cleanup_gpio(event): """Stuff to do before stopping.""" @@ -36,14 +36,14 @@ def setup(hass, config): def setup_output(pin): """Set up a GPIO as output.""" # pylint: disable=import-error - import Adafruit_BBIO.GPIO as GPIO + from Adafruit_BBIO import GPIO GPIO.setup(pin, GPIO.OUT) def setup_input(pin, pull_mode): """Set up a GPIO as input.""" # pylint: disable=import-error - import Adafruit_BBIO.GPIO as GPIO + from Adafruit_BBIO import GPIO GPIO.setup(pin, GPIO.IN, GPIO.PUD_DOWN if pull_mode == 'DOWN' else GPIO.PUD_UP) @@ -52,20 +52,20 @@ def setup_input(pin, pull_mode): def write_output(pin, value): """Write a value to a GPIO.""" # pylint: disable=import-error - import Adafruit_BBIO.GPIO as GPIO + from Adafruit_BBIO import GPIO GPIO.output(pin, value) def read_input(pin): """Read a value from a GPIO.""" # pylint: disable=import-error - import Adafruit_BBIO.GPIO as GPIO + from Adafruit_BBIO import GPIO return GPIO.input(pin) is GPIO.HIGH def edge_detect(pin, event_callback, bounce): """Add detection for RISING and FALLING events.""" # pylint: disable=import-error - import Adafruit_BBIO.GPIO as GPIO + from Adafruit_BBIO import GPIO GPIO.add_event_detect( pin, GPIO.BOTH, callback=event_callback, bouncetime=bounce) diff --git a/homeassistant/components/binary_sensor/bbb_gpio.py b/homeassistant/components/binary_sensor/bbb_gpio.py index 785b178969f2..690d1651db9f 100644 --- a/homeassistant/components/binary_sensor/bbb_gpio.py +++ b/homeassistant/components/binary_sensor/bbb_gpio.py @@ -8,7 +8,7 @@ import logging import voluptuous as vol -import homeassistant.components.bbb_gpio as bbb_gpio +from homeassistant.components import bbb_gpio from homeassistant.components.binary_sensor import ( BinarySensorDevice, PLATFORM_SCHEMA) from homeassistant.const import (DEVICE_DEFAULT_NAME, CONF_NAME) diff --git a/homeassistant/components/binary_sensor/modbus.py b/homeassistant/components/binary_sensor/modbus.py index 00dc588a4688..1a45235f15a8 100644 --- a/homeassistant/components/binary_sensor/modbus.py +++ b/homeassistant/components/binary_sensor/modbus.py @@ -7,7 +7,7 @@ https://home-assistant.io/components/binary_sensor.modbus/ import logging import voluptuous as vol -import homeassistant.components.modbus as modbus +from homeassistant.components import modbus from homeassistant.const import CONF_NAME, CONF_SLAVE from homeassistant.components.binary_sensor import BinarySensorDevice from homeassistant.helpers import config_validation as cv diff --git a/homeassistant/components/binary_sensor/mqtt.py b/homeassistant/components/binary_sensor/mqtt.py index d2533eb8f5b4..cb943ac3f188 100644 --- a/homeassistant/components/binary_sensor/mqtt.py +++ b/homeassistant/components/binary_sensor/mqtt.py @@ -11,7 +11,7 @@ from typing import Optional import voluptuous as vol from homeassistant.core import callback -import homeassistant.components.mqtt as mqtt +from homeassistant.components import mqtt from homeassistant.components.binary_sensor import ( BinarySensorDevice, DEVICE_CLASSES_SCHEMA) from homeassistant.const import ( diff --git a/homeassistant/components/binary_sensor/rpi_gpio.py b/homeassistant/components/binary_sensor/rpi_gpio.py index 4072f4ae2349..31a518dc1dc1 100644 --- a/homeassistant/components/binary_sensor/rpi_gpio.py +++ b/homeassistant/components/binary_sensor/rpi_gpio.py @@ -8,7 +8,7 @@ import logging import voluptuous as vol -import homeassistant.components.rpi_gpio as rpi_gpio +from homeassistant.components import rpi_gpio from homeassistant.components.binary_sensor import ( BinarySensorDevice, PLATFORM_SCHEMA) from homeassistant.const import DEVICE_DEFAULT_NAME diff --git a/homeassistant/components/binary_sensor/rpi_pfio.py b/homeassistant/components/binary_sensor/rpi_pfio.py index 1abfa25c82b5..a1126bdd2f90 100644 --- a/homeassistant/components/binary_sensor/rpi_pfio.py +++ b/homeassistant/components/binary_sensor/rpi_pfio.py @@ -10,7 +10,7 @@ import voluptuous as vol from homeassistant.components.binary_sensor import ( PLATFORM_SCHEMA, BinarySensorDevice) -import homeassistant.components.rpi_pfio as rpi_pfio +from homeassistant.components import rpi_pfio from homeassistant.const import CONF_NAME, DEVICE_DEFAULT_NAME import homeassistant.helpers.config_validation as cv diff --git a/homeassistant/components/binary_sensor/wemo.py b/homeassistant/components/binary_sensor/wemo.py index e6eff0d9bb5b..a589ab4e8c89 100644 --- a/homeassistant/components/binary_sensor/wemo.py +++ b/homeassistant/components/binary_sensor/wemo.py @@ -15,7 +15,7 @@ _LOGGER = logging.getLogger(__name__) def setup_platform(hass, config, add_devices_callback, discovery_info=None): """Register discovered WeMo binary sensors.""" - import pywemo.discovery as discovery + from pywemo import discovery if discovery_info is not None: location = discovery_info['ssdp_description'] diff --git a/homeassistant/components/calendar/__init__.py b/homeassistant/components/calendar/__init__.py index 35566b0cbed9..9d105fb02d0f 100644 --- a/homeassistant/components/calendar/__init__.py +++ b/homeassistant/components/calendar/__init__.py @@ -130,7 +130,7 @@ class CalendarEventDevice(Entity): now = dt.now() - if start <= now and end > now: + if start <= now < end: return STATE_ON if now >= end: diff --git a/homeassistant/components/calendar/todoist.py b/homeassistant/components/calendar/todoist.py index 71a6a17de107..42ab7b01c63b 100644 --- a/homeassistant/components/calendar/todoist.py +++ b/homeassistant/components/calendar/todoist.py @@ -503,7 +503,7 @@ class TodoistProjectData(object): time_format = '%a %d %b %Y %H:%M:%S %z' for task in project_task_data: due_date = datetime.strptime(task['due_date_utc'], time_format) - if due_date > start_date and due_date < end_date: + if start_date < due_date < end_date: event = { 'uid': task['id'], 'title': task['content'], diff --git a/homeassistant/components/camera/mqtt.py b/homeassistant/components/camera/mqtt.py index b2a27230a02d..dc991644b8e5 100644 --- a/homeassistant/components/camera/mqtt.py +++ b/homeassistant/components/camera/mqtt.py @@ -11,7 +11,7 @@ import logging import voluptuous as vol from homeassistant.core import callback -import homeassistant.components.mqtt as mqtt +from homeassistant.components import mqtt from homeassistant.const import CONF_NAME from homeassistant.components.camera import Camera, PLATFORM_SCHEMA from homeassistant.helpers import config_validation as cv diff --git a/homeassistant/components/camera/nest.py b/homeassistant/components/camera/nest.py index ab26df5caf00..fcd9b7e8a074 100644 --- a/homeassistant/components/camera/nest.py +++ b/homeassistant/components/camera/nest.py @@ -9,7 +9,7 @@ from datetime import timedelta import requests -import homeassistant.components.nest as nest +from homeassistant.components import nest from homeassistant.components.camera import (PLATFORM_SCHEMA, Camera) from homeassistant.util.dt import utcnow diff --git a/homeassistant/components/camera/zoneminder.py b/homeassistant/components/camera/zoneminder.py index 90ef08c24feb..be59a1c1f50e 100644 --- a/homeassistant/components/camera/zoneminder.py +++ b/homeassistant/components/camera/zoneminder.py @@ -12,7 +12,7 @@ from homeassistant.const import CONF_NAME from homeassistant.components.camera.mjpeg import ( CONF_MJPEG_URL, CONF_STILL_IMAGE_URL, MjpegCamera) -import homeassistant.components.zoneminder as zoneminder +from homeassistant.components import zoneminder _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/climate/flexit.py b/homeassistant/components/climate/flexit.py index 565e913319f4..6c340e4a5f01 100644 --- a/homeassistant/components/climate/flexit.py +++ b/homeassistant/components/climate/flexit.py @@ -20,7 +20,7 @@ from homeassistant.const import ( from homeassistant.components.climate import ( ClimateDevice, PLATFORM_SCHEMA, SUPPORT_TARGET_TEMPERATURE, SUPPORT_FAN_MODE) -import homeassistant.components.modbus as modbus +from homeassistant.components import modbus import homeassistant.helpers.config_validation as cv REQUIREMENTS = ['pyflexit==0.3'] diff --git a/homeassistant/components/climate/modbus.py b/homeassistant/components/climate/modbus.py index 7d392e5a40f6..e567340efc9f 100644 --- a/homeassistant/components/climate/modbus.py +++ b/homeassistant/components/climate/modbus.py @@ -18,7 +18,7 @@ from homeassistant.const import ( from homeassistant.components.climate import ( ClimateDevice, PLATFORM_SCHEMA, SUPPORT_TARGET_TEMPERATURE) -import homeassistant.components.modbus as modbus +from homeassistant.components import modbus import homeassistant.helpers.config_validation as cv DEPENDENCIES = ['modbus'] diff --git a/homeassistant/components/climate/mqtt.py b/homeassistant/components/climate/mqtt.py index fbe5460979b0..1426ff31af90 100644 --- a/homeassistant/components/climate/mqtt.py +++ b/homeassistant/components/climate/mqtt.py @@ -10,7 +10,7 @@ import logging import voluptuous as vol from homeassistant.core import callback -import homeassistant.components.mqtt as mqtt +from homeassistant.components import mqtt from homeassistant.components.climate import ( STATE_HEAT, STATE_COOL, STATE_DRY, STATE_FAN_ONLY, ClimateDevice, diff --git a/homeassistant/components/cover/mqtt.py b/homeassistant/components/cover/mqtt.py index 62e1069e18bb..e1775e2f968f 100644 --- a/homeassistant/components/cover/mqtt.py +++ b/homeassistant/components/cover/mqtt.py @@ -9,7 +9,7 @@ import logging import voluptuous as vol from homeassistant.core import callback -import homeassistant.components.mqtt as mqtt +from homeassistant.components import mqtt from homeassistant.components.cover import ( CoverDevice, ATTR_TILT_POSITION, SUPPORT_OPEN_TILT, SUPPORT_CLOSE_TILT, SUPPORT_STOP_TILT, SUPPORT_SET_TILT_POSITION, diff --git a/homeassistant/components/cover/rfxtrx.py b/homeassistant/components/cover/rfxtrx.py index aefb7ab89d71..5079a3b60c29 100644 --- a/homeassistant/components/cover/rfxtrx.py +++ b/homeassistant/components/cover/rfxtrx.py @@ -6,7 +6,7 @@ https://home-assistant.io/components/cover.rfxtrx/ """ import voluptuous as vol -import homeassistant.components.rfxtrx as rfxtrx +from homeassistant.components import rfxtrx from homeassistant.components.cover import CoverDevice, PLATFORM_SCHEMA from homeassistant.const import CONF_NAME from homeassistant.components.rfxtrx import ( diff --git a/homeassistant/components/cover/rpi_gpio.py b/homeassistant/components/cover/rpi_gpio.py index 384f96f3f523..2f6951cfc0d2 100644 --- a/homeassistant/components/cover/rpi_gpio.py +++ b/homeassistant/components/cover/rpi_gpio.py @@ -14,7 +14,7 @@ import voluptuous as vol from homeassistant.components.cover import CoverDevice, PLATFORM_SCHEMA from homeassistant.const import CONF_NAME -import homeassistant.components.rpi_gpio as rpi_gpio +from homeassistant.components import rpi_gpio import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/cover/scsgate.py b/homeassistant/components/cover/scsgate.py index ac4fddf98bb7..04bf0ef1d32a 100644 --- a/homeassistant/components/cover/scsgate.py +++ b/homeassistant/components/cover/scsgate.py @@ -8,7 +8,7 @@ import logging import voluptuous as vol -import homeassistant.components.scsgate as scsgate +from homeassistant.components import scsgate from homeassistant.components.cover import (CoverDevice, PLATFORM_SCHEMA) from homeassistant.const import (CONF_DEVICES, CONF_NAME) import homeassistant.helpers.config_validation as cv diff --git a/homeassistant/components/demo.py b/homeassistant/components/demo.py index 64ce3cda0730..c2c7866148ff 100644 --- a/homeassistant/components/demo.py +++ b/homeassistant/components/demo.py @@ -7,7 +7,7 @@ https://home-assistant.io/components/demo/ import asyncio import time -import homeassistant.bootstrap as bootstrap +from homeassistant import bootstrap import homeassistant.core as ha from homeassistant.const import ATTR_ENTITY_ID, CONF_PLATFORM diff --git a/homeassistant/components/device_tracker/__init__.py b/homeassistant/components/device_tracker/__init__.py index 580c0272e469..b95a0a9d73b6 100644 --- a/homeassistant/components/device_tracker/__init__.py +++ b/homeassistant/components/device_tracker/__init__.py @@ -24,7 +24,7 @@ from homeassistant.helpers.event import async_track_time_interval from homeassistant.helpers.restore_state import async_get_last_state from homeassistant.helpers.typing import GPSType, ConfigType, HomeAssistantType import homeassistant.helpers.config_validation as cv -import homeassistant.util as util +from homeassistant import util from homeassistant.util.async_ import run_coroutine_threadsafe import homeassistant.util.dt as dt_util from homeassistant.util.yaml import dump diff --git a/homeassistant/components/device_tracker/mqtt.py b/homeassistant/components/device_tracker/mqtt.py index 2e2d9b10d985..b5031e8ccfbc 100644 --- a/homeassistant/components/device_tracker/mqtt.py +++ b/homeassistant/components/device_tracker/mqtt.py @@ -9,7 +9,7 @@ import logging import voluptuous as vol -import homeassistant.components.mqtt as mqtt +from homeassistant.components import mqtt from homeassistant.core import callback from homeassistant.const import CONF_DEVICES from homeassistant.components.mqtt import CONF_QOS diff --git a/homeassistant/components/device_tracker/mqtt_json.py b/homeassistant/components/device_tracker/mqtt_json.py index 9a5532fc9f44..7e5ae7c92271 100644 --- a/homeassistant/components/device_tracker/mqtt_json.py +++ b/homeassistant/components/device_tracker/mqtt_json.py @@ -10,7 +10,7 @@ import logging import voluptuous as vol -import homeassistant.components.mqtt as mqtt +from homeassistant.components import mqtt from homeassistant.core import callback from homeassistant.components.mqtt import CONF_QOS from homeassistant.components.device_tracker import PLATFORM_SCHEMA diff --git a/homeassistant/components/device_tracker/owntracks.py b/homeassistant/components/device_tracker/owntracks.py index e99524c36db6..2d7f1e80406d 100644 --- a/homeassistant/components/device_tracker/owntracks.py +++ b/homeassistant/components/device_tracker/owntracks.py @@ -12,7 +12,7 @@ from collections import defaultdict import voluptuous as vol -import homeassistant.components.mqtt as mqtt +from homeassistant.components import mqtt import homeassistant.helpers.config_validation as cv from homeassistant.components import zone as zone_comp from homeassistant.components.device_tracker import ( diff --git a/homeassistant/components/emulated_hue/hue_api.py b/homeassistant/components/emulated_hue/hue_api.py index 2b74984e4ca2..f7fbe2e15e3f 100644 --- a/homeassistant/components/emulated_hue/hue_api.py +++ b/homeassistant/components/emulated_hue/hue_api.py @@ -237,11 +237,11 @@ class HueOneLightChangeView(HomeAssistantView): # Convert 0-100 to a fan speed if brightness == 0: data[ATTR_SPEED] = SPEED_OFF - elif brightness <= 33.3 and brightness > 0: + elif 0 < brightness <= 33.3: data[ATTR_SPEED] = SPEED_LOW - elif brightness <= 66.6 and brightness > 33.3: + elif 33.3 < brightness <= 66.6: data[ATTR_SPEED] = SPEED_MEDIUM - elif brightness <= 100 and brightness > 66.6: + elif 66.6 < brightness <= 100: data[ATTR_SPEED] = SPEED_HIGH if entity.domain in config.off_maps_to_on_domains: diff --git a/homeassistant/components/fan/insteon_local.py b/homeassistant/components/fan/insteon_local.py index b8a5c99add42..1a5e8124dfc5 100644 --- a/homeassistant/components/fan/insteon_local.py +++ b/homeassistant/components/fan/insteon_local.py @@ -11,7 +11,7 @@ from homeassistant.components.fan import ( ATTR_SPEED, SPEED_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH, SUPPORT_SET_SPEED, FanEntity) from homeassistant.helpers.entity import ToggleEntity -import homeassistant.util as util +from homeassistant import util _CONFIGURING = {} _LOGGER = logging.getLogger(__name__) @@ -94,10 +94,7 @@ class InsteonLocalFanDevice(FanEntity): def turn_on(self: ToggleEntity, speed: str = None, **kwargs) -> None: """Turn device on.""" if speed is None: - if ATTR_SPEED in kwargs: - speed = kwargs[ATTR_SPEED] - else: - speed = SPEED_MEDIUM + speed = kwargs.get(ATTR_SPEED, SPEED_MEDIUM) self.set_speed(speed) diff --git a/homeassistant/components/fan/mqtt.py b/homeassistant/components/fan/mqtt.py index 6fa506edec66..5faa735801db 100644 --- a/homeassistant/components/fan/mqtt.py +++ b/homeassistant/components/fan/mqtt.py @@ -9,7 +9,7 @@ import logging import voluptuous as vol from homeassistant.core import callback -import homeassistant.components.mqtt as mqtt +from homeassistant.components import mqtt from homeassistant.const import ( CONF_NAME, CONF_OPTIMISTIC, CONF_STATE, STATE_ON, STATE_OFF, CONF_PAYLOAD_OFF, CONF_PAYLOAD_ON) diff --git a/homeassistant/components/homekit/__init__.py b/homeassistant/components/homekit/__init__.py index 81f668809438..32d0956e8782 100644 --- a/homeassistant/components/homekit/__init__.py +++ b/homeassistant/components/homekit/__init__.py @@ -9,7 +9,7 @@ from zlib import adler32 import voluptuous as vol -import homeassistant.components.cover as cover +from homeassistant.components import cover from homeassistant.const import ( ATTR_DEVICE_CLASS, ATTR_SUPPORTED_FEATURES, ATTR_UNIT_OF_MEASUREMENT, CONF_IP_ADDRESS, CONF_NAME, CONF_PORT, CONF_TYPE, DEVICE_CLASS_HUMIDITY, diff --git a/homeassistant/components/homekit/util.py b/homeassistant/components/homekit/util.py index 6a43a0c62289..c2f8951c8d83 100644 --- a/homeassistant/components/homekit/util.py +++ b/homeassistant/components/homekit/util.py @@ -3,7 +3,7 @@ import logging import voluptuous as vol -import homeassistant.components.media_player as media_player +from homeassistant.components import media_player from homeassistant.core import split_entity_id from homeassistant.const import ( ATTR_CODE, ATTR_SUPPORTED_FEATURES, CONF_NAME, CONF_TYPE, TEMP_CELSIUS) diff --git a/homeassistant/components/ifttt.py b/homeassistant/components/ifttt.py index 0a4ad66ce567..9497282ab21c 100644 --- a/homeassistant/components/ifttt.py +++ b/homeassistant/components/ifttt.py @@ -63,7 +63,7 @@ def setup(hass, config): value3 = call.data.get(ATTR_VALUE3) try: - import pyfttt as pyfttt + import pyfttt pyfttt.send_event(key, event, value1, value2, value3) except requests.exceptions.RequestException: _LOGGER.exception("Error communicating with IFTTT") diff --git a/homeassistant/components/image_processing/microsoft_face_detect.py b/homeassistant/components/image_processing/microsoft_face_detect.py index bda0e1bc550f..0b57dba8bcab 100644 --- a/homeassistant/components/image_processing/microsoft_face_detect.py +++ b/homeassistant/components/image_processing/microsoft_face_detect.py @@ -103,7 +103,7 @@ class MicrosoftFaceDetectEntity(ImageProcessingFaceEntity): _LOGGER.error("Can't process image on microsoft face: %s", err) return - if face_data is None or len(face_data) < 1: + if not face_data: return faces = [] diff --git a/homeassistant/components/image_processing/microsoft_face_identify.py b/homeassistant/components/image_processing/microsoft_face_identify.py index 8984f25cdf2d..9479a804a441 100644 --- a/homeassistant/components/image_processing/microsoft_face_identify.py +++ b/homeassistant/components/image_processing/microsoft_face_identify.py @@ -90,7 +90,7 @@ class MicrosoftFaceIdentifyEntity(ImageProcessingFaceEntity): face_data = yield from self._api.call_api( 'post', 'detect', image, binary=True) - if face_data is None or len(face_data) < 1: + if not face_data: return face_ids = [data['faceId'] for data in face_data] diff --git a/homeassistant/components/light/hue.py b/homeassistant/components/light/hue.py index 837a6f82510e..0da59b6f100c 100644 --- a/homeassistant/components/light/hue.py +++ b/homeassistant/components/light/hue.py @@ -11,7 +11,7 @@ import random import async_timeout -import homeassistant.components.hue as hue +from homeassistant.components import hue from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_EFFECT, ATTR_FLASH, ATTR_TRANSITION, ATTR_HS_COLOR, EFFECT_COLORLOOP, EFFECT_RANDOM, diff --git a/homeassistant/components/light/hyperion.py b/homeassistant/components/light/hyperion.py index 8ba2329af7e0..cbac8cf4e201 100644 --- a/homeassistant/components/light/hyperion.py +++ b/homeassistant/components/light/hyperion.py @@ -146,10 +146,7 @@ class Hyperion(Light): else: rgb_color = self._rgb_mem - if ATTR_BRIGHTNESS in kwargs: - brightness = kwargs[ATTR_BRIGHTNESS] - else: - brightness = self._brightness + brightness = kwargs.get(ATTR_BRIGHTNESS, self._brightness) if ATTR_EFFECT in kwargs: self._skip_update = True diff --git a/homeassistant/components/light/insteon_local.py b/homeassistant/components/light/insteon_local.py index bd7814df8f3e..e2bc54de5176 100644 --- a/homeassistant/components/light/insteon_local.py +++ b/homeassistant/components/light/insteon_local.py @@ -9,7 +9,7 @@ from datetime import timedelta from homeassistant.components.light import ( ATTR_BRIGHTNESS, SUPPORT_BRIGHTNESS, Light) -import homeassistant.util as util +from homeassistant import util _CONFIGURING = {} _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/light/litejet.py b/homeassistant/components/light/litejet.py index 2ebe766c8c5e..b8491b6f0f53 100644 --- a/homeassistant/components/light/litejet.py +++ b/homeassistant/components/light/litejet.py @@ -6,7 +6,7 @@ https://home-assistant.io/components/light.litejet/ """ import logging -import homeassistant.components.litejet as litejet +from homeassistant.components import litejet from homeassistant.components.light import ( ATTR_BRIGHTNESS, SUPPORT_BRIGHTNESS, Light) diff --git a/homeassistant/components/light/lutron_caseta.py b/homeassistant/components/light/lutron_caseta.py index 09f0a337cc34..29186b8fcd22 100644 --- a/homeassistant/components/light/lutron_caseta.py +++ b/homeassistant/components/light/lutron_caseta.py @@ -48,10 +48,7 @@ class LutronCasetaLight(LutronCasetaDevice, Light): @asyncio.coroutine def async_turn_on(self, **kwargs): """Turn the light on.""" - if ATTR_BRIGHTNESS in kwargs: - brightness = kwargs[ATTR_BRIGHTNESS] - else: - brightness = 255 + brightness = kwargs.get(ATTR_BRIGHTNESS, 255) self._smartbridge.set_value(self._device_id, to_lutron_level(brightness)) diff --git a/homeassistant/components/light/mqtt.py b/homeassistant/components/light/mqtt.py index c0e363f85d6d..cfc1ce27b949 100644 --- a/homeassistant/components/light/mqtt.py +++ b/homeassistant/components/light/mqtt.py @@ -9,7 +9,7 @@ import logging import voluptuous as vol from homeassistant.core import callback -import homeassistant.components.mqtt as mqtt +from homeassistant.components import mqtt from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_EFFECT, ATTR_HS_COLOR, ATTR_WHITE_VALUE, Light, SUPPORT_BRIGHTNESS, SUPPORT_COLOR_TEMP, diff --git a/homeassistant/components/light/mqtt_json.py b/homeassistant/components/light/mqtt_json.py index 705e106fdff8..d17c7dd73bf5 100644 --- a/homeassistant/components/light/mqtt_json.py +++ b/homeassistant/components/light/mqtt_json.py @@ -9,7 +9,7 @@ import json import voluptuous as vol from homeassistant.core import callback -import homeassistant.components.mqtt as mqtt +from homeassistant.components import mqtt from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_EFFECT, ATTR_FLASH, ATTR_TRANSITION, ATTR_WHITE_VALUE, ATTR_HS_COLOR, diff --git a/homeassistant/components/light/mqtt_template.py b/homeassistant/components/light/mqtt_template.py index f6b3fbe8b707..ffa73aca9152 100644 --- a/homeassistant/components/light/mqtt_template.py +++ b/homeassistant/components/light/mqtt_template.py @@ -8,7 +8,7 @@ import logging import voluptuous as vol from homeassistant.core import callback -import homeassistant.components.mqtt as mqtt +from homeassistant.components import mqtt from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_EFFECT, ATTR_FLASH, ATTR_HS_COLOR, ATTR_TRANSITION, ATTR_WHITE_VALUE, Light, PLATFORM_SCHEMA, diff --git a/homeassistant/components/light/rfxtrx.py b/homeassistant/components/light/rfxtrx.py index cdfe2fe56719..293783ee3ab1 100644 --- a/homeassistant/components/light/rfxtrx.py +++ b/homeassistant/components/light/rfxtrx.py @@ -8,7 +8,7 @@ import logging import voluptuous as vol -import homeassistant.components.rfxtrx as rfxtrx +from homeassistant.components import rfxtrx from homeassistant.components.light import ( ATTR_BRIGHTNESS, SUPPORT_BRIGHTNESS, Light, PLATFORM_SCHEMA) from homeassistant.const import CONF_NAME diff --git a/homeassistant/components/light/scsgate.py b/homeassistant/components/light/scsgate.py index 214a2d99449c..3d567afe09e0 100644 --- a/homeassistant/components/light/scsgate.py +++ b/homeassistant/components/light/scsgate.py @@ -8,7 +8,7 @@ import logging import voluptuous as vol -import homeassistant.components.scsgate as scsgate +from homeassistant.components import scsgate from homeassistant.components.light import (Light, PLATFORM_SCHEMA) from homeassistant.const import ( ATTR_ENTITY_ID, ATTR_STATE, CONF_DEVICES, CONF_NAME) diff --git a/homeassistant/components/light/wemo.py b/homeassistant/components/light/wemo.py index 4cd34b698dae..4c912d60fb71 100644 --- a/homeassistant/components/light/wemo.py +++ b/homeassistant/components/light/wemo.py @@ -8,7 +8,7 @@ import asyncio import logging from datetime import timedelta -import homeassistant.util as util +from homeassistant import util from homeassistant.components.light import ( Light, ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_HS_COLOR, ATTR_TRANSITION, SUPPORT_BRIGHTNESS, SUPPORT_COLOR_TEMP, SUPPORT_COLOR, SUPPORT_TRANSITION) @@ -27,7 +27,7 @@ SUPPORT_WEMO = (SUPPORT_BRIGHTNESS | SUPPORT_COLOR_TEMP | SUPPORT_COLOR | def setup_platform(hass, config, add_devices, discovery_info=None): """Set up discovered WeMo switches.""" - import pywemo.discovery as discovery + from pywemo import discovery if discovery_info is not None: location = discovery_info['ssdp_description'] diff --git a/homeassistant/components/lock/mqtt.py b/homeassistant/components/lock/mqtt.py index d8af22cd5c3c..45029e679a5c 100644 --- a/homeassistant/components/lock/mqtt.py +++ b/homeassistant/components/lock/mqtt.py @@ -17,7 +17,7 @@ from homeassistant.components.mqtt import ( MqttAvailability) from homeassistant.const import ( CONF_NAME, CONF_OPTIMISTIC, CONF_VALUE_TEMPLATE) -import homeassistant.components.mqtt as mqtt +from homeassistant.components import mqtt import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/mailbox/demo.py b/homeassistant/components/mailbox/demo.py index ccb371de2f8a..8096a4fabb70 100644 --- a/homeassistant/components/mailbox/demo.py +++ b/homeassistant/components/mailbox/demo.py @@ -9,7 +9,7 @@ import logging import os from hashlib import sha1 -import homeassistant.util.dt as dt +from homeassistant.util import dt from homeassistant.components.mailbox import (Mailbox, CONTENT_TYPE_MPEG, StreamError) diff --git a/homeassistant/components/media_player/bluesound.py b/homeassistant/components/media_player/bluesound.py index ec878e5c0437..c96889f4fe4f 100644 --- a/homeassistant/components/media_player/bluesound.py +++ b/homeassistant/components/media_player/bluesound.py @@ -333,10 +333,10 @@ class BluesoundPlayer(MediaPlayerDevice): if response.status == 200: result = await response.text() - if len(result) < 1: - data = None - else: + if result: data = xmltodict.parse(result) + else: + data = None elif response.status == 595: _LOGGER.info("Status 595 returned, treating as timeout") raise BluesoundPlayer._TimeoutException() @@ -640,7 +640,7 @@ class BluesoundPlayer(MediaPlayerDevice): volume = self.volume_level if not volume: return None - return volume < 0.001 and volume >= 0 + return 0 <= volume < 0.001 @property def name(self): @@ -847,12 +847,12 @@ class BluesoundPlayer(MediaPlayerDevice): items = [x for x in self._preset_items if x['title'] == source] - if len(items) < 1: + if not items: items = [x for x in self._services_items if x['title'] == source] - if len(items) < 1: + if not items: items = [x for x in self._capture_items if x['title'] == source] - if len(items) < 1: + if not items: return selected_source = items[0] diff --git a/homeassistant/components/media_player/horizon.py b/homeassistant/components/media_player/horizon.py index 4b0f9d0cf21a..9be4143ef2ba 100644 --- a/homeassistant/components/media_player/horizon.py +++ b/homeassistant/components/media_player/horizon.py @@ -18,7 +18,7 @@ from homeassistant.const import (CONF_HOST, CONF_NAME, CONF_PORT, STATE_OFF, STATE_PAUSED, STATE_PLAYING) from homeassistant.exceptions import PlatformNotReady import homeassistant.helpers.config_validation as cv -import homeassistant.util as util +from homeassistant import util REQUIREMENTS = ['einder==0.3.1'] diff --git a/homeassistant/components/media_player/kodi.py b/homeassistant/components/media_player/kodi.py index 7fa8d5b3fe84..0585692e77d7 100644 --- a/homeassistant/components/media_player/kodi.py +++ b/homeassistant/components/media_player/kodi.py @@ -759,7 +759,7 @@ class KodiDevice(MediaPlayerDevice): @asyncio.coroutine def async_set_shuffle(self, shuffle): """Set shuffle mode, for the first player.""" - if len(self._players) < 1: + if not self._players: raise RuntimeError("Error: No active player.") yield from self.server.Player.SetShuffle( {"playerid": self._players[0]['playerid'], "shuffle": shuffle}) diff --git a/homeassistant/components/media_player/lg_netcast.py b/homeassistant/components/media_player/lg_netcast.py index df1ee662124b..955ba7ccb326 100644 --- a/homeassistant/components/media_player/lg_netcast.py +++ b/homeassistant/components/media_player/lg_netcast.py @@ -18,7 +18,7 @@ from homeassistant.components.media_player import ( from homeassistant.const import ( CONF_HOST, CONF_NAME, CONF_ACCESS_TOKEN, STATE_OFF, STATE_PLAYING, STATE_PAUSED, STATE_UNKNOWN) -import homeassistant.util as util +from homeassistant import util REQUIREMENTS = ['pylgnetcast-homeassistant==0.2.0.dev0'] diff --git a/homeassistant/components/media_player/vizio.py b/homeassistant/components/media_player/vizio.py index 81e4c3541d34..203a2c2b4089 100644 --- a/homeassistant/components/media_player/vizio.py +++ b/homeassistant/components/media_player/vizio.py @@ -18,7 +18,7 @@ from homeassistant.const import ( CONF_ACCESS_TOKEN, CONF_HOST, CONF_NAME, STATE_OFF, STATE_ON, STATE_UNKNOWN) from homeassistant.helpers import config_validation as cv -import homeassistant.util as util +from homeassistant import util REQUIREMENTS = ['pyvizio==0.0.3'] diff --git a/homeassistant/components/media_player/webostv.py b/homeassistant/components/media_player/webostv.py index 42d0ae85ab3c..362095daee66 100644 --- a/homeassistant/components/media_player/webostv.py +++ b/homeassistant/components/media_player/webostv.py @@ -24,7 +24,7 @@ from homeassistant.const import ( STATE_OFF, STATE_PAUSED, STATE_PLAYING, STATE_UNKNOWN) import homeassistant.helpers.config_validation as cv from homeassistant.helpers.script import Script -import homeassistant.util as util +from homeassistant import util REQUIREMENTS = ['pylgtv==0.1.7', 'websockets==3.2'] diff --git a/homeassistant/components/mqtt/discovery.py b/homeassistant/components/mqtt/discovery.py index 3916714b8d16..1cbe099ddde8 100644 --- a/homeassistant/components/mqtt/discovery.py +++ b/homeassistant/components/mqtt/discovery.py @@ -8,7 +8,7 @@ import json import logging import re -import homeassistant.components.mqtt as mqtt +from homeassistant.components import mqtt from homeassistant.helpers.discovery import async_load_platform from homeassistant.const import CONF_PLATFORM from homeassistant.components.mqtt import CONF_STATE_TOPIC diff --git a/homeassistant/components/mysensors/gateway.py b/homeassistant/components/mysensors/gateway.py index a7719a80d995..73222cb6be21 100644 --- a/homeassistant/components/mysensors/gateway.py +++ b/homeassistant/components/mysensors/gateway.py @@ -80,7 +80,7 @@ async def setup_gateways(hass, config): async def _get_gateway(hass, config, gateway_conf, persistence_file): """Return gateway after setup of the gateway.""" - import mysensors.mysensors as mysensors + from mysensors import mysensors conf = config[DOMAIN] persistence = conf[CONF_PERSISTENCE] diff --git a/homeassistant/components/remote/harmony.py b/homeassistant/components/remote/harmony.py index 842dce087e80..a63b73250357 100644 --- a/homeassistant/components/remote/harmony.py +++ b/homeassistant/components/remote/harmony.py @@ -10,7 +10,7 @@ import time import voluptuous as vol -import homeassistant.components.remote as remote +from homeassistant.components import remote from homeassistant.components.remote import ( ATTR_ACTIVITY, ATTR_DELAY_SECS, ATTR_DEVICE, ATTR_NUM_REPEATS, DEFAULT_DELAY_SECS, DOMAIN, PLATFORM_SCHEMA) diff --git a/homeassistant/components/remote/itach.py b/homeassistant/components/remote/itach.py index 78d277ca65fd..829a038953cd 100644 --- a/homeassistant/components/remote/itach.py +++ b/homeassistant/components/remote/itach.py @@ -10,7 +10,7 @@ import logging import voluptuous as vol import homeassistant.helpers.config_validation as cv -import homeassistant.components.remote as remote +from homeassistant.components import remote from homeassistant.const import ( DEVICE_DEFAULT_NAME, CONF_NAME, CONF_MAC, CONF_HOST, CONF_PORT, CONF_DEVICES) diff --git a/homeassistant/components/remote/kira.py b/homeassistant/components/remote/kira.py index 42d4ce770546..dc37eb760f7d 100644 --- a/homeassistant/components/remote/kira.py +++ b/homeassistant/components/remote/kira.py @@ -7,7 +7,7 @@ https://home-assistant.io/components/remote.kira/ import functools as ft import logging -import homeassistant.components.remote as remote +from homeassistant.components import remote from homeassistant.const import CONF_DEVICE, CONF_NAME from homeassistant.helpers.entity import Entity diff --git a/homeassistant/components/rpi_gpio.py b/homeassistant/components/rpi_gpio.py index 5cb7bb337ce9..824ec46d636d 100644 --- a/homeassistant/components/rpi_gpio.py +++ b/homeassistant/components/rpi_gpio.py @@ -19,7 +19,7 @@ DOMAIN = 'rpi_gpio' def setup(hass, config): """Set up the Raspberry PI GPIO component.""" - import RPi.GPIO as GPIO + from RPi import GPIO def cleanup_gpio(event): """Stuff to do before stopping.""" @@ -36,32 +36,32 @@ def setup(hass, config): def setup_output(port): """Set up a GPIO as output.""" - import RPi.GPIO as GPIO + from RPi import GPIO GPIO.setup(port, GPIO.OUT) def setup_input(port, pull_mode): """Set up a GPIO as input.""" - import RPi.GPIO as GPIO + from RPi import GPIO GPIO.setup(port, GPIO.IN, GPIO.PUD_DOWN if pull_mode == 'DOWN' else GPIO.PUD_UP) def write_output(port, value): """Write a value to a GPIO.""" - import RPi.GPIO as GPIO + from RPi import GPIO GPIO.output(port, value) def read_input(port): """Read a value from a GPIO.""" - import RPi.GPIO as GPIO + from RPi import GPIO return GPIO.input(port) def edge_detect(port, event_callback, bounce): """Add detection for RISING and FALLING events.""" - import RPi.GPIO as GPIO + from RPi import GPIO GPIO.add_event_detect( port, GPIO.BOTH, diff --git a/homeassistant/components/scene/litejet.py b/homeassistant/components/scene/litejet.py index 37fb58d8dc7a..87539e2dded9 100644 --- a/homeassistant/components/scene/litejet.py +++ b/homeassistant/components/scene/litejet.py @@ -6,7 +6,7 @@ https://home-assistant.io/components/scene.litejet/ """ import logging -import homeassistant.components.litejet as litejet +from homeassistant.components import litejet from homeassistant.components.scene import Scene DEPENDENCIES = ['litejet'] diff --git a/homeassistant/components/sensor/arduino.py b/homeassistant/components/sensor/arduino.py index f49d8e76f6c5..d4d8ea09d294 100644 --- a/homeassistant/components/sensor/arduino.py +++ b/homeassistant/components/sensor/arduino.py @@ -11,7 +11,7 @@ import logging import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA -import homeassistant.components.arduino as arduino +from homeassistant.components import arduino from homeassistant.const import CONF_NAME from homeassistant.helpers.entity import Entity import homeassistant.helpers.config_validation as cv diff --git a/homeassistant/components/sensor/arwn.py b/homeassistant/components/sensor/arwn.py index 7308cd4f791c..6b0d3e569d78 100644 --- a/homeassistant/components/sensor/arwn.py +++ b/homeassistant/components/sensor/arwn.py @@ -8,7 +8,7 @@ import asyncio import json import logging -import homeassistant.components.mqtt as mqtt +from homeassistant.components import mqtt from homeassistant.core import callback from homeassistant.const import TEMP_FAHRENHEIT, TEMP_CELSIUS from homeassistant.helpers.entity import Entity diff --git a/homeassistant/components/sensor/dht.py b/homeassistant/components/sensor/dht.py index b22e4df9a50b..c5c26bc5ffa0 100644 --- a/homeassistant/components/sensor/dht.py +++ b/homeassistant/components/sensor/dht.py @@ -128,7 +128,7 @@ class DHTSensor(Entity): temperature = data[SENSOR_TEMPERATURE] _LOGGER.debug("Temperature %.1f \u00b0C + offset %.1f", temperature, temperature_offset) - if (temperature >= -20) and (temperature < 80): + if -20 <= temperature < 80: self._state = round(temperature + temperature_offset, 1) if self.temp_unit == TEMP_FAHRENHEIT: self._state = round(celsius_to_fahrenheit(temperature), 1) @@ -136,7 +136,7 @@ class DHTSensor(Entity): humidity = data[SENSOR_HUMIDITY] _LOGGER.debug("Humidity %.1f%% + offset %.1f", humidity, humidity_offset) - if (humidity >= 0) and (humidity <= 100): + if 0 <= humidity <= 100: self._state = round(humidity + humidity_offset, 1) diff --git a/homeassistant/components/sensor/filter.py b/homeassistant/components/sensor/filter.py index 261f6e2b510a..df913ab6d804 100644 --- a/homeassistant/components/sensor/filter.py +++ b/homeassistant/components/sensor/filter.py @@ -23,7 +23,7 @@ import homeassistant.helpers.config_validation as cv from homeassistant.util.decorator import Registry from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import async_track_state_change -import homeassistant.components.history as history +from homeassistant.components import history import homeassistant.util.dt as dt_util _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/sensor/google_travel_time.py b/homeassistant/components/sensor/google_travel_time.py index e7d258727019..d14a70ecc84c 100644 --- a/homeassistant/components/sensor/google_travel_time.py +++ b/homeassistant/components/sensor/google_travel_time.py @@ -17,7 +17,7 @@ from homeassistant.const import ( ATTR_LONGITUDE, CONF_MODE) from homeassistant.util import Throttle import homeassistant.helpers.config_validation as cv -import homeassistant.helpers.location as location +from homeassistant.helpers import location import homeassistant.util.dt as dt_util REQUIREMENTS = ['googlemaps==2.5.1'] diff --git a/homeassistant/components/sensor/google_wifi.py b/homeassistant/components/sensor/google_wifi.py index c070a3e990f7..388af9b3ce76 100644 --- a/homeassistant/components/sensor/google_wifi.py +++ b/homeassistant/components/sensor/google_wifi.py @@ -10,7 +10,7 @@ from datetime import timedelta import voluptuous as vol import requests -import homeassistant.util.dt as dt +from homeassistant.util import dt import homeassistant.helpers.config_validation as cv from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( diff --git a/homeassistant/components/sensor/gtfs.py b/homeassistant/components/sensor/gtfs.py index 93e15b9cd5e2..120fe8fdb225 100644 --- a/homeassistant/components/sensor/gtfs.py +++ b/homeassistant/components/sensor/gtfs.py @@ -176,7 +176,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): gtfs = pygtfs.Schedule(joined_path) # pylint: disable=no-member - if len(gtfs.feeds) < 1: + if not gtfs.feeds: pygtfs.append_feed(gtfs, os.path.join(gtfs_dir, data)) add_devices([GTFSDepartureSensor(gtfs, name, origin, destination, offset)]) diff --git a/homeassistant/components/sensor/history_stats.py b/homeassistant/components/sensor/history_stats.py index 7af858b9d94b..f1f12b6ecabd 100644 --- a/homeassistant/components/sensor/history_stats.py +++ b/homeassistant/components/sensor/history_stats.py @@ -10,7 +10,7 @@ import math import voluptuous as vol -import homeassistant.components.history as history +from homeassistant.components import history import homeassistant.helpers.config_validation as cv import homeassistant.util.dt as dt_util from homeassistant.components.sensor import PLATFORM_SCHEMA diff --git a/homeassistant/components/sensor/modbus.py b/homeassistant/components/sensor/modbus.py index c4014fbd1dd0..5f404ccd5f7f 100644 --- a/homeassistant/components/sensor/modbus.py +++ b/homeassistant/components/sensor/modbus.py @@ -9,7 +9,7 @@ import struct import voluptuous as vol -import homeassistant.components.modbus as modbus +from homeassistant.components import modbus from homeassistant.const import ( CONF_NAME, CONF_OFFSET, CONF_UNIT_OF_MEASUREMENT, CONF_SLAVE, CONF_STRUCTURE) diff --git a/homeassistant/components/sensor/mold_indicator.py b/homeassistant/components/sensor/mold_indicator.py index 2822ce01dca4..62d8af2ee8fc 100644 --- a/homeassistant/components/sensor/mold_indicator.py +++ b/homeassistant/components/sensor/mold_indicator.py @@ -10,7 +10,7 @@ import math import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA -import homeassistant.util as util +from homeassistant import util from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import track_state_change from homeassistant.const import ( diff --git a/homeassistant/components/sensor/mqtt.py b/homeassistant/components/sensor/mqtt.py index 997fd312a6a5..0e29c55d39d1 100644 --- a/homeassistant/components/sensor/mqtt.py +++ b/homeassistant/components/sensor/mqtt.py @@ -20,7 +20,7 @@ from homeassistant.const import ( CONF_FORCE_UPDATE, CONF_NAME, CONF_VALUE_TEMPLATE, STATE_UNKNOWN, CONF_UNIT_OF_MEASUREMENT, CONF_ICON, CONF_DEVICE_CLASS) from homeassistant.helpers.entity import Entity -import homeassistant.components.mqtt as mqtt +from homeassistant.components import mqtt import homeassistant.helpers.config_validation as cv from homeassistant.helpers.typing import HomeAssistantType, ConfigType from homeassistant.helpers.event import async_track_point_in_utc_time diff --git a/homeassistant/components/sensor/mqtt_room.py b/homeassistant/components/sensor/mqtt_room.py index 2c0f8eb4d5a7..2a61c1143eee 100644 --- a/homeassistant/components/sensor/mqtt_room.py +++ b/homeassistant/components/sensor/mqtt_room.py @@ -11,7 +11,7 @@ from datetime import timedelta import voluptuous as vol -import homeassistant.components.mqtt as mqtt +from homeassistant.components import mqtt import homeassistant.helpers.config_validation as cv from homeassistant.components.mqtt import CONF_STATE_TOPIC from homeassistant.components.sensor import PLATFORM_SCHEMA diff --git a/homeassistant/components/sensor/pilight.py b/homeassistant/components/sensor/pilight.py index 9784cc3dc4ca..c30f15750497 100644 --- a/homeassistant/components/sensor/pilight.py +++ b/homeassistant/components/sensor/pilight.py @@ -12,7 +12,7 @@ from homeassistant.const import ( CONF_NAME, STATE_UNKNOWN, CONF_UNIT_OF_MEASUREMENT, CONF_PAYLOAD) from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.helpers.entity import Entity -import homeassistant.components.pilight as pilight +from homeassistant.components import pilight import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/sensor/rfxtrx.py b/homeassistant/components/sensor/rfxtrx.py index a5a6eb5f07b7..b410e7e860a8 100644 --- a/homeassistant/components/sensor/rfxtrx.py +++ b/homeassistant/components/sensor/rfxtrx.py @@ -8,7 +8,7 @@ import logging import voluptuous as vol -import homeassistant.components.rfxtrx as rfxtrx +from homeassistant.components import rfxtrx from homeassistant.components.rfxtrx import ( ATTR_DATA_TYPE, ATTR_FIRE_EVENT, CONF_AUTOMATIC_ADD, CONF_DATA_TYPE, CONF_DEVICES, CONF_FIRE_EVENT, DATA_TYPES) diff --git a/homeassistant/components/sensor/season.py b/homeassistant/components/sensor/season.py index b04b7727e407..f06f6a896e73 100644 --- a/homeassistant/components/sensor/season.py +++ b/homeassistant/components/sensor/season.py @@ -12,7 +12,7 @@ import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import CONF_TYPE from homeassistant.helpers.entity import Entity -import homeassistant.util as util +from homeassistant import util REQUIREMENTS = ['ephem==3.7.6.0'] diff --git a/homeassistant/components/sensor/tellstick.py b/homeassistant/components/sensor/tellstick.py index de929aa09427..2fc67e57162c 100644 --- a/homeassistant/components/sensor/tellstick.py +++ b/homeassistant/components/sensor/tellstick.py @@ -39,7 +39,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_devices, discovery_info=None): """Set up the Tellstick sensors.""" - import tellcore.telldus as telldus + from tellcore import telldus import tellcore.constants as tellcore_constants sensor_value_descriptions = { diff --git a/homeassistant/components/sensor/thinkingcleaner.py b/homeassistant/components/sensor/thinkingcleaner.py index 83cf799e3cdc..0b936d8c8c78 100644 --- a/homeassistant/components/sensor/thinkingcleaner.py +++ b/homeassistant/components/sensor/thinkingcleaner.py @@ -7,7 +7,7 @@ https://home-assistant.io/components/sensor.thinkingcleaner/ import logging from datetime import timedelta -import homeassistant.util as util +from homeassistant import util from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/sensor/toon.py b/homeassistant/components/sensor/toon.py index cecce0d270f8..a8875f6904c7 100644 --- a/homeassistant/components/sensor/toon.py +++ b/homeassistant/components/sensor/toon.py @@ -5,7 +5,7 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.toon/ """ import logging -import datetime as datetime +import datetime from homeassistant.helpers.entity import Entity import homeassistant.components.toon as toon_main diff --git a/homeassistant/components/sensor/waze_travel_time.py b/homeassistant/components/sensor/waze_travel_time.py index 79b14c94b881..70c169a1b7c9 100644 --- a/homeassistant/components/sensor/waze_travel_time.py +++ b/homeassistant/components/sensor/waze_travel_time.py @@ -14,7 +14,7 @@ from homeassistant.const import ( ATTR_ATTRIBUTION, CONF_NAME, CONF_REGION, EVENT_HOMEASSISTANT_START, ATTR_LATITUDE, ATTR_LONGITUDE) import homeassistant.helpers.config_validation as cv -import homeassistant.helpers.location as location +from homeassistant.helpers import location from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle diff --git a/homeassistant/components/sensor/zabbix.py b/homeassistant/components/sensor/zabbix.py index baeed3915577..21a3030b79b4 100644 --- a/homeassistant/components/sensor/zabbix.py +++ b/homeassistant/components/sensor/zabbix.py @@ -8,7 +8,7 @@ import logging import voluptuous as vol -import homeassistant.components.zabbix as zabbix +from homeassistant.components import zabbix import homeassistant.helpers.config_validation as cv from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import CONF_NAME diff --git a/homeassistant/components/sensor/zoneminder.py b/homeassistant/components/sensor/zoneminder.py index 1189a53bb098..60b6a018fc2b 100644 --- a/homeassistant/components/sensor/zoneminder.py +++ b/homeassistant/components/sensor/zoneminder.py @@ -12,7 +12,7 @@ from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import STATE_UNKNOWN from homeassistant.const import CONF_MONITORED_CONDITIONS from homeassistant.helpers.entity import Entity -import homeassistant.components.zoneminder as zoneminder +from homeassistant.components import zoneminder import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/snips.py b/homeassistant/components/snips.py index 4f50c6beaaa2..342908191066 100644 --- a/homeassistant/components/snips.py +++ b/homeassistant/components/snips.py @@ -12,7 +12,7 @@ import voluptuous as vol from homeassistant.core import callback from homeassistant.helpers import intent, config_validation as cv -import homeassistant.components.mqtt as mqtt +from homeassistant.components import mqtt DOMAIN = 'snips' DEPENDENCIES = ['mqtt'] diff --git a/homeassistant/components/switch/arduino.py b/homeassistant/components/switch/arduino.py index 1547f4f1dee3..2bcb04c566e5 100644 --- a/homeassistant/components/switch/arduino.py +++ b/homeassistant/components/switch/arduino.py @@ -10,7 +10,7 @@ import logging import voluptuous as vol -import homeassistant.components.arduino as arduino +from homeassistant.components import arduino from homeassistant.components.switch import (SwitchDevice, PLATFORM_SCHEMA) from homeassistant.const import CONF_NAME import homeassistant.helpers.config_validation as cv diff --git a/homeassistant/components/switch/bbb_gpio.py b/homeassistant/components/switch/bbb_gpio.py index 5412f559b739..94952ac736b7 100644 --- a/homeassistant/components/switch/bbb_gpio.py +++ b/homeassistant/components/switch/bbb_gpio.py @@ -9,7 +9,7 @@ import logging import voluptuous as vol from homeassistant.components.switch import PLATFORM_SCHEMA -import homeassistant.components.bbb_gpio as bbb_gpio +from homeassistant.components import bbb_gpio from homeassistant.const import (DEVICE_DEFAULT_NAME, CONF_NAME) from homeassistant.helpers.entity import ToggleEntity import homeassistant.helpers.config_validation as cv diff --git a/homeassistant/components/switch/insteon_local.py b/homeassistant/components/switch/insteon_local.py index 4456436ea61a..c4c8a8546706 100644 --- a/homeassistant/components/switch/insteon_local.py +++ b/homeassistant/components/switch/insteon_local.py @@ -8,7 +8,7 @@ import logging from datetime import timedelta from homeassistant.components.switch import SwitchDevice -import homeassistant.util as util +from homeassistant import util _CONFIGURING = {} _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/switch/litejet.py b/homeassistant/components/switch/litejet.py index 1e7c46733ad4..79ef4a5fd7f4 100644 --- a/homeassistant/components/switch/litejet.py +++ b/homeassistant/components/switch/litejet.py @@ -6,7 +6,7 @@ https://home-assistant.io/components/switch.litejet/ """ import logging -import homeassistant.components.litejet as litejet +from homeassistant.components import litejet from homeassistant.components.switch import SwitchDevice DEPENDENCIES = ['litejet'] diff --git a/homeassistant/components/switch/modbus.py b/homeassistant/components/switch/modbus.py index ca70c2127740..94e1d7ea6d63 100644 --- a/homeassistant/components/switch/modbus.py +++ b/homeassistant/components/switch/modbus.py @@ -7,7 +7,7 @@ https://home-assistant.io/components/switch.modbus/ import logging import voluptuous as vol -import homeassistant.components.modbus as modbus +from homeassistant.components import modbus from homeassistant.const import ( CONF_NAME, CONF_SLAVE, CONF_COMMAND_ON, CONF_COMMAND_OFF) from homeassistant.helpers.entity import ToggleEntity diff --git a/homeassistant/components/switch/mqtt.py b/homeassistant/components/switch/mqtt.py index 1075888e1996..4442b186e301 100644 --- a/homeassistant/components/switch/mqtt.py +++ b/homeassistant/components/switch/mqtt.py @@ -18,7 +18,7 @@ from homeassistant.components.switch import SwitchDevice from homeassistant.const import ( CONF_NAME, CONF_OPTIMISTIC, CONF_VALUE_TEMPLATE, CONF_PAYLOAD_OFF, CONF_PAYLOAD_ON, CONF_ICON, STATE_ON) -import homeassistant.components.mqtt as mqtt +from homeassistant.components import mqtt import homeassistant.helpers.config_validation as cv from homeassistant.helpers.restore_state import async_get_last_state diff --git a/homeassistant/components/switch/pilight.py b/homeassistant/components/switch/pilight.py index 57fa4b00c987..4e5f1c47e9ff 100644 --- a/homeassistant/components/switch/pilight.py +++ b/homeassistant/components/switch/pilight.py @@ -10,7 +10,7 @@ import logging import voluptuous as vol import homeassistant.helpers.config_validation as cv -import homeassistant.components.pilight as pilight +from homeassistant.components import pilight from homeassistant.components.switch import (SwitchDevice, PLATFORM_SCHEMA) from homeassistant.const import (CONF_NAME, CONF_ID, CONF_SWITCHES, CONF_STATE, CONF_PROTOCOL, STATE_ON) diff --git a/homeassistant/components/switch/pulseaudio_loopback.py b/homeassistant/components/switch/pulseaudio_loopback.py index e25368f3c5cf..06f2ee5f550c 100644 --- a/homeassistant/components/switch/pulseaudio_loopback.py +++ b/homeassistant/components/switch/pulseaudio_loopback.py @@ -11,7 +11,7 @@ from datetime import timedelta import voluptuous as vol -import homeassistant.util as util +from homeassistant import util from homeassistant.components.switch import (SwitchDevice, PLATFORM_SCHEMA) from homeassistant.const import (CONF_NAME, CONF_HOST, CONF_PORT) import homeassistant.helpers.config_validation as cv diff --git a/homeassistant/components/switch/rfxtrx.py b/homeassistant/components/switch/rfxtrx.py index 68e916120088..17b5c8e40d55 100644 --- a/homeassistant/components/switch/rfxtrx.py +++ b/homeassistant/components/switch/rfxtrx.py @@ -8,7 +8,7 @@ import logging import voluptuous as vol -import homeassistant.components.rfxtrx as rfxtrx +from homeassistant.components import rfxtrx from homeassistant.components.switch import SwitchDevice, PLATFORM_SCHEMA from homeassistant.components.rfxtrx import ( CONF_AUTOMATIC_ADD, CONF_FIRE_EVENT, DEFAULT_SIGNAL_REPETITIONS, diff --git a/homeassistant/components/switch/rpi_gpio.py b/homeassistant/components/switch/rpi_gpio.py index 26de2a78e189..300af4be61d1 100644 --- a/homeassistant/components/switch/rpi_gpio.py +++ b/homeassistant/components/switch/rpi_gpio.py @@ -9,7 +9,7 @@ import logging import voluptuous as vol from homeassistant.components.switch import PLATFORM_SCHEMA -import homeassistant.components.rpi_gpio as rpi_gpio +from homeassistant.components import rpi_gpio from homeassistant.const import DEVICE_DEFAULT_NAME from homeassistant.helpers.entity import ToggleEntity import homeassistant.helpers.config_validation as cv diff --git a/homeassistant/components/switch/rpi_pfio.py b/homeassistant/components/switch/rpi_pfio.py index 3031b1e02904..dad0c7c59ba3 100644 --- a/homeassistant/components/switch/rpi_pfio.py +++ b/homeassistant/components/switch/rpi_pfio.py @@ -8,7 +8,7 @@ import logging import voluptuous as vol -import homeassistant.components.rpi_pfio as rpi_pfio +from homeassistant.components import rpi_pfio from homeassistant.components.switch import PLATFORM_SCHEMA from homeassistant.const import ATTR_NAME, DEVICE_DEFAULT_NAME import homeassistant.helpers.config_validation as cv diff --git a/homeassistant/components/switch/scsgate.py b/homeassistant/components/switch/scsgate.py index 8b2734612def..adc08a65e711 100644 --- a/homeassistant/components/switch/scsgate.py +++ b/homeassistant/components/switch/scsgate.py @@ -8,7 +8,7 @@ import logging import voluptuous as vol -import homeassistant.components.scsgate as scsgate +from homeassistant.components import scsgate from homeassistant.components.switch import (SwitchDevice, PLATFORM_SCHEMA) from homeassistant.const import ( ATTR_ENTITY_ID, ATTR_STATE, CONF_NAME, CONF_DEVICES) diff --git a/homeassistant/components/switch/thinkingcleaner.py b/homeassistant/components/switch/thinkingcleaner.py index 37c2f52e228d..0753435cfba1 100644 --- a/homeassistant/components/switch/thinkingcleaner.py +++ b/homeassistant/components/switch/thinkingcleaner.py @@ -8,8 +8,7 @@ import time import logging from datetime import timedelta -import homeassistant.util as util - +from homeassistant import util from homeassistant.const import (STATE_ON, STATE_OFF) from homeassistant.helpers.entity import ToggleEntity diff --git a/homeassistant/components/switch/wemo.py b/homeassistant/components/switch/wemo.py index c18ad492d40a..c189c312b25c 100644 --- a/homeassistant/components/switch/wemo.py +++ b/homeassistant/components/switch/wemo.py @@ -35,7 +35,7 @@ WEMO_STANDBY = 8 def setup_platform(hass, config, add_devices_callback, discovery_info=None): """Set up discovered WeMo switches.""" - import pywemo.discovery as discovery + from pywemo import discovery if discovery_info is not None: location = discovery_info['ssdp_description'] diff --git a/homeassistant/components/switch/zoneminder.py b/homeassistant/components/switch/zoneminder.py index adf3bf2d9bd6..fa32843eb4b9 100644 --- a/homeassistant/components/switch/zoneminder.py +++ b/homeassistant/components/switch/zoneminder.py @@ -10,7 +10,7 @@ import voluptuous as vol from homeassistant.components.switch import (SwitchDevice, PLATFORM_SCHEMA) from homeassistant.const import (CONF_COMMAND_ON, CONF_COMMAND_OFF) -import homeassistant.components.zoneminder as zoneminder +from homeassistant.components import zoneminder import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/thingspeak.py b/homeassistant/components/thingspeak.py index a21d44527a13..9a876a87683b 100644 --- a/homeassistant/components/thingspeak.py +++ b/homeassistant/components/thingspeak.py @@ -11,9 +11,8 @@ import voluptuous as vol from homeassistant.const import ( CONF_API_KEY, CONF_ID, CONF_WHITELIST, STATE_UNAVAILABLE, STATE_UNKNOWN) -from homeassistant.helpers import state as state_helper +from homeassistant.helpers import event, state as state_helper import homeassistant.helpers.config_validation as cv -import homeassistant.helpers.event as event REQUIREMENTS = ['thingspeak==0.4.1'] diff --git a/homeassistant/components/vacuum/mqtt.py b/homeassistant/components/vacuum/mqtt.py index 8c2f110257f7..fd80f4cdbfba 100644 --- a/homeassistant/components/vacuum/mqtt.py +++ b/homeassistant/components/vacuum/mqtt.py @@ -9,7 +9,7 @@ import logging import voluptuous as vol -import homeassistant.components.mqtt as mqtt +from homeassistant.components import mqtt from homeassistant.components.mqtt import MqttAvailability from homeassistant.components.vacuum import ( SUPPORT_BATTERY, SUPPORT_CLEAN_SPOT, SUPPORT_FAN_SPEED, diff --git a/homeassistant/core.py b/homeassistant/core.py index bfc3aed194f4..dac16111de78 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -38,9 +38,9 @@ from homeassistant.exceptions import ( from homeassistant.util.async_ import ( run_coroutine_threadsafe, run_callback_threadsafe, fire_coroutine_threadsafe) -import homeassistant.util as util +from homeassistant import util import homeassistant.util.dt as dt_util -import homeassistant.util.location as location +from homeassistant.util import location from homeassistant.util.unit_system import UnitSystem, METRIC_SYSTEM # NOQA # Typing imports that create a circular dependency diff --git a/homeassistant/helpers/service.py b/homeassistant/helpers/service.py index 7ab90b7a0489..e10d608fc62d 100644 --- a/homeassistant/helpers/service.py +++ b/homeassistant/helpers/service.py @@ -123,7 +123,7 @@ async def async_get_all_descriptions(hass): def domain_yaml_file(domain): """Return the services.yaml location for a domain.""" if domain == ha.DOMAIN: - import homeassistant.components as components + from homeassistant import components component_path = path.dirname(components.__file__) else: component_path = path.dirname(get_component(hass, domain).__file__) diff --git a/homeassistant/scripts/check_config.py b/homeassistant/scripts/check_config.py index 69b1bf21c088..1924de88aafd 100644 --- a/homeassistant/scripts/check_config.py +++ b/homeassistant/scripts/check_config.py @@ -18,7 +18,7 @@ from homeassistant.config import ( CONF_PACKAGES, merge_packages_config, _format_config_error, find_config_file, load_yaml_config_file, extract_domain_configs, config_per_platform) -import homeassistant.util.yaml as yaml +from homeassistant.util import yaml from homeassistant.exceptions import HomeAssistantError REQUIREMENTS = ('colorlog==3.1.4',) From e62e2bb1315ff641f670faa64819216cd712c194 Mon Sep 17 00:00:00 2001 From: Andrey Date: Wed, 18 Jul 2018 13:16:27 +0300 Subject: [PATCH 019/113] Make sure that only pypi dependencies are used (#15490) --- script/gen_requirements_all.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/script/gen_requirements_all.py b/script/gen_requirements_all.py index 9a5b4dd1a43e..d92502de0782 100755 --- a/script/gen_requirements_all.py +++ b/script/gen_requirements_all.py @@ -182,6 +182,10 @@ def gather_modules(): for req in module.REQUIREMENTS: if req in IGNORE_REQ: continue + if '://' in req: + errors.append( + "{}[Only pypi dependencies are allowed: {}]".format( + package, req)) if req.partition('==')[1] == '' and req not in IGNORE_PIN: errors.append( "{}[Please pin requirement {}, see {}]".format( @@ -257,7 +261,7 @@ def write_requirements_file(data): def write_test_requirements_file(data): - """Write the modules to the requirements_all.txt.""" + """Write the modules to the requirements_test_all.txt.""" with open('requirements_test_all.txt', 'w+', newline="\n") as req_file: req_file.write(data) @@ -275,7 +279,7 @@ def validate_requirements_file(data): def validate_requirements_test_file(data): - """Validate if requirements_all.txt is up to date.""" + """Validate if requirements_test_all.txt is up to date.""" with open('requirements_test_all.txt', 'r') as req_file: return data == req_file.read() From e427f9ee38bf2dfc82d90ecabcda2a1de3ff1ee9 Mon Sep 17 00:00:00 2001 From: Anders Melchiorsen Date: Wed, 18 Jul 2018 12:18:22 +0200 Subject: [PATCH 020/113] RFC: Only use supported light properties (#15484) * Only use supported light properties * Fix tests --- homeassistant/components/light/__init__.py | 47 +++++++++----------- homeassistant/components/light/mqtt.py | 2 +- tests/components/light/test_group.py | 19 ++++---- tests/components/light/test_mqtt.py | 7 +++ tests/components/light/test_mqtt_template.py | 2 + 5 files changed, 43 insertions(+), 34 deletions(-) diff --git a/homeassistant/components/light/__init__.py b/homeassistant/components/light/__init__.py index b8a976072155..bd428a84bedc 100644 --- a/homeassistant/components/light/__init__.py +++ b/homeassistant/components/light/__init__.py @@ -83,17 +83,6 @@ COLOR_GROUP = "Color descriptors" LIGHT_PROFILES_FILE = "light_profiles.csv" -PROP_TO_ATTR = { - 'brightness': ATTR_BRIGHTNESS, - 'color_temp': ATTR_COLOR_TEMP, - 'min_mireds': ATTR_MIN_MIREDS, - 'max_mireds': ATTR_MAX_MIREDS, - 'hs_color': ATTR_HS_COLOR, - 'white_value': ATTR_WHITE_VALUE, - 'effect_list': ATTR_EFFECT_LIST, - 'effect': ATTR_EFFECT, -} - # Service call validation schemas VALID_TRANSITION = vol.All(vol.Coerce(float), vol.Clamp(min=0, max=6553)) VALID_BRIGHTNESS = vol.All(vol.Coerce(int), vol.Clamp(min=0, max=255)) @@ -494,29 +483,37 @@ class Light(ToggleEntity): def state_attributes(self): """Return optional state attributes.""" data = {} + supported_features = self.supported_features - if self.supported_features & SUPPORT_COLOR_TEMP: + if supported_features & SUPPORT_COLOR_TEMP: data[ATTR_MIN_MIREDS] = self.min_mireds data[ATTR_MAX_MIREDS] = self.max_mireds if self.is_on: - for prop, attr in PROP_TO_ATTR.items(): - value = getattr(self, prop) - if value is not None: - data[attr] = value + if supported_features & SUPPORT_BRIGHTNESS: + data[ATTR_BRIGHTNESS] = self.brightness - # Expose current color also as RGB and XY - if ATTR_HS_COLOR in data: - data[ATTR_RGB_COLOR] = color_util.color_hs_to_RGB( - *data[ATTR_HS_COLOR]) - data[ATTR_XY_COLOR] = color_util.color_hs_to_xy( - *data[ATTR_HS_COLOR]) + if supported_features & SUPPORT_COLOR_TEMP: + data[ATTR_COLOR_TEMP] = self.color_temp + + if self.supported_features & SUPPORT_COLOR and self.hs_color: + # pylint: disable=unsubscriptable-object,not-an-iterable + hs_color = self.hs_color data[ATTR_HS_COLOR] = ( - round(data[ATTR_HS_COLOR][0], 3), - round(data[ATTR_HS_COLOR][1], 3), + round(hs_color[0], 3), + round(hs_color[1], 3), ) + data[ATTR_RGB_COLOR] = color_util.color_hs_to_RGB(*hs_color) + data[ATTR_XY_COLOR] = color_util.color_hs_to_xy(*hs_color) - return data + if supported_features & SUPPORT_WHITE_VALUE: + data[ATTR_WHITE_VALUE] = self.white_value + + if supported_features & SUPPORT_EFFECT: + data[ATTR_EFFECT_LIST] = self.effect_list + data[ATTR_EFFECT] = self.effect + + return {key: val for key, val in data.items() if val is not None} @property def supported_features(self): diff --git a/homeassistant/components/light/mqtt.py b/homeassistant/components/light/mqtt.py index cfc1ce27b949..09fa094c1b2c 100644 --- a/homeassistant/components/light/mqtt.py +++ b/homeassistant/components/light/mqtt.py @@ -205,7 +205,7 @@ class MqttLight(MqttAvailability, Light): topic[CONF_COLOR_TEMP_COMMAND_TOPIC] is not None and SUPPORT_COLOR_TEMP) self._supported_features |= ( - topic[CONF_EFFECT_STATE_TOPIC] is not None and + topic[CONF_EFFECT_COMMAND_TOPIC] is not None and SUPPORT_EFFECT) self._supported_features |= ( topic[CONF_WHITE_VALUE_COMMAND_TOPIC] is not None and diff --git a/tests/components/light/test_group.py b/tests/components/light/test_group.py index 26b949720d94..901535c54651 100644 --- a/tests/components/light/test_group.py +++ b/tests/components/light/test_group.py @@ -200,21 +200,24 @@ async def test_effect_list(hass): }}) hass.states.async_set('light.test1', 'on', - {'effect_list': ['None', 'Random', 'Colorloop']}) + {'effect_list': ['None', 'Random', 'Colorloop'], + 'supported_features': 4}) await hass.async_block_till_done() state = hass.states.get('light.light_group') assert set(state.attributes['effect_list']) == { 'None', 'Random', 'Colorloop'} hass.states.async_set('light.test2', 'on', - {'effect_list': ['None', 'Random', 'Rainbow']}) + {'effect_list': ['None', 'Random', 'Rainbow'], + 'supported_features': 4}) await hass.async_block_till_done() state = hass.states.get('light.light_group') assert set(state.attributes['effect_list']) == { 'None', 'Random', 'Colorloop', 'Rainbow'} hass.states.async_set('light.test1', 'off', - {'effect_list': ['None', 'Colorloop', 'Seven']}) + {'effect_list': ['None', 'Colorloop', 'Seven'], + 'supported_features': 4}) await hass.async_block_till_done() state = hass.states.get('light.light_group') assert set(state.attributes['effect_list']) == { @@ -229,27 +232,27 @@ async def test_effect(hass): }}) hass.states.async_set('light.test1', 'on', - {'effect': 'None', 'supported_features': 2}) + {'effect': 'None', 'supported_features': 6}) await hass.async_block_till_done() state = hass.states.get('light.light_group') assert state.attributes['effect'] == 'None' hass.states.async_set('light.test2', 'on', - {'effect': 'None', 'supported_features': 2}) + {'effect': 'None', 'supported_features': 6}) await hass.async_block_till_done() state = hass.states.get('light.light_group') assert state.attributes['effect'] == 'None' hass.states.async_set('light.test3', 'on', - {'effect': 'Random', 'supported_features': 2}) + {'effect': 'Random', 'supported_features': 6}) await hass.async_block_till_done() state = hass.states.get('light.light_group') assert state.attributes['effect'] == 'None' hass.states.async_set('light.test1', 'off', - {'effect': 'None', 'supported_features': 2}) + {'effect': 'None', 'supported_features': 6}) hass.states.async_set('light.test2', 'off', - {'effect': 'None', 'supported_features': 2}) + {'effect': 'None', 'supported_features': 6}) await hass.async_block_till_done() state = hass.states.get('light.light_group') assert state.attributes['effect'] == 'Random' diff --git a/tests/components/light/test_mqtt.py b/tests/components/light/test_mqtt.py index 7d6dd65e90a5..404d60c0a2e0 100644 --- a/tests/components/light/test_mqtt.py +++ b/tests/components/light/test_mqtt.py @@ -415,6 +415,12 @@ class TestLightMQTT(unittest.TestCase): 'name': 'test', 'state_topic': 'test_light_rgb/status', 'command_topic': 'test_light_rgb/set', + 'brightness_command_topic': 'test_light_rgb/brightness/set', + 'rgb_command_topic': 'test_light_rgb/rgb/set', + 'color_temp_command_topic': 'test_light_rgb/color_temp/set', + 'effect_command_topic': 'test_light_rgb/effect/set', + 'white_value_command_topic': 'test_light_rgb/white_value/set', + 'xy_command_topic': 'test_light_rgb/xy/set', 'brightness_state_topic': 'test_light_rgb/brightness/status', 'color_temp_state_topic': 'test_light_rgb/color_temp/status', 'effect_state_topic': 'test_light_rgb/effect/status', @@ -475,6 +481,7 @@ class TestLightMQTT(unittest.TestCase): 'effect_command_topic': 'test_light_rgb/effect/set', 'white_value_command_topic': 'test_light_rgb/white_value/set', 'xy_command_topic': 'test_light_rgb/xy/set', + 'effect_list': ['colorloop', 'random'], 'qos': 2, 'payload_on': 'on', 'payload_off': 'off' diff --git a/tests/components/light/test_mqtt_template.py b/tests/components/light/test_mqtt_template.py index e1c3da50e7e5..1cf09f2ccb5a 100644 --- a/tests/components/light/test_mqtt_template.py +++ b/tests/components/light/test_mqtt_template.py @@ -228,6 +228,8 @@ class TestLightMQTTTemplate(unittest.TestCase): '{{ green|d }}-' '{{ blue|d }}', 'command_off_template': 'off', + 'effect_list': ['colorloop', 'random'], + 'effect_command_topic': 'test_light_rgb/effect/set', 'qos': 2 } }) From 9c5bbfe96d8f20b6221238847c26bd5bfbbf1774 Mon Sep 17 00:00:00 2001 From: Mattias Welponer Date: Wed, 18 Jul 2018 12:19:08 +0200 Subject: [PATCH 021/113] Cleanup of HomematicIP Cloud code (#15475) * Check if device supports lowBat and shows it only if battery is low * Show empty battery icon if lowBat is true * Default return None * Sabotage attribute and icon if device has this feature * Bug fix and cleanup * Use dedicated function for security state * Cleanup of sensor attributes and icons * Empty --- .../alarm_control_panel/homematicip_cloud.py | 18 +++++++----------- .../components/homematicip_cloud/device.py | 19 +++++++++++++++---- .../components/sensor/homematicip_cloud.py | 7 ++----- 3 files changed, 24 insertions(+), 20 deletions(-) diff --git a/homeassistant/components/alarm_control_panel/homematicip_cloud.py b/homeassistant/components/alarm_control_panel/homematicip_cloud.py index 893fa76c44b4..43cb44949787 100644 --- a/homeassistant/components/alarm_control_panel/homematicip_cloud.py +++ b/homeassistant/components/alarm_control_panel/homematicip_cloud.py @@ -20,7 +20,6 @@ DEPENDENCIES = ['homematicip_cloud'] _LOGGER = logging.getLogger(__name__) -HMIP_OPEN = 'OPEN' HMIP_ZONE_AWAY = 'EXTERNAL' HMIP_ZONE_HOME = 'INTERNAL' @@ -57,14 +56,18 @@ class HomematicipSecurityZone(HomematicipGenericDevice, AlarmControlPanel): @property def state(self): """Return the state of the device.""" + from homematicip.base.enums import WindowState + if self._device.active: if (self._device.sabotage or self._device.motionDetected or - self._device.windowState == HMIP_OPEN): + self._device.windowState == WindowState.OPEN): return STATE_ALARM_TRIGGERED - if self._device.label == HMIP_ZONE_HOME: + active = self._home.get_security_zones_activation() + if active == (True, True): + return STATE_ALARM_ARMED_AWAY + elif active == (False, True): return STATE_ALARM_ARMED_HOME - return STATE_ALARM_ARMED_AWAY return STATE_ALARM_DISARMED @@ -79,10 +82,3 @@ class HomematicipSecurityZone(HomematicipGenericDevice, AlarmControlPanel): async def async_alarm_arm_away(self, code=None): """Send arm away command.""" await self._home.set_security_zones_activation(True, True) - - @property - def device_state_attributes(self): - """Return the state attributes of the alarm control device.""" - # The base class is loading the battery property, but device doesn't - # have this property - base class needs clean-up. - return None diff --git a/homeassistant/components/homematicip_cloud/device.py b/homeassistant/components/homematicip_cloud/device.py index 94fe5f40be8d..bb21e1df3d5f 100644 --- a/homeassistant/components/homematicip_cloud/device.py +++ b/homeassistant/components/homematicip_cloud/device.py @@ -62,10 +62,21 @@ class HomematicipGenericDevice(Entity): """Device available.""" return not self._device.unreach + @property + def icon(self): + """Return the icon.""" + if hasattr(self._device, 'lowBat') and self._device.lowBat: + return 'mdi:battery-outline' + if hasattr(self._device, 'sabotage') and self._device.sabotage: + return 'mdi:alert' + return None + @property def device_state_attributes(self): """Return the state attributes of the generic device.""" - return { - ATTR_LOW_BATTERY: self._device.lowBat, - ATTR_MODEL_TYPE: self._device.modelType - } + attr = {ATTR_MODEL_TYPE: self._device.modelType} + if hasattr(self._device, 'lowBat') and self._device.lowBat: + attr.update({ATTR_LOW_BATTERY: self._device.lowBat}) + if hasattr(self._device, 'sabotage') and self._device.sabotage: + attr.update({ATTR_SABOTAGE: self._device.sabotage}) + return attr diff --git a/homeassistant/components/sensor/homematicip_cloud.py b/homeassistant/components/sensor/homematicip_cloud.py index 87021e9c7c5f..7292e3b2f404 100644 --- a/homeassistant/components/sensor/homematicip_cloud.py +++ b/homeassistant/components/sensor/homematicip_cloud.py @@ -80,11 +80,6 @@ class HomematicipAccesspointStatus(HomematicipGenericDevice): """Return the unit this state is expressed in.""" return '%' - @property - def device_state_attributes(self): - """Return the state attributes of the access point.""" - return {} - class HomematicipHeatingThermostat(HomematicipGenericDevice): """MomematicIP heating thermostat representation.""" @@ -98,6 +93,8 @@ class HomematicipHeatingThermostat(HomematicipGenericDevice): """Return the icon.""" from homematicip.base.enums import ValveState + if super().icon: + return super().icon if self._device.valveState != ValveState.ADAPTION_DONE: return 'mdi:alert' return 'mdi:radiator' From 06c3f756b1b0a106d54589e9a07b39eb3273f8e2 Mon Sep 17 00:00:00 2001 From: Daniel Shokouhi Date: Wed, 18 Jul 2018 03:19:38 -0700 Subject: [PATCH 022/113] Implement locate service for neato (#15467) * Implement locate service for neato * Hound --- homeassistant/components/neato.py | 2 +- homeassistant/components/vacuum/neato.py | 9 +++++++-- requirements_all.txt | 2 +- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/neato.py b/homeassistant/components/neato.py index fc407de0a6b7..63b0f61bb7ce 100644 --- a/homeassistant/components/neato.py +++ b/homeassistant/components/neato.py @@ -17,7 +17,7 @@ from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) -REQUIREMENTS = ['pybotvac==0.0.7'] +REQUIREMENTS = ['pybotvac==0.0.8'] DOMAIN = 'neato' NEATO_ROBOTS = 'neato_robots' diff --git a/homeassistant/components/vacuum/neato.py b/homeassistant/components/vacuum/neato.py index 6289fed265d0..224e763a097d 100644 --- a/homeassistant/components/vacuum/neato.py +++ b/homeassistant/components/vacuum/neato.py @@ -12,7 +12,8 @@ from homeassistant.const import STATE_OFF, STATE_ON from homeassistant.components.vacuum import ( VacuumDevice, SUPPORT_BATTERY, SUPPORT_PAUSE, SUPPORT_RETURN_HOME, SUPPORT_STATUS, SUPPORT_STOP, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, - SUPPORT_MAP, ATTR_STATUS, ATTR_BATTERY_LEVEL, ATTR_BATTERY_ICON) + SUPPORT_MAP, ATTR_STATUS, ATTR_BATTERY_LEVEL, ATTR_BATTERY_ICON, + SUPPORT_LOCATE) from homeassistant.components.neato import ( NEATO_ROBOTS, NEATO_LOGIN, NEATO_MAP_DATA, ACTION, ERRORS, MODE, ALERTS) @@ -24,7 +25,7 @@ SCAN_INTERVAL = timedelta(minutes=5) SUPPORT_NEATO = SUPPORT_BATTERY | SUPPORT_PAUSE | SUPPORT_RETURN_HOME | \ SUPPORT_STOP | SUPPORT_TURN_OFF | SUPPORT_TURN_ON | \ - SUPPORT_STATUS | SUPPORT_MAP + SUPPORT_STATUS | SUPPORT_MAP | SUPPORT_LOCATE ATTR_CLEAN_START = 'clean_start' ATTR_CLEAN_STOP = 'clean_stop' @@ -211,3 +212,7 @@ class NeatoConnectedVacuum(VacuumDevice): self.robot.pause_cleaning() if self._state['state'] == 3: self.robot.resume_cleaning() + + def locate(self, **kwargs): + """Locate the robot by making it emit a sound.""" + self.robot.locate() diff --git a/requirements_all.txt b/requirements_all.txt index a39a2cada31f..8ef15e4bd184 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -753,7 +753,7 @@ pyblackbird==0.5 # pybluez==0.22 # homeassistant.components.neato -pybotvac==0.0.7 +pybotvac==0.0.8 # homeassistant.components.cloudflare pycfdns==0.0.1 From 26375a3014a305a33425b8298e545c36f4bb2f23 Mon Sep 17 00:00:00 2001 From: John Arild Berentsen Date: Wed, 18 Jul 2018 12:20:02 +0200 Subject: [PATCH 023/113] Make RS room thermostat discoverable (#15451) * Make RS room thermostat discoverable * Reversed generic type name --- homeassistant/components/zwave/discovery_schemas.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/zwave/discovery_schemas.py b/homeassistant/components/zwave/discovery_schemas.py index fc2e7fc912d6..f88b911a6a5b 100644 --- a/homeassistant/components/zwave/discovery_schemas.py +++ b/homeassistant/components/zwave/discovery_schemas.py @@ -37,7 +37,9 @@ DISCOVERY_SCHEMAS = [ const.DISC_OPTIONAL: True, }})}, {const.DISC_COMPONENT: 'climate', - const.DISC_GENERIC_DEVICE_CLASS: [const.GENERIC_TYPE_THERMOSTAT], + const.DISC_GENERIC_DEVICE_CLASS: [ + const.GENERIC_TYPE_THERMOSTAT, + const.GENERIC_TYPE_SENSOR_MULTILEVEL], const.DISC_VALUES: dict(DEFAULT_VALUES_SCHEMA, **{ const.DISC_PRIMARY: { const.DISC_COMMAND_CLASS: [ From 6834e00be6b8fa361e4601907db00baec3d007c0 Mon Sep 17 00:00:00 2001 From: fucm <39280548+fucm@users.noreply.github.com> Date: Wed, 18 Jul 2018 12:38:34 +0200 Subject: [PATCH 024/113] Add support for Tahoma Soke Sensor (#15441) --- .../components/binary_sensor/tahoma.py | 98 +++++++++++++++++++ homeassistant/components/tahoma.py | 3 +- 2 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 homeassistant/components/binary_sensor/tahoma.py diff --git a/homeassistant/components/binary_sensor/tahoma.py b/homeassistant/components/binary_sensor/tahoma.py new file mode 100644 index 000000000000..efcfb629f396 --- /dev/null +++ b/homeassistant/components/binary_sensor/tahoma.py @@ -0,0 +1,98 @@ +""" +Support for Tahoma binary sensors. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/binary_sensor.tahoma/ +""" + +import logging +from datetime import timedelta + +from homeassistant.components.binary_sensor import ( + BinarySensorDevice) +from homeassistant.components.tahoma import ( + DOMAIN as TAHOMA_DOMAIN, TahomaDevice) +from homeassistant.const import (STATE_OFF, STATE_ON, ATTR_BATTERY_LEVEL) + +DEPENDENCIES = ['tahoma'] + +_LOGGER = logging.getLogger(__name__) + +SCAN_INTERVAL = timedelta(seconds=120) + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Set up Tahoma controller devices.""" + _LOGGER.debug("Setup Tahoma Binary sensor platform") + controller = hass.data[TAHOMA_DOMAIN]['controller'] + devices = [] + for device in hass.data[TAHOMA_DOMAIN]['devices']['smoke']: + devices.append(TahomaBinarySensor(device, controller)) + add_devices(devices, True) + + +class TahomaBinarySensor(TahomaDevice, BinarySensorDevice): + """Representation of a Tahoma Binary Sensor.""" + + def __init__(self, tahoma_device, controller): + """Initialize the sensor.""" + super().__init__(tahoma_device, controller) + + self._state = None + self._icon = None + self._battery = None + + @property + def is_on(self): + """Return the state of the sensor.""" + return bool(self._state == STATE_ON) + + @property + def device_class(self): + """Return the class of the device.""" + if self.tahoma_device.type == 'rtds:RTDSSmokeSensor': + return 'smoke' + return None + + @property + def icon(self): + """Icon for device by its type.""" + return self._icon + + @property + def device_state_attributes(self): + """Return the device state attributes.""" + attr = {} + super_attr = super().device_state_attributes + if super_attr is not None: + attr.update(super_attr) + + if self._battery is not None: + attr[ATTR_BATTERY_LEVEL] = self._battery + return attr + + def update(self): + """Update the state.""" + self.controller.get_states([self.tahoma_device]) + if self.tahoma_device.type == 'rtds:RTDSSmokeSensor': + if self.tahoma_device.active_states['core:SmokeState']\ + == 'notDetected': + self._state = STATE_OFF + else: + self._state = STATE_ON + + if 'core:SensorDefectState' in self.tahoma_device.active_states: + # Set to 'lowBattery' for low battery warning. + self._battery = self.tahoma_device.active_states[ + 'core:SensorDefectState'] + else: + self._battery = None + + if self._state == STATE_ON: + self._icon = "mdi:fire" + elif self._battery == 'lowBattery': + self._icon = "mdi:battery-alert" + else: + self._icon = None + + _LOGGER.debug("Update %s, state: %s", self._name, self._state) diff --git a/homeassistant/components/tahoma.py b/homeassistant/components/tahoma.py index ba91dd7c1fc2..1cbc81709c48 100644 --- a/homeassistant/components/tahoma.py +++ b/homeassistant/components/tahoma.py @@ -32,7 +32,7 @@ CONFIG_SCHEMA = vol.Schema({ }, extra=vol.ALLOW_EXTRA) TAHOMA_COMPONENTS = [ - 'scene', 'sensor', 'cover', 'switch' + 'scene', 'sensor', 'cover', 'switch', 'binary_sensor' ] TAHOMA_TYPES = { @@ -50,6 +50,7 @@ TAHOMA_TYPES = { 'io:WindowOpenerVeluxIOComponent': 'cover', 'io:LightIOSystemSensor': 'sensor', 'rts:GarageDoor4TRTSComponent': 'switch', + 'rtds:RTDSSmokeSensor': 'smoke', } From e5f0da75e25d2088bf31842c7b60e75b0d8a3458 Mon Sep 17 00:00:00 2001 From: Tom Harris Date: Wed, 18 Jul 2018 10:11:54 -0400 Subject: [PATCH 025/113] Mini-Remote events (#15523) * Add event handler to capture binary sensor on messages * Log event trigger * Log event firing * Capture platform correctly * Fix test for platform eq binary_sensor * Create sensor events * Add light and battery sensors * Bump insteonplm version to 0.11.6 * Fix naming of BUTTON_PRESSED_STATE_NAME * Fix naming of fire event methods * Add logging * Add DOMAIN definition * Get state name from plm.devices * Remove stale reference to button ID * Fix reference to state name * Remove incorrect ref to self * Log remote button pressed event * Change mode to button_mode and fix values to array * Rename CONF_MODE to CONF_BUTTON_MODE * Log platform create with mode * Properly assign button_mode to track mode * Implement is_on * Change mini-remotes to events only * Remove button_mode config option * Fix reference to _fire_button_on_off_event * Bump insteon version to 0.11.7 * Flake8 clean up * Flake8 cleanup * Use % format in logging per pylint * Code review updates * Resolve conflict * Lint --- .../components/binary_sensor/insteon_plm.py | 11 ++++-- .../components/insteon_plm/__init__.py | 35 ++++++++++++++++--- requirements_all.txt | 2 +- 3 files changed, 41 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/binary_sensor/insteon_plm.py b/homeassistant/components/binary_sensor/insteon_plm.py index 9cb87b317499..25fc3fb5d73f 100644 --- a/homeassistant/components/binary_sensor/insteon_plm.py +++ b/homeassistant/components/binary_sensor/insteon_plm.py @@ -17,7 +17,9 @@ _LOGGER = logging.getLogger(__name__) SENSOR_TYPES = {'openClosedSensor': 'opening', 'motionSensor': 'motion', 'doorSensor': 'door', - 'wetLeakSensor': 'moisture'} + 'wetLeakSensor': 'moisture', + 'lightSensor': 'light', + 'batterySensor': 'battery'} @asyncio.coroutine @@ -54,4 +56,9 @@ class InsteonPLMBinarySensor(InsteonPLMEntity, BinarySensorDevice): @property def is_on(self): """Return the boolean response if the node is on.""" - return bool(self._insteon_device_state.value) + on_val = bool(self._insteon_device_state.value) + + if self._insteon_device_state.name == 'lightSensor': + return not on_val + + return on_val diff --git a/homeassistant/components/insteon_plm/__init__.py b/homeassistant/components/insteon_plm/__init__.py index 82fc6b022662..ef631223894b 100644 --- a/homeassistant/components/insteon_plm/__init__.py +++ b/homeassistant/components/insteon_plm/__init__.py @@ -17,7 +17,7 @@ import homeassistant.helpers.config_validation as cv from homeassistant.helpers import discovery from homeassistant.helpers.entity import Entity -REQUIREMENTS = ['insteonplm==0.11.3'] +REQUIREMENTS = ['insteonplm==0.11.7'] _LOGGER = logging.getLogger(__name__) @@ -55,6 +55,11 @@ SRV_HOUSECODE = 'housecode' HOUSECODES = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p'] +BUTTON_PRESSED_STATE_NAME = 'onLevelButton' +EVENT_BUTTON_ON = 'insteon_plm.button_on' +EVENT_BUTTON_OFF = 'insteon_plm.button_off' +EVENT_CONF_BUTTON = 'button' + CONF_DEVICE_OVERRIDE_SCHEMA = vol.All( cv.deprecated(CONF_PLATFORM), vol.Schema({ vol.Required(CONF_ADDRESS): cv.string, @@ -130,9 +135,14 @@ def async_setup(hass, config): """Detect device from transport to be delegated to platform.""" for state_key in device.states: platform_info = ipdb[device.states[state_key]] - if platform_info: + if platform_info and platform_info.platform: platform = platform_info.platform - if platform: + + if platform == 'on_off_events': + device.states[state_key].register_updates( + _fire_button_on_off_event) + + else: _LOGGER.info("New INSTEON PLM device: %s (%s) %s", device.address, device.states[state_key].name, @@ -223,6 +233,23 @@ def async_setup(hass, config): schema=X10_HOUSECODE_SCHEMA) _LOGGER.debug("Insteon_plm Services registered") + def _fire_button_on_off_event(address, group, val): + # Firing an event when a button is pressed. + device = plm.devices[address.hex] + state_name = device.states[group].name + button = ("" if state_name == BUTTON_PRESSED_STATE_NAME + else state_name[-1].lower()) + schema = {CONF_ADDRESS: address.hex} + if button != "": + schema[EVENT_CONF_BUTTON] = button + if val: + event = EVENT_BUTTON_ON + else: + event = EVENT_BUTTON_OFF + _LOGGER.debug('Firing event %s with address %s and button %s', + event, address.hex, button) + hass.bus.fire(event, schema) + _LOGGER.info("Looking for PLM on %s", port) conn = yield from insteonplm.Connection.create( device=port, @@ -329,7 +356,7 @@ class IPDB(object): State(DimmableSwitch_Fan, 'fan'), State(DimmableSwitch, 'light'), - State(DimmableRemote, 'binary_sensor'), + State(DimmableRemote, 'on_off_events'), State(X10DimmableSwitch, 'light'), State(X10OnOffSwitch, 'switch'), diff --git a/requirements_all.txt b/requirements_all.txt index 8ef15e4bd184..1ef323861483 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -452,7 +452,7 @@ influxdb==5.0.0 insteonlocal==0.53 # homeassistant.components.insteon_plm -insteonplm==0.11.3 +insteonplm==0.11.7 # homeassistant.components.sensor.iperf3 iperf3==0.1.10 From a8c7425e1728bf898f57e1fd517a5cfeaa4fda20 Mon Sep 17 00:00:00 2001 From: Giel Janssens Date: Wed, 18 Jul 2018 16:58:45 +0200 Subject: [PATCH 026/113] Update pyatmo (#15540) --- homeassistant/components/netatmo.py | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/netatmo.py b/homeassistant/components/netatmo.py index a635d1820db0..b1c7a650843a 100644 --- a/homeassistant/components/netatmo.py +++ b/homeassistant/components/netatmo.py @@ -16,7 +16,7 @@ from homeassistant.helpers import discovery import homeassistant.helpers.config_validation as cv from homeassistant.util import Throttle -REQUIREMENTS = ['pyatmo==1.0.0'] +REQUIREMENTS = ['pyatmo==1.1.1'] _LOGGER = logging.getLogger(__name__) diff --git a/requirements_all.txt b/requirements_all.txt index 1ef323861483..970a8c134eee 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -737,7 +737,7 @@ pyasn1-modules==0.1.5 pyasn1==0.3.7 # homeassistant.components.netatmo -pyatmo==1.0.0 +pyatmo==1.1.1 # homeassistant.components.apple_tv pyatv==0.3.10 From dfe17491f83248b2f9347a4bb2c9673c861cd6bc Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 18 Jul 2018 17:33:44 +0200 Subject: [PATCH 027/113] Bump frontend to 20180718.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 141da89f3595..5a3ac0d16b50 100644 --- a/homeassistant/components/frontend/__init__.py +++ b/homeassistant/components/frontend/__init__.py @@ -26,7 +26,7 @@ from homeassistant.helpers.translation import async_get_translations from homeassistant.loader import bind_hass from homeassistant.util.yaml import load_yaml -REQUIREMENTS = ['home-assistant-frontend==20180717.0'] +REQUIREMENTS = ['home-assistant-frontend==20180718.0'] DOMAIN = 'frontend' DEPENDENCIES = ['api', 'websocket_api', 'http', 'system_log', 'onboarding'] diff --git a/requirements_all.txt b/requirements_all.txt index 970a8c134eee..051fc0d0dd0a 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -415,7 +415,7 @@ hole==0.3.0 holidays==0.9.5 # homeassistant.components.frontend -home-assistant-frontend==20180717.0 +home-assistant-frontend==20180718.0 # homeassistant.components.homekit_controller # homekit==0.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index ad3877392c7b..01f90202e8fb 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -81,7 +81,7 @@ hbmqtt==0.9.2 holidays==0.9.5 # homeassistant.components.frontend -home-assistant-frontend==20180717.0 +home-assistant-frontend==20180718.0 # homeassistant.components.homematicip_cloud homematicip==0.9.8 From 4650366f077b713856b068340be9773b4615de98 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 18 Jul 2018 23:00:26 +0200 Subject: [PATCH 028/113] Don't be so strict client-side (#15546) --- homeassistant/util/ssl.py | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/homeassistant/util/ssl.py b/homeassistant/util/ssl.py index fc02009b7af7..4f528cfcb516 100644 --- a/homeassistant/util/ssl.py +++ b/homeassistant/util/ssl.py @@ -6,21 +6,14 @@ import certifi def client_context(): """Return an SSL context for making requests.""" - context = _get_context() - context.verify_mode = ssl.CERT_REQUIRED - context.check_hostname = True - context.load_verify_locations(cafile=certifi.where(), capath=None) + context = ssl.create_default_context( + purpose=ssl.Purpose.SERVER_AUTH, + cafile=certifi.where() + ) return context def server_context(): - """Return an SSL context for being a server.""" - context = _get_context() - context.options |= ssl.OP_CIPHER_SERVER_PREFERENCE - return context - - -def _get_context(): """Return an SSL context following the Mozilla recommendations. TLS configuration follows the best-practice guidelines specified here: @@ -31,7 +24,8 @@ def _get_context(): context.options |= ( ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3 | - ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1 + ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1 | + ssl.OP_CIPHER_SERVER_PREFERENCE ) if hasattr(ssl, 'OP_NO_COMPRESSION'): context.options |= ssl.OP_NO_COMPRESSION From 22d961de708c6721a098d241b2e18d9a483bd56b Mon Sep 17 00:00:00 2001 From: quthla Date: Wed, 18 Jul 2018 23:39:37 +0200 Subject: [PATCH 029/113] Update reading when device is added (#15548) --- homeassistant/components/sensor/bme280.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/sensor/bme280.py b/homeassistant/components/sensor/bme280.py index 8f3949046cae..1685d34c0ecf 100644 --- a/homeassistant/components/sensor/bme280.py +++ b/homeassistant/components/sensor/bme280.py @@ -117,7 +117,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): except KeyError: pass - async_add_devices(dev) + async_add_devices(dev, True) class BME280Handler: From 2a76a0852f969bfcfbb77a9826715884a4ab41d4 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 19 Jul 2018 08:37:00 +0200 Subject: [PATCH 030/113] Allow CORS requests to token endpoint (#15519) * Allow CORS requests to token endpoint * Tests * Fuck emulated hue * Clean up * Only cors existing methods --- homeassistant/components/auth/__init__.py | 1 + .../components/emulated_hue/__init__.py | 12 ++++++------ homeassistant/components/http/__init__.py | 5 ++--- homeassistant/components/http/cors.py | 14 ++++++++++++++ homeassistant/components/http/view.py | 19 ++++++++++++------- tests/components/auth/test_init.py | 17 +++++++++++++++++ tests/components/emulated_hue/test_hue_api.py | 8 ++++---- tests/components/http/test_cors.py | 4 ++-- tests/components/http/test_data_validator.py | 2 +- 9 files changed, 59 insertions(+), 23 deletions(-) diff --git a/homeassistant/components/auth/__init__.py b/homeassistant/components/auth/__init__.py index 84287c2e425a..435555c2e314 100644 --- a/homeassistant/components/auth/__init__.py +++ b/homeassistant/components/auth/__init__.py @@ -241,6 +241,7 @@ class GrantTokenView(HomeAssistantView): url = '/auth/token' name = 'api:auth:token' requires_auth = False + cors_allowed = True def __init__(self, retrieve_credentials): """Initialize the grant token view.""" diff --git a/homeassistant/components/emulated_hue/__init__.py b/homeassistant/components/emulated_hue/__init__.py index ce94a560daee..36ce1c392f99 100644 --- a/homeassistant/components/emulated_hue/__init__.py +++ b/homeassistant/components/emulated_hue/__init__.py @@ -90,12 +90,12 @@ def setup(hass, yaml_config): handler = None server = None - DescriptionXmlView(config).register(app.router) - HueUsernameView().register(app.router) - HueAllLightsStateView(config).register(app.router) - HueOneLightStateView(config).register(app.router) - HueOneLightChangeView(config).register(app.router) - HueGroupView(config).register(app.router) + DescriptionXmlView(config).register(app, app.router) + HueUsernameView().register(app, app.router) + HueAllLightsStateView(config).register(app, app.router) + HueOneLightStateView(config).register(app, app.router) + HueOneLightChangeView(config).register(app, app.router) + HueGroupView(config).register(app, app.router) upnp_listener = UPNPResponderThread( config.host_ip_addr, config.listen_port, diff --git a/homeassistant/components/http/__init__.py b/homeassistant/components/http/__init__.py index c8eba41e66b0..0cbee628a8a9 100644 --- a/homeassistant/components/http/__init__.py +++ b/homeassistant/components/http/__init__.py @@ -187,8 +187,7 @@ class HomeAssistantHTTP(object): support_legacy=hass.auth.support_legacy, api_password=api_password) - if cors_origins: - setup_cors(app, cors_origins) + setup_cors(app, cors_origins) app['hass'] = hass @@ -226,7 +225,7 @@ class HomeAssistantHTTP(object): '{0} missing required attribute "name"'.format(class_name) ) - view.register(self.app.router) + view.register(self.app, self.app.router) def register_redirect(self, url, redirect_to): """Register a redirect with the server. diff --git a/homeassistant/components/http/cors.py b/homeassistant/components/http/cors.py index 0a37f22867ef..b01e68f701d9 100644 --- a/homeassistant/components/http/cors.py +++ b/homeassistant/components/http/cors.py @@ -27,6 +27,20 @@ def setup_cors(app, origins): ) for host in origins }) + def allow_cors(route, methods): + """Allow cors on a route.""" + cors.add(route, { + '*': aiohttp_cors.ResourceOptions( + allow_headers=ALLOWED_CORS_HEADERS, + allow_methods=methods, + ) + }) + + app['allow_cors'] = allow_cors + + if not origins: + return + async def cors_startup(app): """Initialize cors when app starts up.""" cors_added = set() diff --git a/homeassistant/components/http/view.py b/homeassistant/components/http/view.py index 3de276564ebb..23698af81013 100644 --- a/homeassistant/components/http/view.py +++ b/homeassistant/components/http/view.py @@ -26,7 +26,9 @@ class HomeAssistantView(object): url = None extra_urls = [] - requires_auth = True # Views inheriting from this class can override this + # Views inheriting from this class can override this + requires_auth = True + cors_allowed = False # pylint: disable=no-self-use def json(self, result, status_code=200, headers=None): @@ -51,10 +53,11 @@ class HomeAssistantView(object): data['code'] = message_code return self.json(data, status_code, headers=headers) - def register(self, router): + def register(self, app, router): """Register the view with a router.""" assert self.url is not None, 'No url set for view' urls = [self.url] + self.extra_urls + routes = [] for method in ('get', 'post', 'delete', 'put'): handler = getattr(self, method, None) @@ -65,13 +68,15 @@ class HomeAssistantView(object): handler = request_handler_factory(self, handler) for url in urls: - router.add_route(method, url, handler) + routes.append( + (method, router.add_route(method, url, handler)) + ) - # aiohttp_cors does not work with class based views - # self.app.router.add_route('*', self.url, self, name=self.name) + if not self.cors_allowed: + return - # for url in self.extra_urls: - # self.app.router.add_route('*', url, self) + for method, route in routes: + app['allow_cors'](route, [method.upper()]) def request_handler_factory(view, handler): diff --git a/tests/components/auth/test_init.py b/tests/components/auth/test_init.py index 46b88e46b4dc..1d3719b8c66e 100644 --- a/tests/components/auth/test_init.py +++ b/tests/components/auth/test_init.py @@ -93,3 +93,20 @@ async def test_ws_current_user(hass, hass_ws_client, hass_access_token): assert user_dict['name'] == user.name assert user_dict['id'] == user.id assert user_dict['is_owner'] == user.is_owner + + +async def test_cors_on_token(hass, aiohttp_client): + """Test logging in with new user and refreshing tokens.""" + client = await async_setup_auth(hass, aiohttp_client) + + resp = await client.options('/auth/token', headers={ + 'origin': 'http://example.com', + 'Access-Control-Request-Method': 'POST', + }) + assert resp.headers['Access-Control-Allow-Origin'] == 'http://example.com' + assert resp.headers['Access-Control-Allow-Methods'] == 'POST' + + resp = await client.post('/auth/token', headers={ + 'origin': 'http://example.com' + }) + assert resp.headers['Access-Control-Allow-Origin'] == 'http://example.com' diff --git a/tests/components/emulated_hue/test_hue_api.py b/tests/components/emulated_hue/test_hue_api.py index 1617f327d27b..c99d273a4580 100644 --- a/tests/components/emulated_hue/test_hue_api.py +++ b/tests/components/emulated_hue/test_hue_api.py @@ -130,10 +130,10 @@ def hue_client(loop, hass_hue, aiohttp_client): } }) - HueUsernameView().register(web_app.router) - HueAllLightsStateView(config).register(web_app.router) - HueOneLightStateView(config).register(web_app.router) - HueOneLightChangeView(config).register(web_app.router) + HueUsernameView().register(web_app, web_app.router) + HueAllLightsStateView(config).register(web_app, web_app.router) + HueOneLightStateView(config).register(web_app, web_app.router) + HueOneLightChangeView(config).register(web_app, web_app.router) return loop.run_until_complete(aiohttp_client(web_app)) diff --git a/tests/components/http/test_cors.py b/tests/components/http/test_cors.py index 27367b4173e3..523d4943ba04 100644 --- a/tests/components/http/test_cors.py +++ b/tests/components/http/test_cors.py @@ -19,14 +19,14 @@ from homeassistant.components.http.cors import setup_cors TRUSTED_ORIGIN = 'https://home-assistant.io' -async def test_cors_middleware_not_loaded_by_default(hass): +async def test_cors_middleware_loaded_by_default(hass): """Test accessing to server from banned IP when feature is off.""" with patch('homeassistant.components.http.setup_cors') as mock_setup: await async_setup_component(hass, 'http', { 'http': {} }) - assert len(mock_setup.mock_calls) == 0 + assert len(mock_setup.mock_calls) == 1 async def test_cors_middleware_loaded_from_config(hass): diff --git a/tests/components/http/test_data_validator.py b/tests/components/http/test_data_validator.py index 2b966daff6cf..b5eed19eb613 100644 --- a/tests/components/http/test_data_validator.py +++ b/tests/components/http/test_data_validator.py @@ -23,7 +23,7 @@ async def get_client(aiohttp_client, validator): """Test method.""" return b'' - TestView().register(app.router) + TestView().register(app, app.router) client = await aiohttp_client(app) return client From 8b04d48ffdf226e345957ddb509da9c1b5cf67dd Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 19 Jul 2018 08:37:13 +0200 Subject: [PATCH 031/113] Update config entry id in entity registry (#15531) --- homeassistant/helpers/entity_registry.py | 16 ++++++++++++++++ tests/helpers/test_entity_registry.py | 13 ++++++++++++- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/homeassistant/helpers/entity_registry.py b/homeassistant/helpers/entity_registry.py index 04d9cc450ba0..b222d78b5775 100644 --- a/homeassistant/helpers/entity_registry.py +++ b/homeassistant/helpers/entity_registry.py @@ -109,6 +109,12 @@ class EntityRegistry: """Get entity. Create if it doesn't exist.""" entity_id = self.async_get_entity_id(domain, platform, unique_id) if entity_id: + entry = self.entities[entity_id] + if entry.config_entry_id == config_entry_id: + return entry + + self._async_update_entity( + entity_id, config_entry_id=config_entry_id) return self.entities[entity_id] entity_id = self.async_generate_entity_id( @@ -129,6 +135,12 @@ class EntityRegistry: @callback def async_update_entity(self, entity_id, *, name=_UNDEF): """Update properties of an entity.""" + return self._async_update_entity(entity_id, name=name) + + @callback + def _async_update_entity(self, entity_id, *, name=_UNDEF, + config_entry_id=_UNDEF): + """Private facing update properties method.""" old = self.entities[entity_id] changes = {} @@ -136,6 +148,10 @@ class EntityRegistry: if name is not _UNDEF and name != old.name: changes['name'] = name + if (config_entry_id is not _UNDEF and + config_entry_id != old.config_entry_id): + changes['config_entry_id'] = config_entry_id + if not changes: return old diff --git a/tests/helpers/test_entity_registry.py b/tests/helpers/test_entity_registry.py index 6808206243f8..5a9efd5c041f 100644 --- a/tests/helpers/test_entity_registry.py +++ b/tests/helpers/test_entity_registry.py @@ -107,7 +107,8 @@ def test_loading_saving_data(hass, registry): # Ensure same order assert list(registry.entities) == list(registry2.entities) new_entry1 = registry.async_get_or_create('light', 'hue', '1234') - new_entry2 = registry.async_get_or_create('light', 'hue', '5678') + new_entry2 = registry.async_get_or_create('light', 'hue', '5678', + config_entry_id='mock-id') assert orig_entry1 == new_entry1 assert orig_entry2 == new_entry2 @@ -191,3 +192,13 @@ def test_async_get_entity_id(registry): assert registry.async_get_entity_id( 'light', 'hue', '1234') == 'light.hue_1234' assert registry.async_get_entity_id('light', 'hue', '123') is None + + +async def test_updating_config_entry_id(registry): + """Test that we update config entry id in registry.""" + entry = registry.async_get_or_create( + 'light', 'hue', '5678', config_entry_id='mock-id-1') + entry2 = registry.async_get_or_create( + 'light', 'hue', '5678', config_entry_id='mock-id-2') + assert entry.entity_id == entry2.entity_id + assert entry2.config_entry_id == 'mock-id-2' From 396895d0778f3396ddd68d2f7eaa7d7ac6179b32 Mon Sep 17 00:00:00 2001 From: Jerad Meisner Date: Thu, 19 Jul 2018 00:39:51 -0700 Subject: [PATCH 032/113] Added WS endpoint for changing homeassistant password. (#15527) * Added WS endpoint for changing homeassistant password. * Remove change password helper. Don't require current password. * Restore current password verification. * Added tests. * Use correct send method --- .../config/auth_provider_homeassistant.py | 54 ++++++++++++++ .../test_auth_provider_homeassistant.py | 74 +++++++++++++++++++ 2 files changed, 128 insertions(+) diff --git a/homeassistant/components/config/auth_provider_homeassistant.py b/homeassistant/components/config/auth_provider_homeassistant.py index fca03ad8fa9f..960e8f5e7b4e 100644 --- a/homeassistant/components/config/auth_provider_homeassistant.py +++ b/homeassistant/components/config/auth_provider_homeassistant.py @@ -20,6 +20,13 @@ SCHEMA_WS_DELETE = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ vol.Required('username'): str, }) +WS_TYPE_CHANGE_PASSWORD = 'config/auth_provider/homeassistant/change_password' +SCHEMA_WS_CHANGE_PASSWORD = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ + vol.Required('type'): WS_TYPE_CHANGE_PASSWORD, + vol.Required('current_password'): str, + vol.Required('new_password'): str +}) + async def async_setup(hass): """Enable the Home Assistant views.""" @@ -31,6 +38,10 @@ async def async_setup(hass): WS_TYPE_DELETE, websocket_delete, SCHEMA_WS_DELETE ) + hass.components.websocket_api.async_register_command( + WS_TYPE_CHANGE_PASSWORD, websocket_change_password, + SCHEMA_WS_CHANGE_PASSWORD + ) return True @@ -118,3 +129,46 @@ def websocket_delete(hass, connection, msg): websocket_api.result_message(msg['id'])) hass.async_add_job(delete_creds()) + + +@callback +def websocket_change_password(hass, connection, msg): + """Change user password.""" + async def change_password(): + """Change user password.""" + user = connection.request.get('hass_user') + if user is None: + connection.send_message_outside(websocket_api.error_message( + msg['id'], 'user_not_found', 'User not found')) + return + + provider = _get_provider(hass) + await provider.async_initialize() + + username = None + for credential in user.credentials: + if credential.auth_provider_type == provider.type: + username = credential.data['username'] + break + + if username is None: + connection.send_message_outside(websocket_api.error_message( + msg['id'], 'credentials_not_found', 'Credentials not found')) + return + + try: + await provider.async_validate_login( + username, msg['current_password']) + except auth_ha.InvalidAuth: + connection.send_message_outside(websocket_api.error_message( + msg['id'], 'invalid_password', 'Invalid password')) + return + + await hass.async_add_executor_job( + provider.data.change_password, username, msg['new_password']) + await provider.data.async_save() + + connection.send_message_outside( + websocket_api.result_message(msg['id'])) + + hass.async_add_job(change_password()) diff --git a/tests/components/config/test_auth_provider_homeassistant.py b/tests/components/config/test_auth_provider_homeassistant.py index fa4ab612bb12..cd2cbc445399 100644 --- a/tests/components/config/test_auth_provider_homeassistant.py +++ b/tests/components/config/test_auth_provider_homeassistant.py @@ -227,3 +227,77 @@ async def test_delete_unknown_auth(hass, hass_ws_client, hass_access_token): result = await client.receive_json() assert not result['success'], result assert result['error']['code'] == 'auth_not_found' + + +async def test_change_password(hass, hass_ws_client, hass_access_token): + """Test that change password succeeds with valid password.""" + provider = hass.auth.auth_providers[0] + await provider.async_initialize() + await hass.async_add_executor_job( + provider.data.add_auth, 'test-user', 'test-pass') + + credentials = await provider.async_get_or_create_credentials({ + 'username': 'test-user' + }) + + user = hass_access_token.refresh_token.user + await hass.auth.async_link_user(user, credentials) + + client = await hass_ws_client(hass, hass_access_token) + await client.send_json({ + 'id': 6, + 'type': auth_ha.WS_TYPE_CHANGE_PASSWORD, + 'current_password': 'test-pass', + 'new_password': 'new-pass' + }) + + result = await client.receive_json() + assert result['success'], result + await provider.async_validate_login('test-user', 'new-pass') + + +async def test_change_password_wrong_pw(hass, hass_ws_client, + hass_access_token): + """Test that change password fails with invalid password.""" + provider = hass.auth.auth_providers[0] + await provider.async_initialize() + await hass.async_add_executor_job( + provider.data.add_auth, 'test-user', 'test-pass') + + credentials = await provider.async_get_or_create_credentials({ + 'username': 'test-user' + }) + + user = hass_access_token.refresh_token.user + await hass.auth.async_link_user(user, credentials) + + client = await hass_ws_client(hass, hass_access_token) + await client.send_json({ + 'id': 6, + 'type': auth_ha.WS_TYPE_CHANGE_PASSWORD, + 'current_password': 'wrong-pass', + 'new_password': 'new-pass' + }) + + result = await client.receive_json() + assert not result['success'], result + assert result['error']['code'] == 'invalid_password' + with pytest.raises(prov_ha.InvalidAuth): + await provider.async_validate_login('test-user', 'new-pass') + + +async def test_change_password_no_creds(hass, hass_ws_client, + hass_access_token): + """Test that change password fails with no credentials.""" + client = await hass_ws_client(hass, hass_access_token) + + await client.send_json({ + 'id': 6, + 'type': auth_ha.WS_TYPE_CHANGE_PASSWORD, + 'current_password': 'test-pass', + 'new_password': 'new-pass' + }) + + result = await client.receive_json() + assert not result['success'], result + assert result['error']['code'] == 'credentials_not_found' From 33ee91a7484a6499f56eb181095eeac46cbaaaee Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 19 Jul 2018 10:52:28 +0200 Subject: [PATCH 033/113] Bump frontend to 20180719.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 5a3ac0d16b50..dc5d1d7bf7e3 100644 --- a/homeassistant/components/frontend/__init__.py +++ b/homeassistant/components/frontend/__init__.py @@ -26,7 +26,7 @@ from homeassistant.helpers.translation import async_get_translations from homeassistant.loader import bind_hass from homeassistant.util.yaml import load_yaml -REQUIREMENTS = ['home-assistant-frontend==20180718.0'] +REQUIREMENTS = ['home-assistant-frontend==20180719.0'] DOMAIN = 'frontend' DEPENDENCIES = ['api', 'websocket_api', 'http', 'system_log', 'onboarding'] diff --git a/requirements_all.txt b/requirements_all.txt index 051fc0d0dd0a..36ad3cdc81dc 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -415,7 +415,7 @@ hole==0.3.0 holidays==0.9.5 # homeassistant.components.frontend -home-assistant-frontend==20180718.0 +home-assistant-frontend==20180719.0 # homeassistant.components.homekit_controller # homekit==0.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 01f90202e8fb..e1e1593b8142 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -81,7 +81,7 @@ hbmqtt==0.9.2 holidays==0.9.5 # homeassistant.components.frontend -home-assistant-frontend==20180718.0 +home-assistant-frontend==20180719.0 # homeassistant.components.homematicip_cloud homematicip==0.9.8 From a42288d056c6c4418856534c0dbacf2489a0a6c8 Mon Sep 17 00:00:00 2001 From: William Scanlon Date: Thu, 19 Jul 2018 13:13:46 -0400 Subject: [PATCH 034/113] Upgrade to simplisafe-python v2 to use new SimpliSafe API (#15542) * Upgrade to simplisafe-python v2 to use new SimpliSafe API --- .../alarm_control_panel/simplisafe.py | 77 +++++++++---------- requirements_all.txt | 2 +- 2 files changed, 37 insertions(+), 42 deletions(-) diff --git a/homeassistant/components/alarm_control_panel/simplisafe.py b/homeassistant/components/alarm_control_panel/simplisafe.py index b4906acba3c8..f818df4bd71a 100644 --- a/homeassistant/components/alarm_control_panel/simplisafe.py +++ b/homeassistant/components/alarm_control_panel/simplisafe.py @@ -9,23 +9,22 @@ import re import voluptuous as vol -import homeassistant.components.alarm_control_panel as alarm -from homeassistant.components.alarm_control_panel import PLATFORM_SCHEMA +from homeassistant.components.alarm_control_panel import ( + PLATFORM_SCHEMA, AlarmControlPanel) from homeassistant.const import ( CONF_CODE, CONF_NAME, CONF_PASSWORD, CONF_USERNAME, - EVENT_HOMEASSISTANT_STOP, STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, + STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED, STATE_UNKNOWN) import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['simplisafe-python==1.0.5'] +REQUIREMENTS = ['simplisafe-python==2.0.2'] _LOGGER = logging.getLogger(__name__) DEFAULT_NAME = 'SimpliSafe' -DOMAIN = 'simplisafe' -NOTIFICATION_ID = 'simplisafe_notification' -NOTIFICATION_TITLE = 'SimpliSafe Setup' +ATTR_ALARM_ACTIVE = "alarm_active" +ATTR_TEMPERATURE = "temperature" PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_PASSWORD): cv.string, @@ -37,36 +36,27 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_devices, discovery_info=None): """Set up the SimpliSafe platform.""" - from simplipy.api import SimpliSafeApiInterface, get_systems + from simplipy.api import SimpliSafeApiInterface, SimpliSafeAPIException name = config.get(CONF_NAME) code = config.get(CONF_CODE) username = config.get(CONF_USERNAME) password = config.get(CONF_PASSWORD) - simplisafe = SimpliSafeApiInterface() - status = simplisafe.set_credentials(username, password) - if status: - hass.data[DOMAIN] = simplisafe - locations = get_systems(simplisafe) - for location in locations: - add_devices([SimpliSafeAlarm(location, name, code)]) - else: - message = 'Failed to log into SimpliSafe. Check credentials.' - _LOGGER.error(message) - hass.components.persistent_notification.create( - message, - title=NOTIFICATION_TITLE, - notification_id=NOTIFICATION_ID) - return False + try: + simplisafe = SimpliSafeApiInterface(username, password) + except SimpliSafeAPIException: + _LOGGER.error("Failed to setup SimpliSafe") + return - def logout(event): - """Logout of the SimpliSafe API.""" - hass.data[DOMAIN].logout() + systems = [] - hass.bus.listen(EVENT_HOMEASSISTANT_STOP, logout) + for system in simplisafe.get_systems(): + systems.append(SimpliSafeAlarm(system, name, code)) + + add_devices(systems) -class SimpliSafeAlarm(alarm.AlarmControlPanel): +class SimpliSafeAlarm(AlarmControlPanel): """Representation of a SimpliSafe alarm.""" def __init__(self, simplisafe, name, code): @@ -75,12 +65,17 @@ class SimpliSafeAlarm(alarm.AlarmControlPanel): self._name = name self._code = str(code) if code else None + @property + def unique_id(self): + """Return the unique ID.""" + return self.simplisafe.location_id + @property def name(self): """Return the name of the device.""" if self._name is not None: return self._name - return 'Alarm {}'.format(self.simplisafe.location_id()) + return 'Alarm {}'.format(self.simplisafe.location_id) @property def code_format(self): @@ -94,12 +89,13 @@ class SimpliSafeAlarm(alarm.AlarmControlPanel): @property def state(self): """Return the state of the device.""" - status = self.simplisafe.state() - if status == 'off': + status = self.simplisafe.state + if status.lower() == 'off': state = STATE_ALARM_DISARMED - elif status == 'home': + elif status.lower() == 'home' or status.lower() == 'home_count': state = STATE_ALARM_ARMED_HOME - elif status == 'away': + elif (status.lower() == 'away' or status.lower() == 'exitDelay' or + status.lower() == 'away_count'): state = STATE_ALARM_ARMED_AWAY else: state = STATE_UNKNOWN @@ -108,14 +104,13 @@ class SimpliSafeAlarm(alarm.AlarmControlPanel): @property def device_state_attributes(self): """Return the state attributes.""" - return { - 'alarm': self.simplisafe.alarm(), - 'co': self.simplisafe.carbon_monoxide(), - 'fire': self.simplisafe.fire(), - 'flood': self.simplisafe.flood(), - 'last_event': self.simplisafe.last_event(), - 'temperature': self.simplisafe.temperature(), - } + attributes = {} + + attributes[ATTR_ALARM_ACTIVE] = self.simplisafe.alarm_active + if self.simplisafe.temperature is not None: + attributes[ATTR_TEMPERATURE] = self.simplisafe.temperature + + return attributes def update(self): """Update alarm status.""" diff --git a/requirements_all.txt b/requirements_all.txt index 36ad3cdc81dc..309a8c7c1b6c 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1241,7 +1241,7 @@ shodan==1.8.1 simplepush==1.1.4 # homeassistant.components.alarm_control_panel.simplisafe -simplisafe-python==1.0.5 +simplisafe-python==2.0.2 # homeassistant.components.skybell skybellpy==0.1.2 From 2fcacbff2390053e19912a9bfd241d89b12beb5c Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 19 Jul 2018 22:10:36 +0200 Subject: [PATCH 035/113] Allow auth providers to influence is_active (#15557) * Allow auth providers to influence is_active * Fix auth script test --- homeassistant/auth/__init__.py | 1 + homeassistant/auth/providers/__init__.py | 4 +++ homeassistant/auth/providers/homeassistant.py | 3 +- .../auth/providers/insecure_example.py | 10 ++++--- .../auth/providers/legacy_api_password.py | 5 +++- homeassistant/scripts/auth.py | 9 +----- tests/auth/providers/test_homeassistant.py | 18 +++++++++++ tests/auth/providers/test_insecure_example.py | 17 +++++++++-- .../providers/test_legacy_api_password.py | 6 +++- tests/components/auth/test_init.py | 30 +++++++++++++++---- tests/scripts/test_auth.py | 2 +- 11 files changed, 82 insertions(+), 23 deletions(-) diff --git a/homeassistant/auth/__init__.py b/homeassistant/auth/__init__.py index cc2f244efb4f..62c416a98830 100644 --- a/homeassistant/auth/__init__.py +++ b/homeassistant/auth/__init__.py @@ -124,6 +124,7 @@ class AuthManager: return await self._store.async_create_user( credentials=credentials, name=info.get('name'), + is_active=info.get('is_active', False) ) async def async_link_user(self, user, credentials): diff --git a/homeassistant/auth/providers/__init__.py b/homeassistant/auth/providers/__init__.py index 3769248fc053..68cc1c7edd27 100644 --- a/homeassistant/auth/providers/__init__.py +++ b/homeassistant/auth/providers/__init__.py @@ -135,5 +135,9 @@ class AuthProvider: """Return extra user metadata for credentials. Will be used to populate info when creating a new user. + + Values to populate: + - name: string + - is_active: boolean """ return {} diff --git a/homeassistant/auth/providers/homeassistant.py b/homeassistant/auth/providers/homeassistant.py index b359f67d77fd..d24110a47362 100644 --- a/homeassistant/auth/providers/homeassistant.py +++ b/homeassistant/auth/providers/homeassistant.py @@ -184,7 +184,8 @@ class HassAuthProvider(AuthProvider): async def async_user_meta_for_credentials(self, credentials): """Get extra info for this credential.""" return { - 'name': credentials.data['username'] + 'name': credentials.data['username'], + 'is_active': True, } async def async_will_remove_credentials(self, credentials): diff --git a/homeassistant/auth/providers/insecure_example.py b/homeassistant/auth/providers/insecure_example.py index e06b16177a12..c86c8eb71f11 100644 --- a/homeassistant/auth/providers/insecure_example.py +++ b/homeassistant/auth/providers/insecure_example.py @@ -75,14 +75,16 @@ class ExampleAuthProvider(AuthProvider): Will be used to populate info when creating a new user. """ username = credentials.data['username'] + info = { + 'is_active': True, + } for user in self.config['users']: if user['username'] == username: - return { - 'name': user.get('name') - } + info['name'] = user.get('name') + break - return {} + return info class LoginFlow(data_entry_flow.FlowHandler): diff --git a/homeassistant/auth/providers/legacy_api_password.py b/homeassistant/auth/providers/legacy_api_password.py index 57c05e3bdc86..1f92fb60f137 100644 --- a/homeassistant/auth/providers/legacy_api_password.py +++ b/homeassistant/auth/providers/legacy_api_password.py @@ -70,7 +70,10 @@ class LegacyApiPasswordAuthProvider(AuthProvider): Will be used to populate info when creating a new user. """ - return {'name': LEGACY_USER} + return { + 'name': LEGACY_USER, + 'is_active': True, + } class LoginFlow(data_entry_flow.FlowHandler): diff --git a/homeassistant/scripts/auth.py b/homeassistant/scripts/auth.py index fea523c4117a..d141faa4c276 100644 --- a/homeassistant/scripts/auth.py +++ b/homeassistant/scripts/auth.py @@ -81,16 +81,9 @@ async def add_user(hass, provider, args): print("Username already exists!") return - credentials = await provider.async_get_or_create_credentials({ - 'username': args.username - }) - - user = await hass.auth.async_create_user(args.username) - await hass.auth.async_link_user(user, credentials) - # Save username/password await provider.data.async_save() - print("User created") + print("Auth created") async def validate_login(hass, provider, args): diff --git a/tests/auth/providers/test_homeassistant.py b/tests/auth/providers/test_homeassistant.py index 08fb63a3c727..9db6293d98a1 100644 --- a/tests/auth/providers/test_homeassistant.py +++ b/tests/auth/providers/test_homeassistant.py @@ -4,6 +4,7 @@ from unittest.mock import Mock import pytest from homeassistant import data_entry_flow +from homeassistant.auth import auth_manager_from_config from homeassistant.auth.providers import ( auth_provider_from_config, homeassistant as hass_auth) @@ -112,3 +113,20 @@ async def test_not_allow_set_id(): 'id': 'invalid', }) assert provider is None + + +async def test_new_users_populate_values(hass, data): + """Test that we populate data for new users.""" + data.add_auth('hello', 'test-pass') + await data.async_save() + + manager = await auth_manager_from_config(hass, [{ + 'type': 'homeassistant' + }]) + provider = manager.auth_providers[0] + credentials = await provider.async_get_or_create_credentials({ + 'username': 'hello' + }) + user = await manager.async_get_or_create_user(credentials) + assert user.name == 'hello' + assert user.is_active diff --git a/tests/auth/providers/test_insecure_example.py b/tests/auth/providers/test_insecure_example.py index 8e8c97387565..b472e4c95df3 100644 --- a/tests/auth/providers/test_insecure_example.py +++ b/tests/auth/providers/test_insecure_example.py @@ -4,7 +4,7 @@ import uuid import pytest -from homeassistant.auth import auth_store, models as auth_models +from homeassistant.auth import auth_store, models as auth_models, AuthManager from homeassistant.auth.providers import insecure_example from tests.common import mock_coro @@ -23,6 +23,7 @@ def provider(hass, store): 'type': 'insecure_example', 'users': [ { + 'name': 'Test Name', 'username': 'user-test', 'password': 'password-test', }, @@ -34,7 +35,15 @@ def provider(hass, store): }) -async def test_create_new_credential(provider): +@pytest.fixture +def manager(hass, store, provider): + """Mock manager.""" + return AuthManager(hass, store, { + (provider.type, provider.id): provider + }) + + +async def test_create_new_credential(manager, provider): """Test that we create a new credential.""" credentials = await provider.async_get_or_create_credentials({ 'username': 'user-test', @@ -42,6 +51,10 @@ async def test_create_new_credential(provider): }) assert credentials.is_new is True + user = await manager.async_get_or_create_user(credentials) + assert user.name == 'Test Name' + assert user.is_active + async def test_match_existing_credentials(store, provider): """See if we match existing users.""" diff --git a/tests/auth/providers/test_legacy_api_password.py b/tests/auth/providers/test_legacy_api_password.py index 007e37b90c4e..71642bd7a32c 100644 --- a/tests/auth/providers/test_legacy_api_password.py +++ b/tests/auth/providers/test_legacy_api_password.py @@ -30,12 +30,16 @@ def manager(hass, store, provider): }) -async def test_create_new_credential(provider): +async def test_create_new_credential(manager, provider): """Test that we create a new credential.""" credentials = await provider.async_get_or_create_credentials({}) assert credentials.data["username"] is legacy_api_password.LEGACY_USER assert credentials.is_new is True + user = await manager.async_get_or_create_user(credentials) + assert user.name == legacy_api_password.LEGACY_USER + assert user.is_active + async def test_only_one_credentials(manager, provider): """Call create twice will return same credential.""" diff --git a/tests/components/auth/test_init.py b/tests/components/auth/test_init.py index 1d3719b8c66e..807bf15854b9 100644 --- a/tests/components/auth/test_init.py +++ b/tests/components/auth/test_init.py @@ -40,11 +40,31 @@ async def test_login_new_user_and_trying_refresh_token(hass, aiohttp_client): 'code': code }) - # User is not active - assert resp.status == 403 - data = await resp.json() - assert data['error'] == 'access_denied' - assert data['error_description'] == 'User is not active' + assert resp.status == 200 + tokens = await resp.json() + + assert hass.auth.async_get_access_token(tokens['access_token']) is not None + + # Use refresh token to get more tokens. + resp = await client.post('/auth/token', data={ + 'client_id': CLIENT_ID, + 'grant_type': 'refresh_token', + 'refresh_token': tokens['refresh_token'] + }) + + assert resp.status == 200 + tokens = await resp.json() + assert 'refresh_token' not in tokens + assert hass.auth.async_get_access_token(tokens['access_token']) is not None + + # Test using access token to hit API. + resp = await client.get('/api/') + assert resp.status == 401 + + resp = await client.get('/api/', headers={ + 'authorization': 'Bearer {}'.format(tokens['access_token']) + }) + assert resp.status == 200 def test_credential_store_expiration(): diff --git a/tests/scripts/test_auth.py b/tests/scripts/test_auth.py index 1320be299b83..f6c027150dd8 100644 --- a/tests/scripts/test_auth.py +++ b/tests/scripts/test_auth.py @@ -47,7 +47,7 @@ async def test_add_user(hass, provider, capsys, hass_storage): assert len(hass_storage[hass_auth.STORAGE_KEY]['data']['users']) == 1 captured = capsys.readouterr() - assert captured.out == 'User created\n' + assert captured.out == 'Auth created\n' assert len(data.users) == 1 data.validate_login('paulus', 'test-pass') From a0193e8e42d3583e3e96fcbe182324dc1be61059 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Thu, 19 Jul 2018 22:52:03 +0200 Subject: [PATCH 036/113] Upgrade pymysensors to 0.16.0 (#15554) --- homeassistant/components/mysensors/__init__.py | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/mysensors/__init__.py b/homeassistant/components/mysensors/__init__.py index 3aa8e82911ef..3066819638f5 100644 --- a/homeassistant/components/mysensors/__init__.py +++ b/homeassistant/components/mysensors/__init__.py @@ -22,7 +22,7 @@ from .const import ( from .device import get_mysensors_devices from .gateway import get_mysensors_gateway, setup_gateways, finish_setup -REQUIREMENTS = ['pymysensors==0.14.0'] +REQUIREMENTS = ['pymysensors==0.16.0'] _LOGGER = logging.getLogger(__name__) diff --git a/requirements_all.txt b/requirements_all.txt index 309a8c7c1b6c..ecd28ed71703 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -926,7 +926,7 @@ pymusiccast==0.1.6 pymyq==0.0.11 # homeassistant.components.mysensors -pymysensors==0.14.0 +pymysensors==0.16.0 # homeassistant.components.lock.nello pynello==1.5.1 From ea18e06b08c595fd2c835eb682b38ebb29d7490d Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 19 Jul 2018 23:12:17 +0200 Subject: [PATCH 037/113] Remove relative time from state machine (#15560) --- homeassistant/components/sensor/netatmo.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/homeassistant/components/sensor/netatmo.py b/homeassistant/components/sensor/netatmo.py index bdc2c5990d92..54b095bb84bb 100644 --- a/homeassistant/components/sensor/netatmo.py +++ b/homeassistant/components/sensor/netatmo.py @@ -51,7 +51,6 @@ SENSOR_TYPES = { 'rf_status_lvl': ['Radio_lvl', '', 'mdi:signal', None], 'wifi_status': ['Wifi', '', 'mdi:wifi', None], 'wifi_status_lvl': ['Wifi_lvl', 'dBm', 'mdi:wifi', None], - 'lastupdated': ['Last Updated', 's', 'mdi:timer', None], } MODULE_SCHEMA = vol.Schema({ @@ -286,8 +285,6 @@ class NetAtmoSensor(Entity): self._state = "High" elif data['wifi_status'] <= 55: self._state = "Full" - elif self.type == 'lastupdated': - self._state = int(time() - data['When']) class NetAtmoData(object): From 2f7b79764a3b4a9c3bd78ecd0743563e1108aac4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Fri, 20 Jul 2018 11:45:20 +0300 Subject: [PATCH 038/113] More pylint 2 fixes (#15565) ## Description: More fixes flagged by pylint 2 that don't hurt to have before the actual pylint 2 upgrade (which I'll submit soon). ## Checklist: - [ ] The code change is tested and works locally. - [x] Local tests pass with `tox`. **Your PR cannot be merged unless tests pass** --- homeassistant/components/abode.py | 2 +- homeassistant/components/ads/__init__.py | 2 +- homeassistant/components/alexa/intent.py | 2 +- homeassistant/components/alexa/smart_home.py | 8 ++++---- homeassistant/components/amcrest.py | 2 +- homeassistant/components/apcupsd.py | 2 +- homeassistant/components/arduino.py | 2 +- homeassistant/components/asterisk_mbox.py | 2 +- .../components/binary_sensor/arest.py | 2 +- .../components/binary_sensor/aurora.py | 2 +- .../components/binary_sensor/hikvision.py | 2 +- homeassistant/components/binary_sensor/iss.py | 2 +- homeassistant/components/binary_sensor/ping.py | 2 +- .../components/binary_sensor/tapsaff.py | 2 +- .../components/binary_sensor/zwave.py | 2 +- homeassistant/components/blink.py | 2 +- homeassistant/components/bloomsky.py | 2 +- .../components/bmw_connected_drive/__init__.py | 2 +- homeassistant/components/calendar/caldav.py | 2 +- homeassistant/components/calendar/demo.py | 2 +- homeassistant/components/calendar/google.py | 2 +- homeassistant/components/calendar/todoist.py | 2 +- homeassistant/components/canary.py | 2 +- homeassistant/components/climate/netatmo.py | 2 +- homeassistant/components/climate/zwave.py | 2 +- homeassistant/components/coinbase.py | 2 +- homeassistant/components/comfoconnect.py | 2 +- homeassistant/components/configurator.py | 2 +- homeassistant/components/cover/zwave.py | 2 +- homeassistant/components/daikin.py | 2 +- homeassistant/components/deconz/__init__.py | 2 +- .../components/device_tracker/__init__.py | 4 ++-- .../components/device_tracker/automatic.py | 2 +- .../device_tracker/bmw_connected_drive.py | 2 +- .../components/device_tracker/freebox.py | 2 +- .../components/device_tracker/google_maps.py | 2 +- .../components/device_tracker/ping.py | 2 +- .../components/device_tracker/tesla.py | 2 +- .../components/device_tracker/tile.py | 2 +- .../components/device_tracker/trackr.py | 2 +- homeassistant/components/dialogflow.py | 2 +- homeassistant/components/digital_ocean.py | 2 +- homeassistant/components/ecobee.py | 2 +- .../components/emulated_hue/__init__.py | 2 +- homeassistant/components/fan/zwave.py | 2 +- homeassistant/components/feedreader.py | 4 ++-- homeassistant/components/ffmpeg.py | 2 +- homeassistant/components/gc100.py | 2 +- homeassistant/components/google.py | 2 +- homeassistant/components/hassio/handler.py | 2 +- homeassistant/components/history.py | 2 +- .../components/homematicip_cloud/hap.py | 4 ++-- homeassistant/components/http/__init__.py | 2 +- homeassistant/components/http/ban.py | 2 +- homeassistant/components/http/view.py | 2 +- homeassistant/components/hue/bridge.py | 2 +- homeassistant/components/hydrawise.py | 2 +- .../components/insteon_plm/__init__.py | 2 +- homeassistant/components/keyboard_remote.py | 2 +- homeassistant/components/knx.py | 6 +++--- homeassistant/components/konnected.py | 2 +- homeassistant/components/light/greenwave.py | 2 +- homeassistant/components/light/lifx.py | 2 +- homeassistant/components/light/lifx_legacy.py | 2 +- homeassistant/components/light/zwave.py | 2 +- homeassistant/components/linode.py | 2 +- homeassistant/components/logbook.py | 2 +- homeassistant/components/mailbox/__init__.py | 2 +- homeassistant/components/matrix.py | 2 +- homeassistant/components/maxcube.py | 2 +- homeassistant/components/media_extractor.py | 2 +- homeassistant/components/media_player/cast.py | 4 ++-- .../components/media_player/firetv.py | 2 +- .../components/media_player/itunes.py | 2 +- .../components/media_player/squeezebox.py | 2 +- homeassistant/components/microsoft_face.py | 2 +- homeassistant/components/mochad.py | 2 +- homeassistant/components/modbus.py | 2 +- homeassistant/components/mqtt/__init__.py | 6 +++--- homeassistant/components/mychevy.py | 4 ++-- homeassistant/components/mysensors/device.py | 2 +- homeassistant/components/neato.py | 2 +- homeassistant/components/nest/__init__.py | 2 +- homeassistant/components/netatmo.py | 2 +- homeassistant/components/notify/__init__.py | 2 +- homeassistant/components/notify/apns.py | 2 +- homeassistant/components/nuimo_controller.py | 4 ++-- homeassistant/components/octoprint.py | 2 +- homeassistant/components/pilight.py | 2 +- homeassistant/components/plant.py | 2 +- homeassistant/components/prometheus.py | 2 +- homeassistant/components/rachio.py | 4 ++-- homeassistant/components/raincloud.py | 2 +- .../components/rainmachine/__init__.py | 2 +- homeassistant/components/raspihats.py | 2 +- .../components/remember_the_milk/__init__.py | 2 +- homeassistant/components/scsgate.py | 2 +- homeassistant/components/sensor/airvisual.py | 2 +- homeassistant/components/sensor/arest.py | 2 +- homeassistant/components/sensor/bbox.py | 2 +- homeassistant/components/sensor/bitcoin.py | 2 +- homeassistant/components/sensor/bom.py | 2 +- homeassistant/components/sensor/broadlink.py | 2 +- homeassistant/components/sensor/buienradar.py | 2 +- .../components/sensor/coinmarketcap.py | 2 +- .../components/sensor/command_line.py | 2 +- homeassistant/components/sensor/cups.py | 2 +- .../components/sensor/currencylayer.py | 2 +- homeassistant/components/sensor/darksky.py | 2 +- .../components/sensor/deutsche_bahn.py | 2 +- homeassistant/components/sensor/dht.py | 2 +- .../components/sensor/dublin_bus_transport.py | 2 +- .../components/sensor/dwd_weather_warnings.py | 2 +- homeassistant/components/sensor/dweet.py | 2 +- homeassistant/components/sensor/ebox.py | 2 +- .../components/sensor/eddystone_temperature.py | 2 +- homeassistant/components/sensor/emoncms.py | 2 +- homeassistant/components/sensor/envirophat.py | 2 +- homeassistant/components/sensor/fail2ban.py | 2 +- homeassistant/components/sensor/fastdotcom.py | 2 +- homeassistant/components/sensor/fido.py | 2 +- homeassistant/components/sensor/filter.py | 4 ++-- homeassistant/components/sensor/fints.py | 2 +- homeassistant/components/sensor/fixer.py | 2 +- .../components/sensor/fritzbox_callmonitor.py | 4 ++-- homeassistant/components/sensor/geizhals.py | 2 +- .../components/sensor/geo_rss_events.py | 2 +- homeassistant/components/sensor/glances.py | 2 +- homeassistant/components/sensor/google_wifi.py | 2 +- .../components/sensor/haveibeenpwned.py | 2 +- homeassistant/components/sensor/hddtemp.py | 2 +- homeassistant/components/sensor/hp_ilo.py | 2 +- homeassistant/components/sensor/hydroquebec.py | 2 +- .../components/sensor/imap_email_content.py | 2 +- homeassistant/components/sensor/influxdb.py | 2 +- .../components/sensor/irish_rail_transport.py | 2 +- homeassistant/components/sensor/london_air.py | 2 +- .../components/sensor/london_underground.py | 2 +- homeassistant/components/sensor/luftdaten.py | 2 +- homeassistant/components/sensor/lyft.py | 2 +- homeassistant/components/sensor/metoffice.py | 2 +- homeassistant/components/sensor/mhz19.py | 2 +- homeassistant/components/sensor/mopar.py | 2 +- homeassistant/components/sensor/mvglive.py | 2 +- homeassistant/components/sensor/netatmo.py | 2 +- homeassistant/components/sensor/netdata.py | 2 +- .../components/sensor/neurio_energy.py | 2 +- .../components/sensor/nsw_fuel_station.py | 2 +- homeassistant/components/sensor/nut.py | 2 +- homeassistant/components/sensor/nzbget.py | 2 +- .../components/sensor/openexchangerates.py | 2 +- .../components/sensor/openhardwaremonitor.py | 2 +- .../components/sensor/openweathermap.py | 2 +- homeassistant/components/sensor/pi_hole.py | 2 +- homeassistant/components/sensor/pollen.py | 2 +- homeassistant/components/sensor/pyload.py | 2 +- homeassistant/components/sensor/qnap.py | 2 +- homeassistant/components/sensor/rest.py | 2 +- homeassistant/components/sensor/sense.py | 2 +- homeassistant/components/sensor/sensehat.py | 2 +- homeassistant/components/sensor/shodan.py | 2 +- homeassistant/components/sensor/sht31.py | 2 +- homeassistant/components/sensor/sigfox.py | 2 +- homeassistant/components/sensor/snmp.py | 2 +- homeassistant/components/sensor/speedtest.py | 2 +- homeassistant/components/sensor/startca.py | 2 +- .../sensor/swiss_hydrological_data.py | 2 +- homeassistant/components/sensor/synologydsm.py | 2 +- homeassistant/components/sensor/sytadin.py | 2 +- homeassistant/components/sensor/ted5000.py | 2 +- homeassistant/components/sensor/teksavvy.py | 2 +- .../components/sensor/thethingsnetwork.py | 2 +- .../components/sensor/transmission.py | 2 +- homeassistant/components/sensor/uber.py | 2 +- .../components/sensor/waterfurnace.py | 2 +- .../components/sensor/wunderground.py | 4 ++-- .../components/sensor/yahoo_finance.py | 2 +- homeassistant/components/sensor/yr.py | 2 +- homeassistant/components/sensor/yweather.py | 2 +- homeassistant/components/sensor/zamg.py | 2 +- homeassistant/components/sensor/zwave.py | 2 +- homeassistant/components/sleepiq.py | 2 +- homeassistant/components/smappee.py | 2 +- .../components/switch/anel_pwrctrl.py | 2 +- homeassistant/components/switch/broadlink.py | 2 +- .../components/switch/digitalloggers.py | 2 +- homeassistant/components/switch/dlink.py | 2 +- homeassistant/components/switch/fritzdect.py | 2 +- homeassistant/components/switch/pilight.py | 2 +- homeassistant/components/switch/scsgate.py | 2 +- homeassistant/components/switch/zwave.py | 2 +- homeassistant/components/tellduslive.py | 2 +- homeassistant/components/toon.py | 2 +- homeassistant/components/tts/__init__.py | 4 ++-- homeassistant/components/upcloud.py | 2 +- homeassistant/components/usps.py | 2 +- homeassistant/components/verisure.py | 2 +- homeassistant/components/vultr.py | 2 +- homeassistant/components/weather/darksky.py | 2 +- .../components/weather/openweathermap.py | 2 +- homeassistant/components/weather/yweather.py | 2 +- homeassistant/components/zigbee.py | 2 +- homeassistant/core.py | 18 +++++++++--------- homeassistant/helpers/entity.py | 2 +- homeassistant/helpers/entity_component.py | 2 +- homeassistant/helpers/entity_platform.py | 2 +- homeassistant/helpers/entity_values.py | 2 +- homeassistant/helpers/state.py | 2 +- homeassistant/helpers/template.py | 8 ++++---- homeassistant/remote.py | 2 +- homeassistant/util/__init__.py | 2 +- homeassistant/util/logging.py | 2 +- homeassistant/util/unit_system.py | 2 +- tests/common.py | 4 ++-- tests/components/alexa/test_smart_home.py | 2 +- tests/components/binary_sensor/test_ffmpeg.py | 4 ++-- tests/components/binary_sensor/test_workday.py | 2 +- tests/components/camera/test_init.py | 4 ++-- .../device_tracker/test_upc_connect.py | 2 +- tests/components/image_processing/test_init.py | 8 ++++---- .../test_microsoft_face_detect.py | 4 ++-- .../test_microsoft_face_identify.py | 4 ++-- .../image_processing/test_openalpr_cloud.py | 4 ++-- .../image_processing/test_openalpr_local.py | 4 ++-- .../components/media_player/test_blackbird.py | 2 +- .../components/media_player/test_monoprice.py | 2 +- tests/components/media_player/test_yamaha.py | 2 +- tests/components/notify/test_html5.py | 2 +- tests/components/test_ffmpeg.py | 2 +- tests/components/test_microsoft_face.py | 2 +- tests/components/test_mqtt_eventstream.py | 2 +- tests/components/test_mqtt_statestream.py | 2 +- tests/components/test_plant.py | 2 +- tests/components/test_rest_command.py | 4 ++-- tests/components/tts/test_google.py | 2 +- tests/components/tts/test_init.py | 2 +- tests/components/tts/test_marytts.py | 2 +- tests/components/tts/test_voicerss.py | 2 +- tests/components/tts/test_yandextts.py | 2 +- tests/helpers/test_dispatcher.py | 2 +- tests/helpers/test_entity.py | 2 +- tests/util/test_init.py | 6 +++--- 242 files changed, 284 insertions(+), 284 deletions(-) diff --git a/homeassistant/components/abode.py b/homeassistant/components/abode.py index 6d5feb87dc2b..bafbc0781caf 100644 --- a/homeassistant/components/abode.py +++ b/homeassistant/components/abode.py @@ -85,7 +85,7 @@ ABODE_PLATFORMS = [ ] -class AbodeSystem(object): +class AbodeSystem: """Abode System class.""" def __init__(self, username, password, cache, diff --git a/homeassistant/components/ads/__init__.py b/homeassistant/components/ads/__init__.py index d603843f51f3..100444c02116 100644 --- a/homeassistant/components/ads/__init__.py +++ b/homeassistant/components/ads/__init__.py @@ -110,7 +110,7 @@ NotificationItem = namedtuple( ) -class AdsHub(object): +class AdsHub: """Representation of an ADS connection.""" def __init__(self, ads_client): diff --git a/homeassistant/components/alexa/intent.py b/homeassistant/components/alexa/intent.py index b6d406bd550f..8d4520d74e83 100644 --- a/homeassistant/components/alexa/intent.py +++ b/homeassistant/components/alexa/intent.py @@ -210,7 +210,7 @@ def resolve_slot_synonyms(key, request): return resolved_value -class AlexaResponse(object): +class AlexaResponse: """Help generating the response for Alexa.""" def __init__(self, hass, intent_info): diff --git a/homeassistant/components/alexa/smart_home.py b/homeassistant/components/alexa/smart_home.py index 9b7da71a2931..80fa4ccb4bfb 100644 --- a/homeassistant/components/alexa/smart_home.py +++ b/homeassistant/components/alexa/smart_home.py @@ -55,7 +55,7 @@ HANDLERS = Registry() ENTITY_ADAPTERS = Registry() -class _DisplayCategory(object): +class _DisplayCategory: """Possible display categories for Discovery response. https://developer.amazon.com/docs/device-apis/alexa-discovery.html#display-categories @@ -153,7 +153,7 @@ class _UnsupportedProperty(Exception): """This entity does not support the requested Smart Home API property.""" -class _AlexaEntity(object): +class _AlexaEntity: """An adaptation of an entity, expressed in Alexa's terms. The API handlers should manipulate entities only through this interface. @@ -208,7 +208,7 @@ class _AlexaEntity(object): raise NotImplementedError -class _AlexaInterface(object): +class _AlexaInterface: def __init__(self, entity): self.entity = entity @@ -615,7 +615,7 @@ class _SensorCapabilities(_AlexaEntity): yield _AlexaTemperatureSensor(self.entity) -class _Cause(object): +class _Cause: """Possible causes for property changes. https://developer.amazon.com/docs/smarthome/state-reporting-for-a-smart-home-skill.html#cause-object diff --git a/homeassistant/components/amcrest.py b/homeassistant/components/amcrest.py index 820ca41ad2e7..bcd0c38c3bdf 100644 --- a/homeassistant/components/amcrest.py +++ b/homeassistant/components/amcrest.py @@ -164,7 +164,7 @@ def setup(hass, config): return True -class AmcrestDevice(object): +class AmcrestDevice: """Representation of a base Amcrest discovery device.""" def __init__(self, camera, name, authentication, ffmpeg_arguments, diff --git a/homeassistant/components/apcupsd.py b/homeassistant/components/apcupsd.py index 7e2b4cda28f8..8808cee79a3b 100644 --- a/homeassistant/components/apcupsd.py +++ b/homeassistant/components/apcupsd.py @@ -58,7 +58,7 @@ def setup(hass, config): return True -class APCUPSdData(object): +class APCUPSdData: """Stores the data retrieved from APCUPSd. For each entity to use, acts as the single point responsible for fetching diff --git a/homeassistant/components/arduino.py b/homeassistant/components/arduino.py index 8625685c057d..785f8c57f943 100644 --- a/homeassistant/components/arduino.py +++ b/homeassistant/components/arduino.py @@ -62,7 +62,7 @@ def setup(hass, config): return True -class ArduinoBoard(object): +class ArduinoBoard: """Representation of an Arduino board.""" def __init__(self, port): diff --git a/homeassistant/components/asterisk_mbox.py b/homeassistant/components/asterisk_mbox.py index 0b5e7c1e1d78..e273d7d6f6a5 100644 --- a/homeassistant/components/asterisk_mbox.py +++ b/homeassistant/components/asterisk_mbox.py @@ -48,7 +48,7 @@ def setup(hass, config): return True -class AsteriskData(object): +class AsteriskData: """Store Asterisk mailbox data.""" def __init__(self, hass, host, port, password): diff --git a/homeassistant/components/binary_sensor/arest.py b/homeassistant/components/binary_sensor/arest.py index 73751ef14bb0..0366f753ba6c 100644 --- a/homeassistant/components/binary_sensor/arest.py +++ b/homeassistant/components/binary_sensor/arest.py @@ -89,7 +89,7 @@ class ArestBinarySensor(BinarySensorDevice): self.arest.update() -class ArestData(object): +class ArestData: """Class for handling the data retrieval for pins.""" def __init__(self, resource, pin): diff --git a/homeassistant/components/binary_sensor/aurora.py b/homeassistant/components/binary_sensor/aurora.py index 772792f5785a..0c33877854fd 100644 --- a/homeassistant/components/binary_sensor/aurora.py +++ b/homeassistant/components/binary_sensor/aurora.py @@ -99,7 +99,7 @@ class AuroraSensor(BinarySensorDevice): self.aurora_data.update() -class AuroraData(object): +class AuroraData: """Get aurora forecast.""" def __init__(self, latitude, longitude, threshold): diff --git a/homeassistant/components/binary_sensor/hikvision.py b/homeassistant/components/binary_sensor/hikvision.py index f9ff4ac0a7a7..de6ad8223d72 100644 --- a/homeassistant/components/binary_sensor/hikvision.py +++ b/homeassistant/components/binary_sensor/hikvision.py @@ -117,7 +117,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(entities) -class HikvisionData(object): +class HikvisionData: """Hikvision device event stream object.""" def __init__(self, hass, url, port, name, username, password): diff --git a/homeassistant/components/binary_sensor/iss.py b/homeassistant/components/binary_sensor/iss.py index d35c36a012e9..d0654317248e 100644 --- a/homeassistant/components/binary_sensor/iss.py +++ b/homeassistant/components/binary_sensor/iss.py @@ -101,7 +101,7 @@ class IssBinarySensor(BinarySensorDevice): self.iss_data.update() -class IssData(object): +class IssData: """Get data from the ISS API.""" def __init__(self, latitude, longitude): diff --git a/homeassistant/components/binary_sensor/ping.py b/homeassistant/components/binary_sensor/ping.py index 0830d86dc2ac..bb597f208e66 100644 --- a/homeassistant/components/binary_sensor/ping.py +++ b/homeassistant/components/binary_sensor/ping.py @@ -96,7 +96,7 @@ class PingBinarySensor(BinarySensorDevice): self.ping.update() -class PingData(object): +class PingData: """The Class for handling the data retrieval.""" def __init__(self, host, count): diff --git a/homeassistant/components/binary_sensor/tapsaff.py b/homeassistant/components/binary_sensor/tapsaff.py index c0f6ca3f1122..5b8e133b5f42 100644 --- a/homeassistant/components/binary_sensor/tapsaff.py +++ b/homeassistant/components/binary_sensor/tapsaff.py @@ -63,7 +63,7 @@ class TapsAffSensor(BinarySensorDevice): self.data.update() -class TapsAffData(object): +class TapsAffData: """Class for handling the data retrieval for pins.""" def __init__(self, location): diff --git a/homeassistant/components/binary_sensor/zwave.py b/homeassistant/components/binary_sensor/zwave.py index fc18648f9079..784a96d86157 100644 --- a/homeassistant/components/binary_sensor/zwave.py +++ b/homeassistant/components/binary_sensor/zwave.py @@ -10,7 +10,7 @@ import homeassistant.util.dt as dt_util from homeassistant.helpers.event import track_point_in_time from homeassistant.components import zwave from homeassistant.components.zwave import workaround -from homeassistant.components.zwave import async_setup_platform # noqa # pylint: disable=unused-import +from homeassistant.components.zwave import async_setup_platform # noqa pylint: disable=unused-import from homeassistant.components.binary_sensor import ( DOMAIN, BinarySensorDevice) diff --git a/homeassistant/components/blink.py b/homeassistant/components/blink.py index a44f0163787b..e84643711ebb 100644 --- a/homeassistant/components/blink.py +++ b/homeassistant/components/blink.py @@ -40,7 +40,7 @@ SNAP_PICTURE_SCHEMA = vol.Schema({ }) -class BlinkSystem(object): +class BlinkSystem: """Blink System class.""" def __init__(self, config_info): diff --git a/homeassistant/components/bloomsky.py b/homeassistant/components/bloomsky.py index bc9d3acf54fe..00377b3f12bd 100644 --- a/homeassistant/components/bloomsky.py +++ b/homeassistant/components/bloomsky.py @@ -50,7 +50,7 @@ def setup(hass, config): return True -class BloomSky(object): +class BloomSky: """Handle all communication with the BloomSky API.""" # API documentation at http://weatherlution.com/bloomsky-api/ diff --git a/homeassistant/components/bmw_connected_drive/__init__.py b/homeassistant/components/bmw_connected_drive/__init__.py index a7ed262ac2c0..061b09c1b3b5 100644 --- a/homeassistant/components/bmw_connected_drive/__init__.py +++ b/homeassistant/components/bmw_connected_drive/__init__.py @@ -118,7 +118,7 @@ def setup_account(account_config: dict, hass, name: str) \ return cd_account -class BMWConnectedDriveAccount(object): +class BMWConnectedDriveAccount: """Representation of a BMW vehicle.""" def __init__(self, username: str, password: str, region_str: str, diff --git a/homeassistant/components/calendar/caldav.py b/homeassistant/components/calendar/caldav.py index 9c30d1481f8a..3db24790aaf4 100644 --- a/homeassistant/components/calendar/caldav.py +++ b/homeassistant/components/calendar/caldav.py @@ -125,7 +125,7 @@ class WebDavCalendarEventDevice(CalendarEventDevice): return await self.data.async_get_events(hass, start_date, end_date) -class WebDavCalendarData(object): +class WebDavCalendarData: """Class to utilize the calendar dav client object to get next event.""" def __init__(self, calendar, include_all_day, search): diff --git a/homeassistant/components/calendar/demo.py b/homeassistant/components/calendar/demo.py index 53129d3316cf..0bf09f6f2c76 100644 --- a/homeassistant/components/calendar/demo.py +++ b/homeassistant/components/calendar/demo.py @@ -28,7 +28,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): ]) -class DemoGoogleCalendarData(object): +class DemoGoogleCalendarData: """Representation of a Demo Calendar element.""" event = {} diff --git a/homeassistant/components/calendar/google.py b/homeassistant/components/calendar/google.py index 279fb1e2694c..925bbcacddf1 100644 --- a/homeassistant/components/calendar/google.py +++ b/homeassistant/components/calendar/google.py @@ -55,7 +55,7 @@ class GoogleCalendarEventDevice(CalendarEventDevice): return await self.data.async_get_events(hass, start_date, end_date) -class GoogleCalendarData(object): +class GoogleCalendarData: """Class to utilize calendar service object to get next event.""" def __init__(self, calendar_service, calendar_id, search, diff --git a/homeassistant/components/calendar/todoist.py b/homeassistant/components/calendar/todoist.py index 42ab7b01c63b..ba1f60027ba3 100644 --- a/homeassistant/components/calendar/todoist.py +++ b/homeassistant/components/calendar/todoist.py @@ -280,7 +280,7 @@ class TodoistProjectDevice(CalendarEventDevice): return attributes -class TodoistProjectData(object): +class TodoistProjectData: """ Class used by the Task Device service object to hold all Todoist Tasks. diff --git a/homeassistant/components/canary.py b/homeassistant/components/canary.py index 4d0fbe617b2c..04c33d83f3db 100644 --- a/homeassistant/components/canary.py +++ b/homeassistant/components/canary.py @@ -65,7 +65,7 @@ def setup(hass, config): return True -class CanaryData(object): +class CanaryData: """Get the latest data and update the states.""" def __init__(self, username, password, timeout): diff --git a/homeassistant/components/climate/netatmo.py b/homeassistant/components/climate/netatmo.py index a4b921037dbe..431834151fd1 100644 --- a/homeassistant/components/climate/netatmo.py +++ b/homeassistant/components/climate/netatmo.py @@ -140,7 +140,7 @@ class NetatmoThermostat(ClimateDevice): self._away = self._data.setpoint_mode == 'away' -class ThermostatData(object): +class ThermostatData: """Get the latest data from Netatmo.""" def __init__(self, auth, device=None): diff --git a/homeassistant/components/climate/zwave.py b/homeassistant/components/climate/zwave.py index 52c544256b67..b1dcacb56543 100644 --- a/homeassistant/components/climate/zwave.py +++ b/homeassistant/components/climate/zwave.py @@ -11,7 +11,7 @@ from homeassistant.components.climate import ( SUPPORT_TARGET_TEMPERATURE, SUPPORT_FAN_MODE, SUPPORT_OPERATION_MODE, SUPPORT_SWING_MODE) from homeassistant.components.zwave import ZWaveDeviceEntity -from homeassistant.components.zwave import async_setup_platform # noqa # pylint: disable=unused-import +from homeassistant.components.zwave import async_setup_platform # noqa pylint: disable=unused-import from homeassistant.const import ( STATE_OFF, TEMP_CELSIUS, TEMP_FAHRENHEIT, ATTR_TEMPERATURE) diff --git a/homeassistant/components/coinbase.py b/homeassistant/components/coinbase.py index c40bd99b542a..154320b4abd3 100644 --- a/homeassistant/components/coinbase.py +++ b/homeassistant/components/coinbase.py @@ -69,7 +69,7 @@ def setup(hass, config): return True -class CoinbaseData(object): +class CoinbaseData: """Get the latest data and update the states.""" def __init__(self, api_key, api_secret): diff --git a/homeassistant/components/comfoconnect.py b/homeassistant/components/comfoconnect.py index 425ed6f9c9a5..69d88274f296 100644 --- a/homeassistant/components/comfoconnect.py +++ b/homeassistant/components/comfoconnect.py @@ -88,7 +88,7 @@ def setup(hass, config): return True -class ComfoConnectBridge(object): +class ComfoConnectBridge: """Representation of a ComfoConnect bridge.""" def __init__(self, hass, bridge, name, token, friendly_name, pin): diff --git a/homeassistant/components/configurator.py b/homeassistant/components/configurator.py index 2c159633a9b5..56fb7b4247b0 100644 --- a/homeassistant/components/configurator.py +++ b/homeassistant/components/configurator.py @@ -128,7 +128,7 @@ def async_setup(hass, config): return True -class Configurator(object): +class Configurator: """The class to keep track of current configuration requests.""" def __init__(self, hass): diff --git a/homeassistant/components/cover/zwave.py b/homeassistant/components/cover/zwave.py index c29c11c5b6bf..d5de8863543b 100644 --- a/homeassistant/components/cover/zwave.py +++ b/homeassistant/components/cover/zwave.py @@ -11,7 +11,7 @@ from homeassistant.components.cover import ( DOMAIN, SUPPORT_OPEN, SUPPORT_CLOSE, ATTR_POSITION) from homeassistant.components.zwave import ZWaveDeviceEntity from homeassistant.components import zwave -from homeassistant.components.zwave import async_setup_platform # noqa # pylint: disable=unused-import +from homeassistant.components.zwave import async_setup_platform # noqa pylint: disable=unused-import from homeassistant.components.zwave import workaround from homeassistant.components.cover import CoverDevice diff --git a/homeassistant/components/daikin.py b/homeassistant/components/daikin.py index 5808528ca5ad..8983ecf82d82 100644 --- a/homeassistant/components/daikin.py +++ b/homeassistant/components/daikin.py @@ -115,7 +115,7 @@ def daikin_api_setup(hass, host, name=None): return api -class DaikinApi(object): +class DaikinApi: """Keep the Daikin instance in one place and centralize the update.""" def __init__(self, device, name): diff --git a/homeassistant/components/deconz/__init__.py b/homeassistant/components/deconz/__init__.py index 88174b9d6129..414d4b0766c8 100644 --- a/homeassistant/components/deconz/__init__.py +++ b/homeassistant/components/deconz/__init__.py @@ -178,7 +178,7 @@ async def async_unload_entry(hass, config_entry): return True -class DeconzEvent(object): +class DeconzEvent: """When you want signals instead of entities. Stateless sensors such as remotes are expected to generate an event diff --git a/homeassistant/components/device_tracker/__init__.py b/homeassistant/components/device_tracker/__init__.py index b95a0a9d73b6..391f36ad6234 100644 --- a/homeassistant/components/device_tracker/__init__.py +++ b/homeassistant/components/device_tracker/__init__.py @@ -231,7 +231,7 @@ def async_setup(hass: HomeAssistantType, config: ConfigType): return True -class DeviceTracker(object): +class DeviceTracker: """Representation of a device tracker.""" def __init__(self, hass: HomeAssistantType, consider_home: timedelta, @@ -577,7 +577,7 @@ class Device(Entity): state.attributes[ATTR_LONGITUDE]) -class DeviceScanner(object): +class DeviceScanner: """Device scanner object.""" hass = None # type: HomeAssistantType diff --git a/homeassistant/components/device_tracker/automatic.py b/homeassistant/components/device_tracker/automatic.py index 607f236f9205..4fcc550d7dbd 100644 --- a/homeassistant/components/device_tracker/automatic.py +++ b/homeassistant/components/device_tracker/automatic.py @@ -193,7 +193,7 @@ class AutomaticAuthCallbackView(HomeAssistantView): return response -class AutomaticData(object): +class AutomaticData: """A class representing an Automatic cloud service connection.""" def __init__(self, hass, client, session, devices, async_see): diff --git a/homeassistant/components/device_tracker/bmw_connected_drive.py b/homeassistant/components/device_tracker/bmw_connected_drive.py index f36afc622ee1..02a126531807 100644 --- a/homeassistant/components/device_tracker/bmw_connected_drive.py +++ b/homeassistant/components/device_tracker/bmw_connected_drive.py @@ -27,7 +27,7 @@ def setup_scanner(hass, config, see, discovery_info=None): return True -class BMWDeviceTracker(object): +class BMWDeviceTracker: """BMW Connected Drive device tracker.""" def __init__(self, see, vehicle): diff --git a/homeassistant/components/device_tracker/freebox.py b/homeassistant/components/device_tracker/freebox.py index b278c4219254..2cac81fd405d 100644 --- a/homeassistant/components/device_tracker/freebox.py +++ b/homeassistant/components/device_tracker/freebox.py @@ -62,7 +62,7 @@ def _build_device(device_dict): device_dict['l3connectivities'][0]['addr']) -class FreeboxDeviceScanner(object): +class FreeboxDeviceScanner: """This class scans for devices connected to the Freebox.""" def __init__(self, hass, config, async_see): diff --git a/homeassistant/components/device_tracker/google_maps.py b/homeassistant/components/device_tracker/google_maps.py index 5f06946fc446..d0669ab4967d 100644 --- a/homeassistant/components/device_tracker/google_maps.py +++ b/homeassistant/components/device_tracker/google_maps.py @@ -42,7 +42,7 @@ def setup_scanner(hass, config: ConfigType, see, discovery_info=None): return scanner.success_init -class GoogleMapsScanner(object): +class GoogleMapsScanner: """Representation of an Google Maps location sharing account.""" def __init__(self, hass, config: ConfigType, see) -> None: diff --git a/homeassistant/components/device_tracker/ping.py b/homeassistant/components/device_tracker/ping.py index 6a0cb18d55ec..d09e1930d4f7 100644 --- a/homeassistant/components/device_tracker/ping.py +++ b/homeassistant/components/device_tracker/ping.py @@ -28,7 +28,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ }) -class Host(object): +class Host: """Host object with ping detection.""" def __init__(self, ip_address, dev_id, hass, config): diff --git a/homeassistant/components/device_tracker/tesla.py b/homeassistant/components/device_tracker/tesla.py index ba9bc8c2631a..c08ddb4047b6 100644 --- a/homeassistant/components/device_tracker/tesla.py +++ b/homeassistant/components/device_tracker/tesla.py @@ -23,7 +23,7 @@ def setup_scanner(hass, config, see, discovery_info=None): return True -class TeslaDeviceTracker(object): +class TeslaDeviceTracker: """A class representing a Tesla device.""" def __init__(self, hass, config, see, tesla_devices): diff --git a/homeassistant/components/device_tracker/tile.py b/homeassistant/components/device_tracker/tile.py index 6df9f3c9974c..526c1a4b47b6 100644 --- a/homeassistant/components/device_tracker/tile.py +++ b/homeassistant/components/device_tracker/tile.py @@ -74,7 +74,7 @@ async def async_setup_scanner(hass, config, async_see, discovery_info=None): return await scanner.async_init() -class TileScanner(object): +class TileScanner: """Define an object to retrieve Tile data.""" def __init__(self, client, hass, async_see, types, show_inactive): diff --git a/homeassistant/components/device_tracker/trackr.py b/homeassistant/components/device_tracker/trackr.py index 84fb449c0701..08d3a4c94458 100644 --- a/homeassistant/components/device_tracker/trackr.py +++ b/homeassistant/components/device_tracker/trackr.py @@ -30,7 +30,7 @@ def setup_scanner(hass, config: dict, see, discovery_info=None): return True -class TrackRDeviceScanner(object): +class TrackRDeviceScanner: """A class representing a TrackR device.""" def __init__(self, hass, config: dict, see) -> None: diff --git a/homeassistant/components/dialogflow.py b/homeassistant/components/dialogflow.py index 28b3a05e403c..0f275a7fe66e 100644 --- a/homeassistant/components/dialogflow.py +++ b/homeassistant/components/dialogflow.py @@ -119,7 +119,7 @@ async def async_handle_message(hass, message): return dialogflow_response.as_dict() -class DialogflowResponse(object): +class DialogflowResponse: """Help generating the response for Dialogflow.""" def __init__(self, parameters): diff --git a/homeassistant/components/digital_ocean.py b/homeassistant/components/digital_ocean.py index a0f508426494..c0c9d95586c4 100644 --- a/homeassistant/components/digital_ocean.py +++ b/homeassistant/components/digital_ocean.py @@ -65,7 +65,7 @@ def setup(hass, config): return True -class DigitalOcean(object): +class DigitalOcean: """Handle all communication with the Digital Ocean API.""" def __init__(self, access_token): diff --git a/homeassistant/components/ecobee.py b/homeassistant/components/ecobee.py index 96f094b527df..3829c2caebd4 100644 --- a/homeassistant/components/ecobee.py +++ b/homeassistant/components/ecobee.py @@ -84,7 +84,7 @@ def setup_ecobee(hass, network, config): discovery.load_platform(hass, 'weather', DOMAIN, {}, config) -class EcobeeData(object): +class EcobeeData: """Get the latest data and update the states.""" def __init__(self, config_file): diff --git a/homeassistant/components/emulated_hue/__init__.py b/homeassistant/components/emulated_hue/__init__.py index 36ce1c392f99..8a67b933b9f7 100644 --- a/homeassistant/components/emulated_hue/__init__.py +++ b/homeassistant/components/emulated_hue/__init__.py @@ -136,7 +136,7 @@ def setup(hass, yaml_config): return True -class Config(object): +class Config: """Hold configuration variables for the emulated hue bridge.""" def __init__(self, hass, conf): diff --git a/homeassistant/components/fan/zwave.py b/homeassistant/components/fan/zwave.py index 364306ff8ddd..645cb033e133 100644 --- a/homeassistant/components/fan/zwave.py +++ b/homeassistant/components/fan/zwave.py @@ -11,7 +11,7 @@ from homeassistant.components.fan import ( DOMAIN, FanEntity, SPEED_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH, SUPPORT_SET_SPEED) from homeassistant.components import zwave -from homeassistant.components.zwave import async_setup_platform # noqa # pylint: disable=unused-import +from homeassistant.components.zwave import async_setup_platform # noqa pylint: disable=unused-import _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/feedreader.py b/homeassistant/components/feedreader.py index 56f153517cfb..782fd8ac8ddc 100644 --- a/homeassistant/components/feedreader.py +++ b/homeassistant/components/feedreader.py @@ -53,7 +53,7 @@ def setup(hass, config): return len(feeds) > 0 -class FeedManager(object): +class FeedManager: """Abstraction over Feedparser module.""" def __init__(self, url, scan_interval, max_entries, hass, storage): @@ -170,7 +170,7 @@ class FeedManager(object): self._firstrun = False -class StoredData(object): +class StoredData: """Abstraction over pickle data storage.""" def __init__(self, data_file): diff --git a/homeassistant/components/ffmpeg.py b/homeassistant/components/ffmpeg.py index e083affe92bc..9aaae16ee21e 100644 --- a/homeassistant/components/ffmpeg.py +++ b/homeassistant/components/ffmpeg.py @@ -116,7 +116,7 @@ def async_setup(hass, config): return True -class FFmpegManager(object): +class FFmpegManager: """Helper for ha-ffmpeg.""" def __init__(self, hass, ffmpeg_bin, run_test): diff --git a/homeassistant/components/gc100.py b/homeassistant/components/gc100.py index 25bcb5b0f79f..0d4b19da0300 100644 --- a/homeassistant/components/gc100.py +++ b/homeassistant/components/gc100.py @@ -53,7 +53,7 @@ def setup(hass, base_config): return True -class GC100Device(object): +class GC100Device: """The GC100 component.""" def __init__(self, hass, gc_device): diff --git a/homeassistant/components/google.py b/homeassistant/components/google.py index fdbc33820720..e37b3ba7ff7e 100644 --- a/homeassistant/components/google.py +++ b/homeassistant/components/google.py @@ -231,7 +231,7 @@ def do_setup(hass, config): return True -class GoogleCalendarService(object): +class GoogleCalendarService: """Calendar service interface to Google.""" def __init__(self, token_file): diff --git a/homeassistant/components/hassio/handler.py b/homeassistant/components/hassio/handler.py index c3caf40ba62a..5410cb9ec1a4 100644 --- a/homeassistant/components/hassio/handler.py +++ b/homeassistant/components/hassio/handler.py @@ -45,7 +45,7 @@ def _api_data(funct): return _wrapper -class HassIO(object): +class HassIO: """Small API wrapper for Hass.io.""" def __init__(self, loop, websession, ip): diff --git a/homeassistant/components/history.py b/homeassistant/components/history.py index 7ee1c70487fe..21d4cdc6e560 100644 --- a/homeassistant/components/history.py +++ b/homeassistant/components/history.py @@ -353,7 +353,7 @@ class HistoryPeriodView(HomeAssistantView): return await hass.async_add_job(self.json, result) -class Filters(object): +class Filters: """Container for the configured include and exclude filters.""" def __init__(self): diff --git a/homeassistant/components/homematicip_cloud/hap.py b/homeassistant/components/homematicip_cloud/hap.py index a4e3e78e8608..57bb9488ee83 100644 --- a/homeassistant/components/homematicip_cloud/hap.py +++ b/homeassistant/components/homematicip_cloud/hap.py @@ -14,7 +14,7 @@ from .errors import HmipcConnectionError _LOGGER = logging.getLogger(__name__) -class HomematicipAuth(object): +class HomematicipAuth: """Manages HomematicIP client registration.""" def __init__(self, hass, config): @@ -73,7 +73,7 @@ class HomematicipAuth(object): return auth -class HomematicipHAP(object): +class HomematicipHAP: """Manages HomematicIP http and websocket connection.""" def __init__(self, hass, config_entry): diff --git a/homeassistant/components/http/__init__.py b/homeassistant/components/http/__init__.py index 0cbee628a8a9..42629f752ad1 100644 --- a/homeassistant/components/http/__init__.py +++ b/homeassistant/components/http/__init__.py @@ -152,7 +152,7 @@ async def async_setup(hass, config): return True -class HomeAssistantHTTP(object): +class HomeAssistantHTTP: """HTTP server for Home Assistant.""" def __init__(self, hass, api_password, diff --git a/homeassistant/components/http/ban.py b/homeassistant/components/http/ban.py index fe8b7db84d1b..7bb5194b1ec4 100644 --- a/homeassistant/components/http/ban.py +++ b/homeassistant/components/http/ban.py @@ -107,7 +107,7 @@ async def process_wrong_login(request): 'Banning IP address', NOTIFICATION_ID_BAN) -class IpBan(object): +class IpBan: """Represents banned IP address.""" def __init__(self, ip_ban: str, banned_at: datetime = None) -> None: diff --git a/homeassistant/components/http/view.py b/homeassistant/components/http/view.py index 23698af81013..34b72e9ed69f 100644 --- a/homeassistant/components/http/view.py +++ b/homeassistant/components/http/view.py @@ -21,7 +21,7 @@ from .const import KEY_AUTHENTICATED, KEY_REAL_IP _LOGGER = logging.getLogger(__name__) -class HomeAssistantView(object): +class HomeAssistantView: """Base view for all views.""" url = None diff --git a/homeassistant/components/hue/bridge.py b/homeassistant/components/hue/bridge.py index 8710b2561b05..f528de462efe 100644 --- a/homeassistant/components/hue/bridge.py +++ b/homeassistant/components/hue/bridge.py @@ -19,7 +19,7 @@ SCENE_SCHEMA = vol.Schema({ }) -class HueBridge(object): +class HueBridge: """Manages a single Hue bridge.""" def __init__(self, hass, config_entry, allow_unreachable, allow_groups): diff --git a/homeassistant/components/hydrawise.py b/homeassistant/components/hydrawise.py index a60e3d5b8fc9..0c4db63034ea 100644 --- a/homeassistant/components/hydrawise.py +++ b/homeassistant/components/hydrawise.py @@ -101,7 +101,7 @@ def setup(hass, config): return True -class HydrawiseHub(object): +class HydrawiseHub: """Representation of a base Hydrawise device.""" def __init__(self, data): diff --git a/homeassistant/components/insteon_plm/__init__.py b/homeassistant/components/insteon_plm/__init__.py index ef631223894b..7845b47e2189 100644 --- a/homeassistant/components/insteon_plm/__init__.py +++ b/homeassistant/components/insteon_plm/__init__.py @@ -316,7 +316,7 @@ def async_setup(hass, config): State = collections.namedtuple('Product', 'stateType platform') -class IPDB(object): +class IPDB: """Embodies the INSTEON Product Database static data and access methods.""" def __init__(self): diff --git a/homeassistant/components/keyboard_remote.py b/homeassistant/components/keyboard_remote.py index bbd7bc440823..9a7cc7caecbf 100644 --- a/homeassistant/components/keyboard_remote.py +++ b/homeassistant/components/keyboard_remote.py @@ -163,7 +163,7 @@ class KeyboardRemoteThread(threading.Thread): ) -class KeyboardRemote(object): +class KeyboardRemote: """Sets up one thread per device.""" def __init__(self, hass, config): diff --git a/homeassistant/components/knx.py b/homeassistant/components/knx.py index 61f8ca90137f..f8db21118ecd 100644 --- a/homeassistant/components/knx.py +++ b/homeassistant/components/knx.py @@ -129,7 +129,7 @@ def _get_devices(hass, discovery_type): hass.data[DATA_KNX].xknx.devices))) -class KNXModule(object): +class KNXModule: """Representation of KNX Object.""" def __init__(self, hass, config): @@ -284,7 +284,7 @@ class KNXAutomation(): device.actions.append(self.action) -class KNXExposeTime(object): +class KNXExposeTime: """Object to Expose Time/Date object to KNX bus.""" def __init__(self, xknx, expose_type, address): @@ -308,7 +308,7 @@ class KNXExposeTime(object): self.xknx.devices.add(self.device) -class KNXExposeSensor(object): +class KNXExposeSensor: """Object to Expose HASS entity to KNX bus.""" def __init__(self, hass, xknx, expose_type, entity_id, address): diff --git a/homeassistant/components/konnected.py b/homeassistant/components/konnected.py index 26fe356d7724..a3e9ff86ed01 100644 --- a/homeassistant/components/konnected.py +++ b/homeassistant/components/konnected.py @@ -114,7 +114,7 @@ async def async_setup(hass, config): return True -class KonnectedDevice(object): +class KonnectedDevice: """A representation of a single Konnected device.""" def __init__(self, hass, host, port, config): diff --git a/homeassistant/components/light/greenwave.py b/homeassistant/components/light/greenwave.py index 8e9d93657cef..52a70532005d 100644 --- a/homeassistant/components/light/greenwave.py +++ b/homeassistant/components/light/greenwave.py @@ -121,7 +121,7 @@ class GreenwaveLight(Light): self._name = bulbs[self._did]['name'] -class GatewayData(object): +class GatewayData: """Handle Gateway data and limit updates.""" def __init__(self, host, token): diff --git a/homeassistant/components/light/lifx.py b/homeassistant/components/light/lifx.py index 9b2c183c1d1a..3738fd8f0041 100644 --- a/homeassistant/components/light/lifx.py +++ b/homeassistant/components/light/lifx.py @@ -204,7 +204,7 @@ def merge_hsbk(base, change): return [b if c is None else c for b, c in zip(base, change)] -class LIFXManager(object): +class LIFXManager: """Representation of all known LIFX entities.""" def __init__(self, hass, async_add_devices): diff --git a/homeassistant/components/light/lifx_legacy.py b/homeassistant/components/light/lifx_legacy.py index 182d7536dc45..3ad75a1cea4c 100644 --- a/homeassistant/components/light/lifx_legacy.py +++ b/homeassistant/components/light/lifx_legacy.py @@ -58,7 +58,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): lifx_library.probe() -class LIFX(object): +class LIFX: """Representation of a LIFX light.""" def __init__(self, add_devices_callback, server_addr=None, diff --git a/homeassistant/components/light/zwave.py b/homeassistant/components/light/zwave.py index f468e8c25ef1..55feef496f8b 100644 --- a/homeassistant/components/light/zwave.py +++ b/homeassistant/components/light/zwave.py @@ -12,7 +12,7 @@ from homeassistant.components.light import ( ATTR_TRANSITION, SUPPORT_BRIGHTNESS, SUPPORT_COLOR_TEMP, SUPPORT_COLOR, SUPPORT_TRANSITION, SUPPORT_WHITE_VALUE, DOMAIN, Light) from homeassistant.components import zwave -from homeassistant.components.zwave import async_setup_platform # noqa # pylint: disable=unused-import +from homeassistant.components.zwave import async_setup_platform # noqa pylint: disable=unused-import from homeassistant.const import STATE_OFF, STATE_ON import homeassistant.util.color as color_util diff --git a/homeassistant/components/linode.py b/homeassistant/components/linode.py index 962e30774b81..c98ef16c7ed6 100644 --- a/homeassistant/components/linode.py +++ b/homeassistant/components/linode.py @@ -62,7 +62,7 @@ def setup(hass, config): return True -class Linode(object): +class Linode: """Handle all communication with the Linode API.""" def __init__(self, access_token): diff --git a/homeassistant/components/logbook.py b/homeassistant/components/logbook.py index eb2e83912210..b9970e3466e8 100644 --- a/homeassistant/components/logbook.py +++ b/homeassistant/components/logbook.py @@ -140,7 +140,7 @@ class LogbookView(HomeAssistantView): return await hass.async_add_job(json_events) -class Entry(object): +class Entry: """A human readable version of the log.""" def __init__(self, when=None, name=None, message=None, domain=None, diff --git a/homeassistant/components/mailbox/__init__.py b/homeassistant/components/mailbox/__init__.py index 8ff3746889e4..6a648e4dc477 100644 --- a/homeassistant/components/mailbox/__init__.py +++ b/homeassistant/components/mailbox/__init__.py @@ -132,7 +132,7 @@ class MailboxEntity(Entity): self.message_count = len(messages) -class Mailbox(object): +class Mailbox: """Represent a mailbox device.""" def __init__(self, hass, name): diff --git a/homeassistant/components/matrix.py b/homeassistant/components/matrix.py index b2805c994e87..5f6c30aaeba5 100644 --- a/homeassistant/components/matrix.py +++ b/homeassistant/components/matrix.py @@ -96,7 +96,7 @@ def setup(hass, config): return True -class MatrixBot(object): +class MatrixBot: """The Matrix Bot.""" def __init__(self, hass, config_file, homeserver, verify_ssl, diff --git a/homeassistant/components/maxcube.py b/homeassistant/components/maxcube.py index bca7a1b4ab7e..b574f0bcb152 100644 --- a/homeassistant/components/maxcube.py +++ b/homeassistant/components/maxcube.py @@ -79,7 +79,7 @@ def setup(hass, config): return True -class MaxCubeHandle(object): +class MaxCubeHandle: """Keep the cube instance in one place and centralize the update.""" def __init__(self, cube, scan_interval): diff --git a/homeassistant/components/media_extractor.py b/homeassistant/components/media_extractor.py index 21accdf84b3a..2ecee06d8a39 100644 --- a/homeassistant/components/media_extractor.py +++ b/homeassistant/components/media_extractor.py @@ -58,7 +58,7 @@ class MEQueryException(Exception): pass -class MediaExtractor(object): +class MediaExtractor: """Class which encapsulates all extraction logic.""" def __init__(self, hass, component_config, call_data): diff --git a/homeassistant/components/media_player/cast.py b/homeassistant/components/media_player/cast.py index 4e24d5f2f713..32ceadf248fe 100644 --- a/homeassistant/components/media_player/cast.py +++ b/homeassistant/components/media_player/cast.py @@ -63,7 +63,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ @attr.s(slots=True, frozen=True) -class ChromecastInfo(object): +class ChromecastInfo: """Class to hold all data about a chromecast for creating connections. This also has the same attributes as the mDNS fields by zeroconf. @@ -258,7 +258,7 @@ async def _async_setup_platform(hass: HomeAssistantType, config: ConfigType, hass.async_add_job(_discover_chromecast, hass, info) -class CastStatusListener(object): +class CastStatusListener: """Helper class to handle pychromecast status callbacks. Necessary because a CastDevice entity can create a new socket client diff --git a/homeassistant/components/media_player/firetv.py b/homeassistant/components/media_player/firetv.py index 280a84f0828e..979aec57c74a 100644 --- a/homeassistant/components/media_player/firetv.py +++ b/homeassistant/components/media_player/firetv.py @@ -68,7 +68,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): _LOGGER.error("Could not connect to firetv-server at %s", host) -class FireTV(object): +class FireTV: """The firetv-server client. Should a native Python 3 ADB module become available, python-firetv can diff --git a/homeassistant/components/media_player/itunes.py b/homeassistant/components/media_player/itunes.py index ca0979f1752d..e5f7a2f9432d 100644 --- a/homeassistant/components/media_player/itunes.py +++ b/homeassistant/components/media_player/itunes.py @@ -41,7 +41,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ }) -class Itunes(object): +class Itunes: """The iTunes API client.""" def __init__(self, host, port, use_ssl): diff --git a/homeassistant/components/media_player/squeezebox.py b/homeassistant/components/media_player/squeezebox.py index 371ad8903641..8eb4c85f6b27 100644 --- a/homeassistant/components/media_player/squeezebox.py +++ b/homeassistant/components/media_player/squeezebox.py @@ -143,7 +143,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): return True -class LogitechMediaServer(object): +class LogitechMediaServer: """Representation of a Logitech media server.""" def __init__(self, hass, host, port, username, password): diff --git a/homeassistant/components/microsoft_face.py b/homeassistant/components/microsoft_face.py index 847f4131f433..e0e0e716d2e8 100644 --- a/homeassistant/components/microsoft_face.py +++ b/homeassistant/components/microsoft_face.py @@ -289,7 +289,7 @@ class MicrosoftFaceGroupEntity(Entity): return attr -class MicrosoftFace(object): +class MicrosoftFace: """Microsoft Face api for HomeAssistant.""" def __init__(self, hass, server_loc, api_key, timeout, entities): diff --git a/homeassistant/components/mochad.py b/homeassistant/components/mochad.py index 9f53f84e020a..7e6738b95f8c 100644 --- a/homeassistant/components/mochad.py +++ b/homeassistant/components/mochad.py @@ -61,7 +61,7 @@ def setup(hass, config): return True -class MochadCtrl(object): +class MochadCtrl: """Mochad controller.""" def __init__(self, host, port): diff --git a/homeassistant/components/modbus.py b/homeassistant/components/modbus.py index fc6db96e029b..f484cb31a6c9 100644 --- a/homeassistant/components/modbus.py +++ b/homeassistant/components/modbus.py @@ -157,7 +157,7 @@ def setup(hass, config): return True -class ModbusHub(object): +class ModbusHub: """Thread safe wrapper class for pymodbus.""" def __init__(self, modbus_client): diff --git a/homeassistant/components/mqtt/__init__.py b/homeassistant/components/mqtt/__init__.py index 55d99a0817e1..3928eb945aa1 100644 --- a/homeassistant/components/mqtt/__init__.py +++ b/homeassistant/components/mqtt/__init__.py @@ -462,7 +462,7 @@ async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool: @attr.s(slots=True, frozen=True) -class Subscription(object): +class Subscription: """Class to hold data about an active subscription.""" topic = attr.ib(type=str) @@ -472,7 +472,7 @@ class Subscription(object): @attr.s(slots=True, frozen=True) -class Message(object): +class Message: """MQTT Message.""" topic = attr.ib(type=str) @@ -481,7 +481,7 @@ class Message(object): retain = attr.ib(type=bool, default=False) -class MQTT(object): +class MQTT: """Home Assistant MQTT client.""" def __init__(self, hass: HomeAssistantType, broker: str, port: int, diff --git a/homeassistant/components/mychevy.py b/homeassistant/components/mychevy.py index 3531c6b4919d..292e56418fc1 100644 --- a/homeassistant/components/mychevy.py +++ b/homeassistant/components/mychevy.py @@ -41,7 +41,7 @@ CONFIG_SCHEMA = vol.Schema({ }, extra=vol.ALLOW_EXTRA) -class EVSensorConfig(object): +class EVSensorConfig: """The EV sensor configuration.""" def __init__(self, name, attr, unit_of_measurement=None, icon=None): @@ -52,7 +52,7 @@ class EVSensorConfig(object): self.icon = icon -class EVBinarySensorConfig(object): +class EVBinarySensorConfig: """The EV binary sensor configuration.""" def __init__(self, name, attr, device_class=None): diff --git a/homeassistant/components/mysensors/device.py b/homeassistant/components/mysensors/device.py index b0770f90c1db..3ae99f61d17f 100644 --- a/homeassistant/components/mysensors/device.py +++ b/homeassistant/components/mysensors/device.py @@ -25,7 +25,7 @@ def get_mysensors_devices(hass, domain): return hass.data[MYSENSORS_PLATFORM_DEVICES.format(domain)] -class MySensorsDevice(object): +class MySensorsDevice: """Representation of a MySensors device.""" def __init__(self, gateway, node_id, child_id, name, value_type): diff --git a/homeassistant/components/neato.py b/homeassistant/components/neato.py index 63b0f61bb7ce..6eabfc713bc1 100644 --- a/homeassistant/components/neato.py +++ b/homeassistant/components/neato.py @@ -118,7 +118,7 @@ def setup(hass, config): return True -class NeatoHub(object): +class NeatoHub: """A My Neato hub wrapper class.""" def __init__(self, hass, domain_config, neato): diff --git a/homeassistant/components/nest/__init__.py b/homeassistant/components/nest/__init__.py index 58fa1953ef0d..23a331b21b2d 100644 --- a/homeassistant/components/nest/__init__.py +++ b/homeassistant/components/nest/__init__.py @@ -183,7 +183,7 @@ async def async_setup_entry(hass, entry): return True -class NestDevice(object): +class NestDevice: """Structure Nest functions for hass.""" def __init__(self, hass, conf, nest): diff --git a/homeassistant/components/netatmo.py b/homeassistant/components/netatmo.py index b1c7a650843a..c25b57fbd627 100644 --- a/homeassistant/components/netatmo.py +++ b/homeassistant/components/netatmo.py @@ -64,7 +64,7 @@ def setup(hass, config): return True -class CameraData(object): +class CameraData: """Get the latest data from Netatmo.""" def __init__(self, auth, home=None): diff --git a/homeassistant/components/notify/__init__.py b/homeassistant/components/notify/__init__.py index 41198d1f2964..13cd6203ed4b 100644 --- a/homeassistant/components/notify/__init__.py +++ b/homeassistant/components/notify/__init__.py @@ -174,7 +174,7 @@ def async_setup(hass, config): return True -class BaseNotificationService(object): +class BaseNotificationService: """An abstract class for notification services.""" hass = None diff --git a/homeassistant/components/notify/apns.py b/homeassistant/components/notify/apns.py index 9cca81e14855..8fabfc3aefb5 100644 --- a/homeassistant/components/notify/apns.py +++ b/homeassistant/components/notify/apns.py @@ -56,7 +56,7 @@ def get_service(hass, config, discovery_info=None): return service -class ApnsDevice(object): +class ApnsDevice: """ The APNS Device class. diff --git a/homeassistant/components/nuimo_controller.py b/homeassistant/components/nuimo_controller.py index e7ab86a5f35f..0f8fbb390731 100644 --- a/homeassistant/components/nuimo_controller.py +++ b/homeassistant/components/nuimo_controller.py @@ -52,7 +52,7 @@ def setup(hass, config): return True -class NuimoLogger(object): +class NuimoLogger: """Handle Nuimo Controller event callbacks.""" def __init__(self, hass, name): @@ -167,7 +167,7 @@ HOMEASSIST_LOGO = ( ".........") -class DiscoveryLogger(object): +class DiscoveryLogger: """Handle Nuimo Discovery callbacks.""" # pylint: disable=no-self-use diff --git a/homeassistant/components/octoprint.py b/homeassistant/components/octoprint.py index c1059227f7a7..bc936265f6ff 100644 --- a/homeassistant/components/octoprint.py +++ b/homeassistant/components/octoprint.py @@ -50,7 +50,7 @@ def setup(hass, config): return True -class OctoPrintAPI(object): +class OctoPrintAPI: """Simple JSON wrapper for OctoPrint's API.""" def __init__(self, api_url, key, bed, number_of_tools): diff --git a/homeassistant/components/pilight.py b/homeassistant/components/pilight.py index 344c750c0ec7..d307a428e0e2 100644 --- a/homeassistant/components/pilight.py +++ b/homeassistant/components/pilight.py @@ -118,7 +118,7 @@ def setup(hass, config): return True -class CallRateDelayThrottle(object): +class CallRateDelayThrottle: """Helper class to provide service call rate throttling. This class provides a decorator to decorate service methods that need diff --git a/homeassistant/components/plant.py b/homeassistant/components/plant.py index 048851e97f54..84dc84027420 100644 --- a/homeassistant/components/plant.py +++ b/homeassistant/components/plant.py @@ -324,7 +324,7 @@ class Plant(Entity): return attrib -class DailyHistory(object): +class DailyHistory: """Stores one measurement per day for a maximum number of days. At the moment only the maximum value per day is kept. diff --git a/homeassistant/components/prometheus.py b/homeassistant/components/prometheus.py index 0a6c959f243d..da986f024a44 100644 --- a/homeassistant/components/prometheus.py +++ b/homeassistant/components/prometheus.py @@ -55,7 +55,7 @@ def setup(hass, config): return True -class PrometheusMetrics(object): +class PrometheusMetrics: """Model all of the metrics which should be exposed to Prometheus.""" def __init__(self, prometheus_client, entity_filter, namespace): diff --git a/homeassistant/components/rachio.py b/homeassistant/components/rachio.py index 3a804c50c74a..90854efe8b1f 100644 --- a/homeassistant/components/rachio.py +++ b/homeassistant/components/rachio.py @@ -130,7 +130,7 @@ def setup(hass, config) -> bool: return True -class RachioPerson(object): +class RachioPerson: """Represent a Rachio user.""" def __init__(self, hass, rachio): @@ -162,7 +162,7 @@ class RachioPerson(object): return self._controllers -class RachioIro(object): +class RachioIro: """Represent a Rachio Iro.""" def __init__(self, hass, rachio, data): diff --git a/homeassistant/components/raincloud.py b/homeassistant/components/raincloud.py index a04f4926b76e..53cd8e79d7e6 100644 --- a/homeassistant/components/raincloud.py +++ b/homeassistant/components/raincloud.py @@ -124,7 +124,7 @@ def setup(hass, config): return True -class RainCloudHub(object): +class RainCloudHub: """Representation of a base RainCloud device.""" def __init__(self, data): diff --git a/homeassistant/components/rainmachine/__init__.py b/homeassistant/components/rainmachine/__init__.py index 22fc427ccce9..b6546f2e67b7 100644 --- a/homeassistant/components/rainmachine/__init__.py +++ b/homeassistant/components/rainmachine/__init__.py @@ -201,7 +201,7 @@ async def async_setup(hass, config): return True -class RainMachine(object): +class RainMachine: """Define a generic RainMachine object.""" def __init__(self, client): diff --git a/homeassistant/components/raspihats.py b/homeassistant/components/raspihats.py index 41480c09a323..f43263bf4bf4 100644 --- a/homeassistant/components/raspihats.py +++ b/homeassistant/components/raspihats.py @@ -63,7 +63,7 @@ class I2CHatsException(Exception): """I2C-HATs exception.""" -class I2CHatsDIScanner(object): +class I2CHatsDIScanner: """Scan Digital Inputs and fire callbacks.""" _DIGITAL_INPUTS = "di" diff --git a/homeassistant/components/remember_the_milk/__init__.py b/homeassistant/components/remember_the_milk/__init__.py index 98cd937de3cc..a94e8e95c6f6 100644 --- a/homeassistant/components/remember_the_milk/__init__.py +++ b/homeassistant/components/remember_the_milk/__init__.py @@ -138,7 +138,7 @@ def _register_new_account(hass, account_name, api_key, shared_secret, ) -class RememberTheMilkConfiguration(object): +class RememberTheMilkConfiguration: """Internal configuration data for RememberTheMilk class. This class stores the authentication token it get from the backend. diff --git a/homeassistant/components/scsgate.py b/homeassistant/components/scsgate.py index a7193b409490..dcea69cbb48d 100644 --- a/homeassistant/components/scsgate.py +++ b/homeassistant/components/scsgate.py @@ -60,7 +60,7 @@ def setup(hass, config): return True -class SCSGate(object): +class SCSGate: """The class for dealing with the SCSGate device via scsgate.Reactor.""" def __init__(self, device, logger): diff --git a/homeassistant/components/sensor/airvisual.py b/homeassistant/components/sensor/airvisual.py index 0002274833ff..403722c7b6ad 100644 --- a/homeassistant/components/sensor/airvisual.py +++ b/homeassistant/components/sensor/airvisual.py @@ -248,7 +248,7 @@ class AirVisualSensor(Entity): }) -class AirVisualData(object): +class AirVisualData: """Define an object to hold sensor data.""" def __init__(self, client, **kwargs): diff --git a/homeassistant/components/sensor/arest.py b/homeassistant/components/sensor/arest.py index 19860ba84fd1..751f0f11171c 100644 --- a/homeassistant/components/sensor/arest.py +++ b/homeassistant/components/sensor/arest.py @@ -158,7 +158,7 @@ class ArestSensor(Entity): return self.arest.available -class ArestData(object): +class ArestData: """The Class for handling the data retrieval for variables.""" def __init__(self, resource, pin=None): diff --git a/homeassistant/components/sensor/bbox.py b/homeassistant/components/sensor/bbox.py index 3689e94b05dc..d24621becc97 100644 --- a/homeassistant/components/sensor/bbox.py +++ b/homeassistant/components/sensor/bbox.py @@ -125,7 +125,7 @@ class BboxSensor(Entity): 2) -class BboxData(object): +class BboxData: """Get data from the Bbox.""" def __init__(self): diff --git a/homeassistant/components/sensor/bitcoin.py b/homeassistant/components/sensor/bitcoin.py index bd23b9850f77..f51b7dcd5bde 100644 --- a/homeassistant/components/sensor/bitcoin.py +++ b/homeassistant/components/sensor/bitcoin.py @@ -169,7 +169,7 @@ class BitcoinSensor(Entity): self._state = '{0:.2f}'.format(stats.market_price_usd) -class BitcoinData(object): +class BitcoinData: """Get the latest data and update the states.""" def __init__(self): diff --git a/homeassistant/components/sensor/bom.py b/homeassistant/components/sensor/bom.py index 5cec528d26a0..eb63e1162541 100644 --- a/homeassistant/components/sensor/bom.py +++ b/homeassistant/components/sensor/bom.py @@ -180,7 +180,7 @@ class BOMCurrentSensor(Entity): self.bom_data.update() -class BOMCurrentData(object): +class BOMCurrentData: """Get data from BOM.""" def __init__(self, hass, station_id): diff --git a/homeassistant/components/sensor/broadlink.py b/homeassistant/components/sensor/broadlink.py index 8806fae5974f..06d7f512c9fe 100644 --- a/homeassistant/components/sensor/broadlink.py +++ b/homeassistant/components/sensor/broadlink.py @@ -96,7 +96,7 @@ class BroadlinkSensor(Entity): self._state = self._broadlink_data.data[self._type] -class BroadlinkData(object): +class BroadlinkData: """Representation of a Broadlink data object.""" def __init__(self, interval, ip_addr, mac_addr, timeout): diff --git a/homeassistant/components/sensor/buienradar.py b/homeassistant/components/sensor/buienradar.py index 10a96ded4373..e366a83a65d6 100644 --- a/homeassistant/components/sensor/buienradar.py +++ b/homeassistant/components/sensor/buienradar.py @@ -374,7 +374,7 @@ class BrSensor(Entity): return self._force_update -class BrData(object): +class BrData: """Get the latest data and updates the states.""" def __init__(self, hass, coordinates, timeframe, devices): diff --git a/homeassistant/components/sensor/coinmarketcap.py b/homeassistant/components/sensor/coinmarketcap.py index f4b666f1e5cc..c4f38b1be02d 100644 --- a/homeassistant/components/sensor/coinmarketcap.py +++ b/homeassistant/components/sensor/coinmarketcap.py @@ -140,7 +140,7 @@ class CoinMarketCapSensor(Entity): self._ticker = self.data.ticker.get('data') -class CoinMarketCapData(object): +class CoinMarketCapData: """Get the latest data and update the states.""" def __init__(self, currency_id, display_currency): diff --git a/homeassistant/components/sensor/command_line.py b/homeassistant/components/sensor/command_line.py index 2003b1b41c94..1db7c58d3281 100644 --- a/homeassistant/components/sensor/command_line.py +++ b/homeassistant/components/sensor/command_line.py @@ -95,7 +95,7 @@ class CommandSensor(Entity): self._state = value -class CommandSensorData(object): +class CommandSensorData: """The class for handling the data retrieval.""" def __init__(self, hass, command, command_timeout): diff --git a/homeassistant/components/sensor/cups.py b/homeassistant/components/sensor/cups.py index 6d55853d7242..846b109afca9 100644 --- a/homeassistant/components/sensor/cups.py +++ b/homeassistant/components/sensor/cups.py @@ -129,7 +129,7 @@ class CupsSensor(Entity): # pylint: disable=no-name-in-module -class CupsData(object): +class CupsData: """Get the latest data from CUPS and update the state.""" def __init__(self, host, port): diff --git a/homeassistant/components/sensor/currencylayer.py b/homeassistant/components/sensor/currencylayer.py index f5d6f278da0b..0834bbc99d85 100644 --- a/homeassistant/components/sensor/currencylayer.py +++ b/homeassistant/components/sensor/currencylayer.py @@ -104,7 +104,7 @@ class CurrencylayerSensor(Entity): value['{}{}'.format(self._base, self._quote)], 4) -class CurrencylayerData(object): +class CurrencylayerData: """Get data from Currencylayer.org.""" def __init__(self, resource, parameters): diff --git a/homeassistant/components/sensor/darksky.py b/homeassistant/components/sensor/darksky.py index e75f36d59f71..2e57e1bd4475 100644 --- a/homeassistant/components/sensor/darksky.py +++ b/homeassistant/components/sensor/darksky.py @@ -372,7 +372,7 @@ def convert_to_camel(data): return components[0] + "".join(x.title() for x in components[1:]) -class DarkSkyData(object): +class DarkSkyData: """Get the latest data from Darksky.""" def __init__(self, api_key, latitude, longitude, units, language, diff --git a/homeassistant/components/sensor/deutsche_bahn.py b/homeassistant/components/sensor/deutsche_bahn.py index ec9b14883a9d..0e6ab164d4f7 100644 --- a/homeassistant/components/sensor/deutsche_bahn.py +++ b/homeassistant/components/sensor/deutsche_bahn.py @@ -85,7 +85,7 @@ class DeutscheBahnSensor(Entity): self._state += " + {}".format(self.data.connections[0]['delay']) -class SchieneData(object): +class SchieneData: """Pull data from the bahn.de web page.""" def __init__(self, start, goal, only_direct): diff --git a/homeassistant/components/sensor/dht.py b/homeassistant/components/sensor/dht.py index c5c26bc5ffa0..6770594b9199 100644 --- a/homeassistant/components/sensor/dht.py +++ b/homeassistant/components/sensor/dht.py @@ -140,7 +140,7 @@ class DHTSensor(Entity): self._state = round(humidity + humidity_offset, 1) -class DHTClient(object): +class DHTClient: """Get the latest data from the DHT sensor.""" def __init__(self, adafruit_dht, sensor, pin): diff --git a/homeassistant/components/sensor/dublin_bus_transport.py b/homeassistant/components/sensor/dublin_bus_transport.py index f6d791f9fd62..a443c78b2b17 100644 --- a/homeassistant/components/sensor/dublin_bus_transport.py +++ b/homeassistant/components/sensor/dublin_bus_transport.py @@ -125,7 +125,7 @@ class DublinPublicTransportSensor(Entity): pass -class PublicTransportData(object): +class PublicTransportData: """The Class for handling the data retrieval.""" def __init__(self, stop, route): diff --git a/homeassistant/components/sensor/dwd_weather_warnings.py b/homeassistant/components/sensor/dwd_weather_warnings.py index e023dfcc49ff..4f9664617a31 100644 --- a/homeassistant/components/sensor/dwd_weather_warnings.py +++ b/homeassistant/components/sensor/dwd_weather_warnings.py @@ -163,7 +163,7 @@ class DwdWeatherWarningsSensor(Entity): self._api.update() -class DwdWeatherWarningsAPI(object): +class DwdWeatherWarningsAPI: """Get the latest data and update the states.""" def __init__(self, region_name): diff --git a/homeassistant/components/sensor/dweet.py b/homeassistant/components/sensor/dweet.py index cca06bd9782d..065c88d8332b 100644 --- a/homeassistant/components/sensor/dweet.py +++ b/homeassistant/components/sensor/dweet.py @@ -99,7 +99,7 @@ class DweetSensor(Entity): values, STATE_UNKNOWN) -class DweetData(object): +class DweetData: """The class for handling the data retrieval.""" def __init__(self, device): diff --git a/homeassistant/components/sensor/ebox.py b/homeassistant/components/sensor/ebox.py index d7b867081a3a..218968ecee8c 100644 --- a/homeassistant/components/sensor/ebox.py +++ b/homeassistant/components/sensor/ebox.py @@ -129,7 +129,7 @@ class EBoxSensor(Entity): self._state = round(self.ebox_data.data[self.type], 2) -class EBoxData(object): +class EBoxData: """Get data from Ebox.""" def __init__(self, username, password, httpsession): diff --git a/homeassistant/components/sensor/eddystone_temperature.py b/homeassistant/components/sensor/eddystone_temperature.py index 978b8db669ac..4c209d17d07d 100644 --- a/homeassistant/components/sensor/eddystone_temperature.py +++ b/homeassistant/components/sensor/eddystone_temperature.py @@ -120,7 +120,7 @@ class EddystoneTemp(Entity): return False -class Monitor(object): +class Monitor: """Continuously scan for BLE advertisements.""" def __init__(self, hass, devices, bt_device_id): diff --git a/homeassistant/components/sensor/emoncms.py b/homeassistant/components/sensor/emoncms.py index cd02137f4d53..a62eaba7df84 100644 --- a/homeassistant/components/sensor/emoncms.py +++ b/homeassistant/components/sensor/emoncms.py @@ -190,7 +190,7 @@ class EmonCmsSensor(Entity): self._state = round(float(elem["value"]), DECIMALS) -class EmonCmsData(object): +class EmonCmsData: """The class for handling the data retrieval.""" def __init__(self, hass, url, apikey, interval): diff --git a/homeassistant/components/sensor/envirophat.py b/homeassistant/components/sensor/envirophat.py index 265350f3e951..bf4ee55c446b 100644 --- a/homeassistant/components/sensor/envirophat.py +++ b/homeassistant/components/sensor/envirophat.py @@ -138,7 +138,7 @@ class EnvirophatSensor(Entity): self._state = self.data.voltage_3 -class EnvirophatData(object): +class EnvirophatData: """Get the latest data and update.""" def __init__(self, envirophat, use_leds): diff --git a/homeassistant/components/sensor/fail2ban.py b/homeassistant/components/sensor/fail2ban.py index 87c301d34f5c..bf868d49201a 100644 --- a/homeassistant/components/sensor/fail2ban.py +++ b/homeassistant/components/sensor/fail2ban.py @@ -112,7 +112,7 @@ class BanSensor(Entity): self.last_ban = 'None' -class BanLogParser(object): +class BanLogParser: """Class to parse fail2ban logs.""" def __init__(self, interval, log_file): diff --git a/homeassistant/components/sensor/fastdotcom.py b/homeassistant/components/sensor/fastdotcom.py index 9143ccaf23fc..65474cd4bf66 100644 --- a/homeassistant/components/sensor/fastdotcom.py +++ b/homeassistant/components/sensor/fastdotcom.py @@ -102,7 +102,7 @@ class SpeedtestSensor(Entity): return ICON -class SpeedtestData(object): +class SpeedtestData: """Get the latest data from fast.com.""" def __init__(self, hass, config): diff --git a/homeassistant/components/sensor/fido.py b/homeassistant/components/sensor/fido.py index a2ee18b3659b..4f724b5b851d 100644 --- a/homeassistant/components/sensor/fido.py +++ b/homeassistant/components/sensor/fido.py @@ -147,7 +147,7 @@ class FidoSensor(Entity): self._state = round(self._state, 2) -class FidoData(object): +class FidoData: """Get data from Fido.""" def __init__(self, username, password, httpsession): diff --git a/homeassistant/components/sensor/filter.py b/homeassistant/components/sensor/filter.py index df913ab6d804..15059b08a179 100644 --- a/homeassistant/components/sensor/filter.py +++ b/homeassistant/components/sensor/filter.py @@ -258,7 +258,7 @@ class SensorFilter(Entity): return state_attr -class FilterState(object): +class FilterState: """State abstraction for filter usage.""" def __init__(self, state): @@ -283,7 +283,7 @@ class FilterState(object): return "{} : {}".format(self.timestamp, self.state) -class Filter(object): +class Filter: """Filter skeleton. Args: diff --git a/homeassistant/components/sensor/fints.py b/homeassistant/components/sensor/fints.py index 131299191393..ef064e842282 100644 --- a/homeassistant/components/sensor/fints.py +++ b/homeassistant/components/sensor/fints.py @@ -101,7 +101,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): add_devices(accounts, True) -class FinTsClient(object): +class FinTsClient: """Wrapper around the FinTS3PinTanClient. Use this class as Context Manager to get the FinTS3Client object. diff --git a/homeassistant/components/sensor/fixer.py b/homeassistant/components/sensor/fixer.py index 438366ae5558..5a6f8da79b22 100644 --- a/homeassistant/components/sensor/fixer.py +++ b/homeassistant/components/sensor/fixer.py @@ -102,7 +102,7 @@ class ExchangeRateSensor(Entity): self._state = round(self.data.rate['rates'][self._target], 3) -class ExchangeData(object): +class ExchangeData: """Get the latest data and update the states.""" def __init__(self, target_currency, api_key): diff --git a/homeassistant/components/sensor/fritzbox_callmonitor.py b/homeassistant/components/sensor/fritzbox_callmonitor.py index 304489f99b76..e42be861d378 100644 --- a/homeassistant/components/sensor/fritzbox_callmonitor.py +++ b/homeassistant/components/sensor/fritzbox_callmonitor.py @@ -143,7 +143,7 @@ class FritzBoxCallSensor(Entity): self.phonebook.update_phonebook() -class FritzBoxCallMonitor(object): +class FritzBoxCallMonitor: """Event listener to monitor calls on the Fritz!Box.""" def __init__(self, host, port, sensor): @@ -225,7 +225,7 @@ class FritzBoxCallMonitor(object): self._sensor.schedule_update_ha_state() -class FritzBoxPhonebook(object): +class FritzBoxPhonebook: """This connects to a FritzBox router and downloads its phone book.""" def __init__(self, host, port, username, password, diff --git a/homeassistant/components/sensor/geizhals.py b/homeassistant/components/sensor/geizhals.py index 94f3f1884d18..9e32bf27c295 100644 --- a/homeassistant/components/sensor/geizhals.py +++ b/homeassistant/components/sensor/geizhals.py @@ -98,7 +98,7 @@ class Geizwatch(Entity): self._state = self.data.prices[0] -class GeizParser(object): +class GeizParser: """Pull data from the geizhals website.""" def __init__(self, product_id, domain, regex): diff --git a/homeassistant/components/sensor/geo_rss_events.py b/homeassistant/components/sensor/geo_rss_events.py index c8c4db17c8d6..b79e6e69adf4 100644 --- a/homeassistant/components/sensor/geo_rss_events.py +++ b/homeassistant/components/sensor/geo_rss_events.py @@ -149,7 +149,7 @@ class GeoRssServiceSensor(Entity): self._state_attributes = matrix -class GeoRssServiceData(object): +class GeoRssServiceData: """Provide access to GeoRSS feed and stores the latest data.""" def __init__(self, home_latitude, home_longitude, url, radius_in_km): diff --git a/homeassistant/components/sensor/glances.py b/homeassistant/components/sensor/glances.py index bd6e91c7b531..a6dfd89e45a1 100644 --- a/homeassistant/components/sensor/glances.py +++ b/homeassistant/components/sensor/glances.py @@ -176,7 +176,7 @@ class GlancesSensor(Entity): self._state = round(use / 1024**2, 1) -class GlancesData(object): +class GlancesData: """The class for handling the data retrieval.""" def __init__(self, resource): diff --git a/homeassistant/components/sensor/google_wifi.py b/homeassistant/components/sensor/google_wifi.py index 388af9b3ce76..cc5461ed5481 100644 --- a/homeassistant/components/sensor/google_wifi.py +++ b/homeassistant/components/sensor/google_wifi.py @@ -138,7 +138,7 @@ class GoogleWifiSensor(Entity): self._state = STATE_UNKNOWN -class GoogleWifiAPI(object): +class GoogleWifiAPI: """Get the latest data and update the states.""" def __init__(self, host, conditions): diff --git a/homeassistant/components/sensor/haveibeenpwned.py b/homeassistant/components/sensor/haveibeenpwned.py index c1fe7ab4880f..bc79c4d0c1da 100644 --- a/homeassistant/components/sensor/haveibeenpwned.py +++ b/homeassistant/components/sensor/haveibeenpwned.py @@ -122,7 +122,7 @@ class HaveIBeenPwnedSensor(Entity): self._state = len(self._data.data[self._email]) -class HaveIBeenPwnedData(object): +class HaveIBeenPwnedData: """Class for handling the data retrieval.""" def __init__(self, emails): diff --git a/homeassistant/components/sensor/hddtemp.py b/homeassistant/components/sensor/hddtemp.py index 006542a777f7..f8afe9c76370 100644 --- a/homeassistant/components/sensor/hddtemp.py +++ b/homeassistant/components/sensor/hddtemp.py @@ -108,7 +108,7 @@ class HddTempSensor(Entity): self._state = None -class HddTempData(object): +class HddTempData: """Get the latest data from HDDTemp and update the states.""" def __init__(self, host, port): diff --git a/homeassistant/components/sensor/hp_ilo.py b/homeassistant/components/sensor/hp_ilo.py index acd10fe08afb..98ee83f89583 100644 --- a/homeassistant/components/sensor/hp_ilo.py +++ b/homeassistant/components/sensor/hp_ilo.py @@ -147,7 +147,7 @@ class HpIloSensor(Entity): self._state = ilo_data -class HpIloData(object): +class HpIloData: """Gets the latest data from HP ILO.""" def __init__(self, host, port, login, password): diff --git a/homeassistant/components/sensor/hydroquebec.py b/homeassistant/components/sensor/hydroquebec.py index 2195153ab1ed..db75d51fbad5 100644 --- a/homeassistant/components/sensor/hydroquebec.py +++ b/homeassistant/components/sensor/hydroquebec.py @@ -162,7 +162,7 @@ class HydroQuebecSensor(Entity): self._state = round(self.hydroquebec_data.data[self.type], 2) -class HydroquebecData(object): +class HydroquebecData: """Get data from HydroQuebec.""" def __init__(self, username, password, httpsession, contract=None): diff --git a/homeassistant/components/sensor/imap_email_content.py b/homeassistant/components/sensor/imap_email_content.py index c0c9bf62efdf..a1a604df3e49 100644 --- a/homeassistant/components/sensor/imap_email_content.py +++ b/homeassistant/components/sensor/imap_email_content.py @@ -58,7 +58,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): return False -class EmailReader(object): +class EmailReader: """A class to read emails from an IMAP server.""" def __init__(self, user, password, server, port): diff --git a/homeassistant/components/sensor/influxdb.py b/homeassistant/components/sensor/influxdb.py index c0d492984e0a..8bfbaf49837f 100644 --- a/homeassistant/components/sensor/influxdb.py +++ b/homeassistant/components/sensor/influxdb.py @@ -155,7 +155,7 @@ class InfluxSensor(Entity): self._state = value -class InfluxSensorData(object): +class InfluxSensorData: """Class for handling the data retrieval.""" def __init__(self, influx, group, field, measurement, where): diff --git a/homeassistant/components/sensor/irish_rail_transport.py b/homeassistant/components/sensor/irish_rail_transport.py index 603d82359de6..5febebeec870 100644 --- a/homeassistant/components/sensor/irish_rail_transport.py +++ b/homeassistant/components/sensor/irish_rail_transport.py @@ -132,7 +132,7 @@ class IrishRailTransportSensor(Entity): self._state = None -class IrishRailTransportData(object): +class IrishRailTransportData: """The Class for handling the data retrieval.""" def __init__(self, irish_rail, station, direction, destination, stops_at): diff --git a/homeassistant/components/sensor/london_air.py b/homeassistant/components/sensor/london_air.py index 2ffbb9142756..bbb5993b0644 100644 --- a/homeassistant/components/sensor/london_air.py +++ b/homeassistant/components/sensor/london_air.py @@ -71,7 +71,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): add_devices(sensors, True) -class APIData(object): +class APIData: """Get the latest data for all authorities.""" def __init__(self): diff --git a/homeassistant/components/sensor/london_underground.py b/homeassistant/components/sensor/london_underground.py index fe13c0db8a7e..4619eda06112 100644 --- a/homeassistant/components/sensor/london_underground.py +++ b/homeassistant/components/sensor/london_underground.py @@ -95,7 +95,7 @@ class LondonTubeSensor(Entity): self._description = self._data.data[self.name]['Description'] -class TubeData(object): +class TubeData: """Get the latest tube data from TFL.""" def __init__(self): diff --git a/homeassistant/components/sensor/luftdaten.py b/homeassistant/components/sensor/luftdaten.py index 9952e2a1c243..c9bc7205ce63 100644 --- a/homeassistant/components/sensor/luftdaten.py +++ b/homeassistant/components/sensor/luftdaten.py @@ -137,7 +137,7 @@ class LuftdatenSensor(Entity): await self.luftdaten.async_update() -class LuftdatenData(object): +class LuftdatenData: """Class for handling the data retrieval.""" def __init__(self, data): diff --git a/homeassistant/components/sensor/lyft.py b/homeassistant/components/sensor/lyft.py index c2f6412049cd..57e5f1c6b02c 100644 --- a/homeassistant/components/sensor/lyft.py +++ b/homeassistant/components/sensor/lyft.py @@ -183,7 +183,7 @@ class LyftSensor(Entity): estimate.get('estimated_cost_cents_max', 0)) / 2) / 100) -class LyftEstimate(object): +class LyftEstimate: """The class for handling the time and price estimate.""" def __init__(self, session, start_latitude, start_longitude, diff --git a/homeassistant/components/sensor/metoffice.py b/homeassistant/components/sensor/metoffice.py index b6366de64320..ec3d3f47ba7f 100644 --- a/homeassistant/components/sensor/metoffice.py +++ b/homeassistant/components/sensor/metoffice.py @@ -174,7 +174,7 @@ class MetOfficeCurrentSensor(Entity): self.data.update() -class MetOfficeCurrentData(object): +class MetOfficeCurrentData: """Get data from Datapoint.""" def __init__(self, hass, datapoint, site): diff --git a/homeassistant/components/sensor/mhz19.py b/homeassistant/components/sensor/mhz19.py index cd559d3bbd26..60f6598ab210 100644 --- a/homeassistant/components/sensor/mhz19.py +++ b/homeassistant/components/sensor/mhz19.py @@ -117,7 +117,7 @@ class MHZ19Sensor(Entity): return result -class MHZClient(object): +class MHZClient: """Get the latest data from the MH-Z sensor.""" def __init__(self, co2sensor, serial): diff --git a/homeassistant/components/sensor/mopar.py b/homeassistant/components/sensor/mopar.py index 3e1887cfd598..81c48555cfca 100644 --- a/homeassistant/components/sensor/mopar.py +++ b/homeassistant/components/sensor/mopar.py @@ -70,7 +70,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): for index, _ in enumerate(data.vehicles)], True) -class MoparData(object): +class MoparData: """Container for Mopar vehicle data. Prevents session expiry re-login race condition. diff --git a/homeassistant/components/sensor/mvglive.py b/homeassistant/components/sensor/mvglive.py index 81c7173e4d03..95f2845eced6 100644 --- a/homeassistant/components/sensor/mvglive.py +++ b/homeassistant/components/sensor/mvglive.py @@ -123,7 +123,7 @@ class MVGLiveSensor(Entity): self._icon = ICONS[self.data.departures.get('product', '-')] -class MVGLiveData(object): +class MVGLiveData: """Pull data from the mvg-live.de web page.""" def __init__(self, station, destinations, directions, diff --git a/homeassistant/components/sensor/netatmo.py b/homeassistant/components/sensor/netatmo.py index 54b095bb84bb..bc0cbc5eea67 100644 --- a/homeassistant/components/sensor/netatmo.py +++ b/homeassistant/components/sensor/netatmo.py @@ -287,7 +287,7 @@ class NetAtmoSensor(Entity): self._state = "Full" -class NetAtmoData(object): +class NetAtmoData: """Get the latest data from NetAtmo.""" def __init__(self, auth, station): diff --git a/homeassistant/components/sensor/netdata.py b/homeassistant/components/sensor/netdata.py index 2d08159967c4..488b16113999 100644 --- a/homeassistant/components/sensor/netdata.py +++ b/homeassistant/components/sensor/netdata.py @@ -134,7 +134,7 @@ class NetdataSensor(Entity): resource_data['dimensions'][self._element]['value'], 2) -class NetdataData(object): +class NetdataData: """The class for handling the data retrieval.""" def __init__(self, api): diff --git a/homeassistant/components/sensor/neurio_energy.py b/homeassistant/components/sensor/neurio_energy.py index 5e3bf55dc9d1..fd8b8d2aeece 100644 --- a/homeassistant/components/sensor/neurio_energy.py +++ b/homeassistant/components/sensor/neurio_energy.py @@ -69,7 +69,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): add_devices([NeurioEnergy(data, DAILY_NAME, DAILY_TYPE, update_daily)]) -class NeurioData(object): +class NeurioData: """Stores data retrieved from Neurio sensor.""" def __init__(self, api_key, api_secret, sensor_id): diff --git a/homeassistant/components/sensor/nsw_fuel_station.py b/homeassistant/components/sensor/nsw_fuel_station.py index 2440dac3204e..5f677d39888e 100644 --- a/homeassistant/components/sensor/nsw_fuel_station.py +++ b/homeassistant/components/sensor/nsw_fuel_station.py @@ -73,7 +73,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): ]) -class StationPriceData(object): +class StationPriceData: """An object to store and fetch the latest data for a given station.""" def __init__(self, client, station_id: int) -> None: diff --git a/homeassistant/components/sensor/nut.py b/homeassistant/components/sensor/nut.py index 7c7ff3480b00..ff0cd8ef891c 100644 --- a/homeassistant/components/sensor/nut.py +++ b/homeassistant/components/sensor/nut.py @@ -260,7 +260,7 @@ class NUTSensor(Entity): self._state = self._data.status[self.type] -class PyNUTData(object): +class PyNUTData: """Stores the data retrieved from NUT. For each entity to use, acts as the single point responsible for fetching diff --git a/homeassistant/components/sensor/nzbget.py b/homeassistant/components/sensor/nzbget.py index 0fa6362ad051..a8dda416a547 100644 --- a/homeassistant/components/sensor/nzbget.py +++ b/homeassistant/components/sensor/nzbget.py @@ -138,7 +138,7 @@ class NZBGetSensor(Entity): self._state = value -class NZBGetAPI(object): +class NZBGetAPI: """Simple JSON-RPC wrapper for NZBGet's API.""" def __init__(self, api_url, username=None, password=None): diff --git a/homeassistant/components/sensor/openexchangerates.py b/homeassistant/components/sensor/openexchangerates.py index 741ffa2842d2..5e8231bb1249 100644 --- a/homeassistant/components/sensor/openexchangerates.py +++ b/homeassistant/components/sensor/openexchangerates.py @@ -93,7 +93,7 @@ class OpenexchangeratesSensor(Entity): self._state = round(value[str(self._quote)], 4) -class OpenexchangeratesData(object): +class OpenexchangeratesData: """Get data from Openexchangerates.org.""" def __init__(self, resource, parameters, quote): diff --git a/homeassistant/components/sensor/openhardwaremonitor.py b/homeassistant/components/sensor/openhardwaremonitor.py index 1b5867836bcf..dc221f6b07d6 100644 --- a/homeassistant/components/sensor/openhardwaremonitor.py +++ b/homeassistant/components/sensor/openhardwaremonitor.py @@ -108,7 +108,7 @@ class OpenHardwareMonitorDevice(Entity): }) -class OpenHardwareMonitorData(object): +class OpenHardwareMonitorData: """Class used to pull data from OHM and create sensors.""" def __init__(self, config, hass): diff --git a/homeassistant/components/sensor/openweathermap.py b/homeassistant/components/sensor/openweathermap.py index 96db4430d326..6241f3e53781 100644 --- a/homeassistant/components/sensor/openweathermap.py +++ b/homeassistant/components/sensor/openweathermap.py @@ -179,7 +179,7 @@ class OpenWeatherMapSensor(Entity): self._state = fc_data.get_weathers()[0].get_detailed_status() -class WeatherData(object): +class WeatherData: """Get the latest data from OpenWeatherMap.""" def __init__(self, owm, forecast, latitude, longitude): diff --git a/homeassistant/components/sensor/pi_hole.py b/homeassistant/components/sensor/pi_hole.py index 2adf5691e2e1..363ada725ba8 100644 --- a/homeassistant/components/sensor/pi_hole.py +++ b/homeassistant/components/sensor/pi_hole.py @@ -153,7 +153,7 @@ class PiHoleSensor(Entity): self.data = self.pi_hole.api.data -class PiHoleData(object): +class PiHoleData: """Get the latest data and update the states.""" def __init__(self, api): diff --git a/homeassistant/components/sensor/pollen.py b/homeassistant/components/sensor/pollen.py index c11c83ab40e1..ea68a902c484 100644 --- a/homeassistant/components/sensor/pollen.py +++ b/homeassistant/components/sensor/pollen.py @@ -263,7 +263,7 @@ class PollencomSensor(Entity): self._state = average -class PollenComData(object): +class PollenComData: """Define a data object to retrieve info from Pollen.com.""" def __init__(self, client, sensor_types): diff --git a/homeassistant/components/sensor/pyload.py b/homeassistant/components/sensor/pyload.py index cc4ce1e64485..a5593c259a55 100644 --- a/homeassistant/components/sensor/pyload.py +++ b/homeassistant/components/sensor/pyload.py @@ -124,7 +124,7 @@ class PyLoadSensor(Entity): self._state = value -class PyLoadAPI(object): +class PyLoadAPI: """Simple wrapper for pyLoad's API.""" def __init__(self, api_url, username=None, password=None): diff --git a/homeassistant/components/sensor/qnap.py b/homeassistant/components/sensor/qnap.py index 97cf948ff9fc..a44823d86ba7 100644 --- a/homeassistant/components/sensor/qnap.py +++ b/homeassistant/components/sensor/qnap.py @@ -164,7 +164,7 @@ def round_nicely(number): return round(number) -class QNAPStatsAPI(object): +class QNAPStatsAPI: """Class to interface with the API.""" def __init__(self, config): diff --git a/homeassistant/components/sensor/rest.py b/homeassistant/components/sensor/rest.py index 75235bedaab6..8db48719a37c 100644 --- a/homeassistant/components/sensor/rest.py +++ b/homeassistant/components/sensor/rest.py @@ -158,7 +158,7 @@ class RestSensor(Entity): return self._attributes -class RestData(object): +class RestData: """Class for handling the data retrieval.""" def __init__(self, method, resource, auth, headers, data, verify_ssl): diff --git a/homeassistant/components/sensor/sense.py b/homeassistant/components/sensor/sense.py index 5eee9053db52..16f4ccb9b6c1 100644 --- a/homeassistant/components/sensor/sense.py +++ b/homeassistant/components/sensor/sense.py @@ -27,7 +27,7 @@ CONSUMPTION_NAME = "Usage" ACTIVE_TYPE = 'active' -class SensorConfig(object): +class SensorConfig: """Data structure holding sensor config.""" def __init__(self, name, sensor_type): diff --git a/homeassistant/components/sensor/sensehat.py b/homeassistant/components/sensor/sensehat.py index a50f4cdfd2c6..f0e566f718f6 100644 --- a/homeassistant/components/sensor/sensehat.py +++ b/homeassistant/components/sensor/sensehat.py @@ -109,7 +109,7 @@ class SenseHatSensor(Entity): self._state = self.data.pressure -class SenseHatData(object): +class SenseHatData: """Get the latest data and update.""" def __init__(self, is_hat_attached): diff --git a/homeassistant/components/sensor/shodan.py b/homeassistant/components/sensor/shodan.py index bc3e127508bc..541abea3091b 100644 --- a/homeassistant/components/sensor/shodan.py +++ b/homeassistant/components/sensor/shodan.py @@ -96,7 +96,7 @@ class ShodanSensor(Entity): self._state = self.data.details['total'] -class ShodanData(object): +class ShodanData: """Get the latest data and update the states.""" def __init__(self, api, query): diff --git a/homeassistant/components/sensor/sht31.py b/homeassistant/components/sensor/sht31.py index e1a7f3c9e5f6..2aeff8e73d82 100644 --- a/homeassistant/components/sensor/sht31.py +++ b/homeassistant/components/sensor/sht31.py @@ -75,7 +75,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): add_devices(devs) -class SHTClient(object): +class SHTClient: """Get the latest data from the SHT sensor.""" def __init__(self, adafruit_sht): diff --git a/homeassistant/components/sensor/sigfox.py b/homeassistant/components/sensor/sigfox.py index da8f3fcc6394..408435a96670 100644 --- a/homeassistant/components/sensor/sigfox.py +++ b/homeassistant/components/sensor/sigfox.py @@ -55,7 +55,7 @@ def epoch_to_datetime(epoch_time): return datetime.datetime.fromtimestamp(epoch_time).isoformat() -class SigfoxAPI(object): +class SigfoxAPI: """Class for interacting with the SigFox API.""" def __init__(self, api_login, api_password): diff --git a/homeassistant/components/sensor/snmp.py b/homeassistant/components/sensor/snmp.py index 95bf207acf8e..0ba74f0e7ed0 100644 --- a/homeassistant/components/sensor/snmp.py +++ b/homeassistant/components/sensor/snmp.py @@ -131,7 +131,7 @@ class SnmpSensor(Entity): self._state = value -class SnmpData(object): +class SnmpData: """Get the latest data and update the states.""" def __init__(self, host, port, community, baseoid, version, accept_errors, diff --git a/homeassistant/components/sensor/speedtest.py b/homeassistant/components/sensor/speedtest.py index bf2868d3b019..8c1ffc037862 100644 --- a/homeassistant/components/sensor/speedtest.py +++ b/homeassistant/components/sensor/speedtest.py @@ -148,7 +148,7 @@ class SpeedtestSensor(Entity): self._state = state.state -class SpeedtestData(object): +class SpeedtestData: """Get the latest data from speedtest.net.""" def __init__(self, hass, config): diff --git a/homeassistant/components/sensor/startca.py b/homeassistant/components/sensor/startca.py index aefbc2d46268..374e14c5ac2e 100644 --- a/homeassistant/components/sensor/startca.py +++ b/homeassistant/components/sensor/startca.py @@ -118,7 +118,7 @@ class StartcaSensor(Entity): self._state = round(self.startcadata.data[self.type], 2) -class StartcaData(object): +class StartcaData: """Get data from Start.ca API.""" def __init__(self, loop, websession, api_key, bandwidth_cap): diff --git a/homeassistant/components/sensor/swiss_hydrological_data.py b/homeassistant/components/sensor/swiss_hydrological_data.py index 63d500e2373b..b4536b48c9ec 100644 --- a/homeassistant/components/sensor/swiss_hydrological_data.py +++ b/homeassistant/components/sensor/swiss_hydrological_data.py @@ -145,7 +145,7 @@ class SwissHydrologicalDataSensor(Entity): self._state = self.data.measurings['03']['current'] -class HydrologicalData(object): +class HydrologicalData: """The Class for handling the data retrieval.""" def __init__(self, station): diff --git a/homeassistant/components/sensor/synologydsm.py b/homeassistant/components/sensor/synologydsm.py index e70dd482f1e4..124cec97617a 100644 --- a/homeassistant/components/sensor/synologydsm.py +++ b/homeassistant/components/sensor/synologydsm.py @@ -129,7 +129,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): hass.bus.listen_once(EVENT_HOMEASSISTANT_START, run_setup) -class SynoApi(object): +class SynoApi: """Class to interface with Synology DSM API.""" def __init__(self, host, port, username, password, temp_unit, use_ssl): diff --git a/homeassistant/components/sensor/sytadin.py b/homeassistant/components/sensor/sytadin.py index 9a85eb255751..6b2284f4376b 100644 --- a/homeassistant/components/sensor/sytadin.py +++ b/homeassistant/components/sensor/sytadin.py @@ -113,7 +113,7 @@ class SytadinSensor(Entity): self._state = self.data.congestion -class SytadinData(object): +class SytadinData: """The class for handling the data retrieval.""" def __init__(self, resource): diff --git a/homeassistant/components/sensor/ted5000.py b/homeassistant/components/sensor/ted5000.py index c2ef1d4c6b90..7298181796a7 100644 --- a/homeassistant/components/sensor/ted5000.py +++ b/homeassistant/components/sensor/ted5000.py @@ -88,7 +88,7 @@ class Ted5000Sensor(Entity): self._gateway.update() -class Ted5000Gateway(object): +class Ted5000Gateway: """The class for handling the data retrieval.""" def __init__(self, url): diff --git a/homeassistant/components/sensor/teksavvy.py b/homeassistant/components/sensor/teksavvy.py index 0bf1ef4caff6..68a1cfc4fe17 100644 --- a/homeassistant/components/sensor/teksavvy.py +++ b/homeassistant/components/sensor/teksavvy.py @@ -119,7 +119,7 @@ class TekSavvySensor(Entity): self._state = round(self.teksavvydata.data[self.type], 2) -class TekSavvyData(object): +class TekSavvyData: """Get data from TekSavvy API.""" def __init__(self, loop, websession, api_key, bandwidth_cap): diff --git a/homeassistant/components/sensor/thethingsnetwork.py b/homeassistant/components/sensor/thethingsnetwork.py index 28a3b48892b6..0f27b6564044 100644 --- a/homeassistant/components/sensor/thethingsnetwork.py +++ b/homeassistant/components/sensor/thethingsnetwork.py @@ -110,7 +110,7 @@ class TtnDataSensor(Entity): self._state = self._ttn_data_storage.data -class TtnDataStorage(object): +class TtnDataStorage: """Get the latest data from The Things Network Data Storage.""" def __init__(self, hass, app_id, device_id, access_key, values): diff --git a/homeassistant/components/sensor/transmission.py b/homeassistant/components/sensor/transmission.py index 4dac411d2246..3e74b4549137 100644 --- a/homeassistant/components/sensor/transmission.py +++ b/homeassistant/components/sensor/transmission.py @@ -148,7 +148,7 @@ class TransmissionSensor(Entity): self._state = self._data.torrentCount -class TransmissionData(object): +class TransmissionData: """Get the latest data and update the states.""" def __init__(self, api): diff --git a/homeassistant/components/sensor/uber.py b/homeassistant/components/sensor/uber.py index e80fe7d2d825..cd476a1a2265 100644 --- a/homeassistant/components/sensor/uber.py +++ b/homeassistant/components/sensor/uber.py @@ -175,7 +175,7 @@ class UberSensor(Entity): self._state = 0 -class UberEstimate(object): +class UberEstimate: """The class for handling the time and price estimate.""" def __init__(self, session, start_latitude, start_longitude, diff --git a/homeassistant/components/sensor/waterfurnace.py b/homeassistant/components/sensor/waterfurnace.py index 24c45ec1ff3e..76c5d2f648ed 100644 --- a/homeassistant/components/sensor/waterfurnace.py +++ b/homeassistant/components/sensor/waterfurnace.py @@ -16,7 +16,7 @@ from homeassistant.helpers.entity import Entity from homeassistant.util import slugify -class WFSensorConfig(object): +class WFSensorConfig: """Water Furnace Sensor configuration.""" def __init__(self, friendly_name, field, icon="mdi:gauge", diff --git a/homeassistant/components/sensor/wunderground.py b/homeassistant/components/sensor/wunderground.py index 7f2df4bcda92..24ae2d0068ff 100644 --- a/homeassistant/components/sensor/wunderground.py +++ b/homeassistant/components/sensor/wunderground.py @@ -40,7 +40,7 @@ MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=5) # Helper classes for declaring sensor configurations -class WUSensorConfig(object): +class WUSensorConfig: """WU Sensor Configuration. defines basic HA properties of the weather sensor and @@ -764,7 +764,7 @@ class WUndergroundSensor(Entity): return self._unique_id -class WUndergroundData(object): +class WUndergroundData: """Get data from WUnderground.""" def __init__(self, hass, api_key, pws_id, lang, latitude, longitude): diff --git a/homeassistant/components/sensor/yahoo_finance.py b/homeassistant/components/sensor/yahoo_finance.py index 8c2cfd9923fd..82cb7f845dc2 100644 --- a/homeassistant/components/sensor/yahoo_finance.py +++ b/homeassistant/components/sensor/yahoo_finance.py @@ -104,7 +104,7 @@ class YahooFinanceSensor(Entity): self._state = self.data.state -class YahooFinanceData(object): +class YahooFinanceData: """Get data from Yahoo Finance.""" def __init__(self, symbol): diff --git a/homeassistant/components/sensor/yr.py b/homeassistant/components/sensor/yr.py index c7ff967723b6..fcddf41af970 100644 --- a/homeassistant/components/sensor/yr.py +++ b/homeassistant/components/sensor/yr.py @@ -142,7 +142,7 @@ class YrSensor(Entity): return self._unit_of_measurement -class YrData(object): +class YrData: """Get the latest data and updates the states.""" def __init__(self, hass, coordinates, forecast, devices): diff --git a/homeassistant/components/sensor/yweather.py b/homeassistant/components/sensor/yweather.py index db66419e54ab..b2279e107da1 100644 --- a/homeassistant/components/sensor/yweather.py +++ b/homeassistant/components/sensor/yweather.py @@ -174,7 +174,7 @@ class YahooWeatherSensor(Entity): float(self._data.yahoo.Atmosphere['visibility'])/1.61, 2) -class YahooWeatherData(object): +class YahooWeatherData: """Handle Yahoo! API object and limit updates.""" def __init__(self, woeid, temp_unit): diff --git a/homeassistant/components/sensor/zamg.py b/homeassistant/components/sensor/zamg.py index df5ff5e8d372..e8e5fdec4d8e 100644 --- a/homeassistant/components/sensor/zamg.py +++ b/homeassistant/components/sensor/zamg.py @@ -133,7 +133,7 @@ class ZamgSensor(Entity): self.probe.update() -class ZamgData(object): +class ZamgData: """The class for handling the data retrieval.""" API_URL = 'http://www.zamg.ac.at/ogd/' diff --git a/homeassistant/components/sensor/zwave.py b/homeassistant/components/sensor/zwave.py index b2a913c2af8b..d2166dde64e2 100644 --- a/homeassistant/components/sensor/zwave.py +++ b/homeassistant/components/sensor/zwave.py @@ -8,7 +8,7 @@ import logging from homeassistant.components.sensor import DOMAIN from homeassistant.components import zwave from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT -from homeassistant.components.zwave import async_setup_platform # noqa # pylint: disable=unused-import +from homeassistant.components.zwave import async_setup_platform # noqa pylint: disable=unused-import _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/sleepiq.py b/homeassistant/components/sleepiq.py index df36eef2f9ef..4d4ecf0160b5 100644 --- a/homeassistant/components/sleepiq.py +++ b/homeassistant/components/sleepiq.py @@ -73,7 +73,7 @@ def setup(hass, config): return True -class SleepIQData(object): +class SleepIQData: """Get the latest data from SleepIQ.""" def __init__(self, client): diff --git a/homeassistant/components/smappee.py b/homeassistant/components/smappee.py index b35cd8cf5a8c..2ee0080a06d6 100644 --- a/homeassistant/components/smappee.py +++ b/homeassistant/components/smappee.py @@ -71,7 +71,7 @@ def setup(hass, config): return True -class Smappee(object): +class Smappee: """Stores data retrieved from Smappee sensor.""" def __init__(self, client_id, client_secret, username, diff --git a/homeassistant/components/switch/anel_pwrctrl.py b/homeassistant/components/switch/anel_pwrctrl.py index 4e62b7119795..01d27b8abcd3 100644 --- a/homeassistant/components/switch/anel_pwrctrl.py +++ b/homeassistant/components/switch/anel_pwrctrl.py @@ -107,7 +107,7 @@ class PwrCtrlSwitch(SwitchDevice): self._port.off() -class PwrCtrlDevice(object): +class PwrCtrlDevice: """Device representation for per device throttling.""" def __init__(self, device): diff --git a/homeassistant/components/switch/broadlink.py b/homeassistant/components/switch/broadlink.py index 460021121775..6b754effaf1d 100644 --- a/homeassistant/components/switch/broadlink.py +++ b/homeassistant/components/switch/broadlink.py @@ -348,7 +348,7 @@ class BroadlinkMP1Slot(BroadlinkRMSwitch): self._state = self._parent_device.get_outlet_status(self._slot) -class BroadlinkMP1Switch(object): +class BroadlinkMP1Switch: """Representation of a Broadlink switch - To fetch states of all slots.""" def __init__(self, device): diff --git a/homeassistant/components/switch/digitalloggers.py b/homeassistant/components/switch/digitalloggers.py index f3af70c6222f..29e6771d1d58 100644 --- a/homeassistant/components/switch/digitalloggers.py +++ b/homeassistant/components/switch/digitalloggers.py @@ -122,7 +122,7 @@ class DINRelay(SwitchDevice): self._state = outlet_status[2] == 'ON' -class DINRelayDevice(object): +class DINRelayDevice: """Device representation for per device throttling.""" def __init__(self, power_switch): diff --git a/homeassistant/components/switch/dlink.py b/homeassistant/components/switch/dlink.py index 1c7253c4ec37..9ce324ef6bb8 100644 --- a/homeassistant/components/switch/dlink.py +++ b/homeassistant/components/switch/dlink.py @@ -125,7 +125,7 @@ class SmartPlugSwitch(SwitchDevice): self.data.update() -class SmartPlugData(object): +class SmartPlugData: """Get the latest data from smart plug.""" def __init__(self, smartplug): diff --git a/homeassistant/components/switch/fritzdect.py b/homeassistant/components/switch/fritzdect.py index 9968f631260d..9c0f852846a7 100644 --- a/homeassistant/components/switch/fritzdect.py +++ b/homeassistant/components/switch/fritzdect.py @@ -163,7 +163,7 @@ class FritzDectSwitch(SwitchDevice): self.data.is_online = False -class FritzDectSwitchData(object): +class FritzDectSwitchData: """Get the latest data from the fritz box.""" def __init__(self, fritz, ain): diff --git a/homeassistant/components/switch/pilight.py b/homeassistant/components/switch/pilight.py index 4e5f1c47e9ff..7ffce13ff6af 100644 --- a/homeassistant/components/switch/pilight.py +++ b/homeassistant/components/switch/pilight.py @@ -80,7 +80,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): add_devices(devices) -class _ReceiveHandle(object): +class _ReceiveHandle: def __init__(self, config, echo): """Initialize the handle.""" self.config_items = config.items() diff --git a/homeassistant/components/switch/scsgate.py b/homeassistant/components/switch/scsgate.py index adc08a65e711..b549f351afc8 100644 --- a/homeassistant/components/switch/scsgate.py +++ b/homeassistant/components/switch/scsgate.py @@ -152,7 +152,7 @@ class SCSGateSwitch(SwitchDevice): ) -class SCSGateScenarioSwitch(object): +class SCSGateScenarioSwitch: """Provides a SCSGate scenario switch. This switch is always in an 'off" state, when toggled it's used to trigger diff --git a/homeassistant/components/switch/zwave.py b/homeassistant/components/switch/zwave.py index 8a0a1683aa41..31f942bd3af7 100644 --- a/homeassistant/components/switch/zwave.py +++ b/homeassistant/components/switch/zwave.py @@ -8,7 +8,7 @@ import logging import time from homeassistant.components.switch import DOMAIN, SwitchDevice from homeassistant.components import zwave -from homeassistant.components.zwave import workaround, async_setup_platform # noqa # pylint: disable=unused-import +from homeassistant.components.zwave import workaround, async_setup_platform # noqa pylint: disable=unused-import _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/tellduslive.py b/homeassistant/components/tellduslive.py index dfb4b1e5fa9c..bb235647d1b3 100644 --- a/homeassistant/components/tellduslive.py +++ b/homeassistant/components/tellduslive.py @@ -206,7 +206,7 @@ def setup(hass, config, session=None): return True -class TelldusLiveClient(object): +class TelldusLiveClient: """Get the latest data and update the states.""" def __init__(self, hass, config, session): diff --git a/homeassistant/components/toon.py b/homeassistant/components/toon.py index ffb820e81481..cfd0d297d546 100644 --- a/homeassistant/components/toon.py +++ b/homeassistant/components/toon.py @@ -59,7 +59,7 @@ def setup(hass, config): return True -class ToonDataStore(object): +class ToonDataStore: """An object to store the Toon data.""" def __init__(self, username, password, gas=DEFAULT_GAS, diff --git a/homeassistant/components/tts/__init__.py b/homeassistant/components/tts/__init__.py index 999b584360cd..f8a521c3e2f9 100644 --- a/homeassistant/components/tts/__init__.py +++ b/homeassistant/components/tts/__init__.py @@ -169,7 +169,7 @@ async def async_setup(hass, config): return True -class SpeechManager(object): +class SpeechManager: """Representation of a speech store.""" def __init__(self, hass): @@ -440,7 +440,7 @@ class SpeechManager(object): return data_bytes.getvalue() -class Provider(object): +class Provider: """Represent a single TTS provider.""" hass = None diff --git a/homeassistant/components/upcloud.py b/homeassistant/components/upcloud.py index 9de7f6c44441..0f503dcdc390 100644 --- a/homeassistant/components/upcloud.py +++ b/homeassistant/components/upcloud.py @@ -92,7 +92,7 @@ def setup(hass, config): return True -class UpCloud(object): +class UpCloud: """Handle all communication with the UpCloud API.""" def __init__(self, manager): diff --git a/homeassistant/components/usps.py b/homeassistant/components/usps.py index 364562f11193..41aa240492be 100644 --- a/homeassistant/components/usps.py +++ b/homeassistant/components/usps.py @@ -65,7 +65,7 @@ def setup(hass, config): return True -class USPSData(object): +class USPSData: """Stores the data retrieved from USPS. For each entity to use, acts as the single point responsible for fetching diff --git a/homeassistant/components/verisure.py b/homeassistant/components/verisure.py index b367752c2478..1f26ab639d6f 100644 --- a/homeassistant/components/verisure.py +++ b/homeassistant/components/verisure.py @@ -89,7 +89,7 @@ def setup(hass, config): return True -class VerisureHub(object): +class VerisureHub: """A Verisure hub wrapper class.""" def __init__(self, domain_config, verisure): diff --git a/homeassistant/components/vultr.py b/homeassistant/components/vultr.py index 59fc707bb28e..b28189444ee5 100644 --- a/homeassistant/components/vultr.py +++ b/homeassistant/components/vultr.py @@ -74,7 +74,7 @@ def setup(hass, config): return True -class Vultr(object): +class Vultr: """Handle all communication with the Vultr API.""" def __init__(self, api_key): diff --git a/homeassistant/components/weather/darksky.py b/homeassistant/components/weather/darksky.py index 7afa97fd4f61..6dac22bc941a 100644 --- a/homeassistant/components/weather/darksky.py +++ b/homeassistant/components/weather/darksky.py @@ -149,7 +149,7 @@ class DarkSkyWeather(WeatherEntity): self._ds_daily = self._dark_sky.daily -class DarkSkyData(object): +class DarkSkyData: """Get the latest data from Dark Sky.""" def __init__(self, api_key, latitude, longitude, units): diff --git a/homeassistant/components/weather/openweathermap.py b/homeassistant/components/weather/openweathermap.py index 65fa7c8cb0f9..0c876d9e2d74 100644 --- a/homeassistant/components/weather/openweathermap.py +++ b/homeassistant/components/weather/openweathermap.py @@ -195,7 +195,7 @@ class OpenWeatherMapWeather(WeatherEntity): self.forecast_data = self._owm.forecast_data -class WeatherData(object): +class WeatherData: """Get the latest data from OpenWeatherMap.""" def __init__(self, owm, latitude, longitude, mode): diff --git a/homeassistant/components/weather/yweather.py b/homeassistant/components/weather/yweather.py index f9befece5a46..3f12195d6bff 100644 --- a/homeassistant/components/weather/yweather.py +++ b/homeassistant/components/weather/yweather.py @@ -175,7 +175,7 @@ class YahooWeatherWeather(WeatherEntity): return -class YahooWeatherData(object): +class YahooWeatherData: """Handle the Yahoo! API object and limit updates.""" def __init__(self, woeid, temp_unit): diff --git a/homeassistant/components/zigbee.py b/homeassistant/components/zigbee.py index 3a84e9638411..67bdf7442515 100644 --- a/homeassistant/components/zigbee.py +++ b/homeassistant/components/zigbee.py @@ -124,7 +124,7 @@ def frame_is_relevant(entity, frame): return True -class ZigBeeConfig(object): +class ZigBeeConfig: """Handle the fetching of configuration from the config file.""" def __init__(self, config): diff --git a/homeassistant/core.py b/homeassistant/core.py index dac16111de78..947ab78b673b 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -116,7 +116,7 @@ class CoreState(enum.Enum): return self.value # type: ignore -class HomeAssistant(object): +class HomeAssistant: """Root object of the Home Assistant home automation.""" def __init__(self, loop=None): @@ -347,7 +347,7 @@ class EventOrigin(enum.Enum): return self.value -class Event(object): +class Event: """Representation of an event within the bus.""" __slots__ = ['event_type', 'data', 'origin', 'time_fired'] @@ -392,7 +392,7 @@ class Event(object): self.time_fired == other.time_fired) -class EventBus(object): +class EventBus: """Allow the firing of and listening for events.""" def __init__(self, hass: HomeAssistant) -> None: @@ -547,7 +547,7 @@ class EventBus(object): _LOGGER.warning("Unable to remove unknown listener %s", listener) -class State(object): +class State: """Object to represent a state within the state machine. entity_id: the entity that is represented. @@ -654,7 +654,7 @@ class State(object): dt_util.as_local(self.last_changed).isoformat()) -class StateMachine(object): +class StateMachine: """Helper class that tracks the state of different entities.""" def __init__(self, bus, loop): @@ -787,7 +787,7 @@ class StateMachine(object): }) -class Service(object): +class Service: """Representation of a callable service.""" __slots__ = ['func', 'schema', 'is_callback', 'is_coroutinefunction'] @@ -800,7 +800,7 @@ class Service(object): self.is_coroutinefunction = asyncio.iscoroutinefunction(func) -class ServiceCall(object): +class ServiceCall: """Representation of a call to a service.""" __slots__ = ['domain', 'service', 'data', 'call_id'] @@ -821,7 +821,7 @@ class ServiceCall(object): return "".format(self.domain, self.service) -class ServiceRegistry(object): +class ServiceRegistry: """Offer the services over the eventbus.""" def __init__(self, hass): @@ -1055,7 +1055,7 @@ class ServiceRegistry(object): _LOGGER.exception('Error executing service %s', service_call) -class Config(object): +class Config: """Configuration settings for Home Assistant.""" def __init__(self): diff --git a/homeassistant/helpers/entity.py b/homeassistant/helpers/entity.py index 7dc5d2524ecc..c7e88b210b3b 100644 --- a/homeassistant/helpers/entity.py +++ b/homeassistant/helpers/entity.py @@ -56,7 +56,7 @@ def async_generate_entity_id(entity_id_format: str, name: Optional[str], entity_id_format.format(slugify(name)), current_ids) -class Entity(object): +class Entity: """An abstract class for Home Assistant entities.""" # SAFE TO OVERWRITE diff --git a/homeassistant/helpers/entity_component.py b/homeassistant/helpers/entity_component.py index 4ac3a147296f..72b6ceecbfd4 100644 --- a/homeassistant/helpers/entity_component.py +++ b/homeassistant/helpers/entity_component.py @@ -17,7 +17,7 @@ from .entity_platform import EntityPlatform DEFAULT_SCAN_INTERVAL = timedelta(seconds=15) -class EntityComponent(object): +class EntityComponent: """The EntityComponent manages platforms that manages entities. This class has the following responsibilities: diff --git a/homeassistant/helpers/entity_platform.py b/homeassistant/helpers/entity_platform.py index 472a88888d88..0847c116954b 100644 --- a/homeassistant/helpers/entity_platform.py +++ b/homeassistant/helpers/entity_platform.py @@ -15,7 +15,7 @@ SLOW_SETUP_MAX_WAIT = 60 PLATFORM_NOT_READY_RETRIES = 10 -class EntityPlatform(object): +class EntityPlatform: """Manage the entities for a single platform.""" def __init__(self, *, hass, logger, domain, platform_name, platform, diff --git a/homeassistant/helpers/entity_values.py b/homeassistant/helpers/entity_values.py index 19980394d26d..5caa6b93131f 100644 --- a/homeassistant/helpers/entity_values.py +++ b/homeassistant/helpers/entity_values.py @@ -6,7 +6,7 @@ import re from homeassistant.core import split_entity_id -class EntityValues(object): +class EntityValues: """Class to store entity id based values.""" def __init__(self, exact=None, domain=None, glob=None): diff --git a/homeassistant/helpers/state.py b/homeassistant/helpers/state.py index 72deabaae284..b2360f3aca52 100644 --- a/homeassistant/helpers/state.py +++ b/homeassistant/helpers/state.py @@ -92,7 +92,7 @@ SERVICE_TO_STATE = { } -class AsyncTrackStates(object): +class AsyncTrackStates: """ Record the time when the with-block is entered. diff --git a/homeassistant/helpers/template.py b/homeassistant/helpers/template.py index f523726c3888..4d09416398b3 100644 --- a/homeassistant/helpers/template.py +++ b/homeassistant/helpers/template.py @@ -82,7 +82,7 @@ def extract_entities(template, variables=None): return MATCH_ALL -class Template(object): +class Template: """Class to hold a template and manage caching and rendering.""" def __init__(self, template, hass=None): @@ -198,7 +198,7 @@ class Template(object): self.hass == other.hass) -class AllStates(object): +class AllStates: """Class to expose all HA states as attributes.""" def __init__(self, hass): @@ -226,7 +226,7 @@ class AllStates(object): return STATE_UNKNOWN if state is None else state.state -class DomainStates(object): +class DomainStates: """Class to expose a specific HA domain as attributes.""" def __init__(self, hass, domain): @@ -286,7 +286,7 @@ def _wrap_state(state): return None if state is None else TemplateState(state) -class TemplateMethods(object): +class TemplateMethods: """Class to expose helpers to templates.""" def __init__(self, hass): diff --git a/homeassistant/remote.py b/homeassistant/remote.py index ae932b7d9554..b8e6a862b46d 100644 --- a/homeassistant/remote.py +++ b/homeassistant/remote.py @@ -41,7 +41,7 @@ class APIStatus(enum.Enum): return self.value # type: ignore -class API(object): +class API: """Object to pass around Home Assistant API location and credentials.""" def __init__(self, host: str, api_password: Optional[str] = None, diff --git a/homeassistant/util/__init__.py b/homeassistant/util/__init__.py index c52bfa584daa..a1c0fb0024c6 100644 --- a/homeassistant/util/__init__.py +++ b/homeassistant/util/__init__.py @@ -236,7 +236,7 @@ class OrderedSet(MutableSet): return set(self) == set(other) -class Throttle(object): +class Throttle: """A class for throttling the execution of tasks. This method decorator adds a cooldown to a method to prevent it from being diff --git a/homeassistant/util/logging.py b/homeassistant/util/logging.py index 65999a3a5c74..7ce98fc2f2a2 100644 --- a/homeassistant/util/logging.py +++ b/homeassistant/util/logging.py @@ -22,7 +22,7 @@ class HideSensitiveDataFilter(logging.Filter): # pylint: disable=invalid-name -class AsyncHandler(object): +class AsyncHandler: """Logging handler wrapper to add an async layer.""" def __init__(self, loop, handler): diff --git a/homeassistant/util/unit_system.py b/homeassistant/util/unit_system.py index ec17c1b796b9..b8fb393a2f32 100644 --- a/homeassistant/util/unit_system.py +++ b/homeassistant/util/unit_system.py @@ -62,7 +62,7 @@ def is_valid_unit(unit: str, unit_type: str) -> bool: return unit in units -class UnitSystem(object): +class UnitSystem: """A container for units of measure.""" def __init__(self: object, name: str, temperature: str, length: str, diff --git a/tests/common.py b/tests/common.py index b03d473e6f32..3e8e813164e3 100644 --- a/tests/common.py +++ b/tests/common.py @@ -355,7 +355,7 @@ def ensure_auth_manager_loaded(auth_mgr): store._users = OrderedDict() -class MockModule(object): +class MockModule: """Representation of a fake module.""" # pylint: disable=invalid-name @@ -391,7 +391,7 @@ class MockModule(object): self.async_unload_entry = async_unload_entry -class MockPlatform(object): +class MockPlatform: """Provide a fake platform.""" # pylint: disable=invalid-name diff --git a/tests/components/alexa/test_smart_home.py b/tests/components/alexa/test_smart_home.py index afa4d19b5d91..ce4ec5aa146c 100644 --- a/tests/components/alexa/test_smart_home.py +++ b/tests/components/alexa/test_smart_home.py @@ -1225,7 +1225,7 @@ def reported_properties(hass, endpoint): return _ReportedProperties(msg['context']['properties']) -class _ReportedProperties(object): +class _ReportedProperties: def __init__(self, properties): self.properties = properties diff --git a/tests/components/binary_sensor/test_ffmpeg.py b/tests/components/binary_sensor/test_ffmpeg.py index aadafadd4a65..da9350008d86 100644 --- a/tests/components/binary_sensor/test_ffmpeg.py +++ b/tests/components/binary_sensor/test_ffmpeg.py @@ -7,7 +7,7 @@ from tests.common import ( get_test_home_assistant, assert_setup_component, mock_coro) -class TestFFmpegNoiseSetup(object): +class TestFFmpegNoiseSetup: """Test class for ffmpeg.""" def setup_method(self): @@ -72,7 +72,7 @@ class TestFFmpegNoiseSetup(object): assert entity.state == 'on' -class TestFFmpegMotionSetup(object): +class TestFFmpegMotionSetup: """Test class for ffmpeg.""" def setup_method(self): diff --git a/tests/components/binary_sensor/test_workday.py b/tests/components/binary_sensor/test_workday.py index af7e856e4175..893745ce3ded 100644 --- a/tests/components/binary_sensor/test_workday.py +++ b/tests/components/binary_sensor/test_workday.py @@ -12,7 +12,7 @@ from tests.common import ( FUNCTION_PATH = 'homeassistant.components.binary_sensor.workday.get_date' -class TestWorkdaySetup(object): +class TestWorkdaySetup: """Test class for workday sensor.""" def setup_method(self): diff --git a/tests/components/camera/test_init.py b/tests/components/camera/test_init.py index d0f1425a595d..cf902ca17790 100644 --- a/tests/components/camera/test_init.py +++ b/tests/components/camera/test_init.py @@ -30,7 +30,7 @@ def mock_camera(hass): yield -class TestSetupCamera(object): +class TestSetupCamera: """Test class for setup camera.""" def setup_method(self): @@ -53,7 +53,7 @@ class TestSetupCamera(object): setup_component(self.hass, camera.DOMAIN, config) -class TestGetImage(object): +class TestGetImage: """Test class for camera.""" def setup_method(self): diff --git a/tests/components/device_tracker/test_upc_connect.py b/tests/components/device_tracker/test_upc_connect.py index e45d70bc172d..6294ba3467a1 100644 --- a/tests/components/device_tracker/test_upc_connect.py +++ b/tests/components/device_tracker/test_upc_connect.py @@ -33,7 +33,7 @@ def mock_load_config(): yield -class TestUPCConnect(object): +class TestUPCConnect: """Tests for the Ddwrt device tracker platform.""" def setup_method(self): diff --git a/tests/components/image_processing/test_init.py b/tests/components/image_processing/test_init.py index 628c5405eaa5..ab2e3be11d68 100644 --- a/tests/components/image_processing/test_init.py +++ b/tests/components/image_processing/test_init.py @@ -12,7 +12,7 @@ from tests.common import ( get_test_home_assistant, get_test_instance_port, assert_setup_component) -class TestSetupImageProcessing(object): +class TestSetupImageProcessing: """Test class for setup image processing.""" def setup_method(self): @@ -48,7 +48,7 @@ class TestSetupImageProcessing(object): assert self.hass.services.has_service(ip.DOMAIN, 'scan') -class TestImageProcessing(object): +class TestImageProcessing: """Test class for image processing.""" def setup_method(self): @@ -109,7 +109,7 @@ class TestImageProcessing(object): assert state.state == '0' -class TestImageProcessingAlpr(object): +class TestImageProcessingAlpr: """Test class for alpr image processing.""" def setup_method(self): @@ -211,7 +211,7 @@ class TestImageProcessingAlpr(object): assert event_data[0]['entity_id'] == 'image_processing.demo_alpr' -class TestImageProcessingFace(object): +class TestImageProcessingFace: """Test class for face image processing.""" def setup_method(self): diff --git a/tests/components/image_processing/test_microsoft_face_detect.py b/tests/components/image_processing/test_microsoft_face_detect.py index b743dee97047..acc2519c9b72 100644 --- a/tests/components/image_processing/test_microsoft_face_detect.py +++ b/tests/components/image_processing/test_microsoft_face_detect.py @@ -11,7 +11,7 @@ from tests.common import ( get_test_home_assistant, assert_setup_component, load_fixture, mock_coro) -class TestMicrosoftFaceDetectSetup(object): +class TestMicrosoftFaceDetectSetup: """Test class for image processing.""" def setup_method(self): @@ -74,7 +74,7 @@ class TestMicrosoftFaceDetectSetup(object): assert self.hass.states.get('image_processing.test_local') -class TestMicrosoftFaceDetect(object): +class TestMicrosoftFaceDetect: """Test class for image processing.""" def setup_method(self): diff --git a/tests/components/image_processing/test_microsoft_face_identify.py b/tests/components/image_processing/test_microsoft_face_identify.py index c2ab5684ed0d..8797f661767e 100644 --- a/tests/components/image_processing/test_microsoft_face_identify.py +++ b/tests/components/image_processing/test_microsoft_face_identify.py @@ -11,7 +11,7 @@ from tests.common import ( get_test_home_assistant, assert_setup_component, load_fixture, mock_coro) -class TestMicrosoftFaceIdentifySetup(object): +class TestMicrosoftFaceIdentifySetup: """Test class for image processing.""" def setup_method(self): @@ -75,7 +75,7 @@ class TestMicrosoftFaceIdentifySetup(object): assert self.hass.states.get('image_processing.test_local') -class TestMicrosoftFaceIdentify(object): +class TestMicrosoftFaceIdentify: """Test class for image processing.""" def setup_method(self): diff --git a/tests/components/image_processing/test_openalpr_cloud.py b/tests/components/image_processing/test_openalpr_cloud.py index 50060e08a4b4..65e735a6f7e0 100644 --- a/tests/components/image_processing/test_openalpr_cloud.py +++ b/tests/components/image_processing/test_openalpr_cloud.py @@ -12,7 +12,7 @@ from tests.common import ( get_test_home_assistant, assert_setup_component, load_fixture, mock_coro) -class TestOpenAlprCloudSetup(object): +class TestOpenAlprCloudSetup: """Test class for image processing.""" def setup_method(self): @@ -103,7 +103,7 @@ class TestOpenAlprCloudSetup(object): setup_component(self.hass, ip.DOMAIN, config) -class TestOpenAlprCloud(object): +class TestOpenAlprCloud: """Test class for image processing.""" def setup_method(self): diff --git a/tests/components/image_processing/test_openalpr_local.py b/tests/components/image_processing/test_openalpr_local.py index fc40f8e17fb5..38e94166c5a9 100644 --- a/tests/components/image_processing/test_openalpr_local.py +++ b/tests/components/image_processing/test_openalpr_local.py @@ -26,7 +26,7 @@ def mock_async_subprocess(): return async_popen -class TestOpenAlprLocalSetup(object): +class TestOpenAlprLocalSetup: """Test class for image processing.""" def setup_method(self): @@ -96,7 +96,7 @@ class TestOpenAlprLocalSetup(object): setup_component(self.hass, ip.DOMAIN, config) -class TestOpenAlprLocal(object): +class TestOpenAlprLocal: """Test class for image processing.""" def setup_method(self): diff --git a/tests/components/media_player/test_blackbird.py b/tests/components/media_player/test_blackbird.py index 7c85775949c9..550bfe88a61e 100644 --- a/tests/components/media_player/test_blackbird.py +++ b/tests/components/media_player/test_blackbird.py @@ -25,7 +25,7 @@ class AttrDict(dict): return self[item] -class MockBlackbird(object): +class MockBlackbird: """Mock for pyblackbird object.""" def __init__(self): diff --git a/tests/components/media_player/test_monoprice.py b/tests/components/media_player/test_monoprice.py index 399cdc67ca65..14e1769047a7 100644 --- a/tests/components/media_player/test_monoprice.py +++ b/tests/components/media_player/test_monoprice.py @@ -27,7 +27,7 @@ class AttrDict(dict): return self[item] -class MockMonoprice(object): +class MockMonoprice: """Mock for pymonoprice object.""" def __init__(self): diff --git a/tests/components/media_player/test_yamaha.py b/tests/components/media_player/test_yamaha.py index e17241485db8..980284737a2b 100644 --- a/tests/components/media_player/test_yamaha.py +++ b/tests/components/media_player/test_yamaha.py @@ -15,7 +15,7 @@ def _create_zone_mock(name, url): return zone -class FakeYamahaDevice(object): +class FakeYamahaDevice: """A fake Yamaha device.""" def __init__(self, ctrl_url, name, zones=None): diff --git a/tests/components/notify/test_html5.py b/tests/components/notify/test_html5.py index 318f3c7512ca..486300679b74 100644 --- a/tests/components/notify/test_html5.py +++ b/tests/components/notify/test_html5.py @@ -65,7 +65,7 @@ async def mock_client(hass, aiohttp_client, registrations=None): return await aiohttp_client(hass.http.app) -class TestHtml5Notify(object): +class TestHtml5Notify: """Tests for HTML5 notify platform.""" def test_get_service_with_no_json(self): diff --git a/tests/components/test_ffmpeg.py b/tests/components/test_ffmpeg.py index 5a5fdffd5a3b..44c3a1dd6954 100644 --- a/tests/components/test_ffmpeg.py +++ b/tests/components/test_ffmpeg.py @@ -38,7 +38,7 @@ class MockFFmpegDev(ffmpeg.FFmpegBase): self.called_entities = entity_ids -class TestFFmpegSetup(object): +class TestFFmpegSetup: """Test class for ffmpeg.""" def setup_method(self): diff --git a/tests/components/test_microsoft_face.py b/tests/components/test_microsoft_face.py index 370059a0a095..92f840b8033d 100644 --- a/tests/components/test_microsoft_face.py +++ b/tests/components/test_microsoft_face.py @@ -9,7 +9,7 @@ from tests.common import ( get_test_home_assistant, assert_setup_component, mock_coro, load_fixture) -class TestMicrosoftFaceSetup(object): +class TestMicrosoftFaceSetup: """Test the microsoft face component.""" def setup_method(self): diff --git a/tests/components/test_mqtt_eventstream.py b/tests/components/test_mqtt_eventstream.py index 48bc04d46edf..389248179803 100644 --- a/tests/components/test_mqtt_eventstream.py +++ b/tests/components/test_mqtt_eventstream.py @@ -18,7 +18,7 @@ from tests.common import ( ) -class TestMqttEventStream(object): +class TestMqttEventStream: """Test the MQTT eventstream module.""" def setup_method(self): diff --git a/tests/components/test_mqtt_statestream.py b/tests/components/test_mqtt_statestream.py index 2ed2f4487ea4..4cf79e679cd4 100644 --- a/tests/components/test_mqtt_statestream.py +++ b/tests/components/test_mqtt_statestream.py @@ -12,7 +12,7 @@ from tests.common import ( ) -class TestMqttStateStream(object): +class TestMqttStateStream: """Test the MQTT statestream module.""" def setup_method(self): diff --git a/tests/components/test_plant.py b/tests/components/test_plant.py index ee1372509d92..95167dd181b1 100644 --- a/tests/components/test_plant.py +++ b/tests/components/test_plant.py @@ -41,7 +41,7 @@ GOOD_CONFIG = { } -class _MockState(object): +class _MockState: def __init__(self, state=None): self.state = state diff --git a/tests/components/test_rest_command.py b/tests/components/test_rest_command.py index 3ddcfae8c01b..097fb799d402 100644 --- a/tests/components/test_rest_command.py +++ b/tests/components/test_rest_command.py @@ -10,7 +10,7 @@ from tests.common import ( get_test_home_assistant, assert_setup_component) -class TestRestCommandSetup(object): +class TestRestCommandSetup: """Test the rest command component.""" def setup_method(self): @@ -47,7 +47,7 @@ class TestRestCommandSetup(object): assert self.hass.services.has_service(rc.DOMAIN, 'test_get') -class TestRestCommandComponent(object): +class TestRestCommandComponent: """Test the rest command component.""" def setup_method(self): diff --git a/tests/components/tts/test_google.py b/tests/components/tts/test_google.py index 6a2d2c650354..cf9a7b2db295 100644 --- a/tests/components/tts/test_google.py +++ b/tests/components/tts/test_google.py @@ -15,7 +15,7 @@ from tests.common import ( from .test_init import mutagen_mock # noqa -class TestTTSGooglePlatform(object): +class TestTTSGooglePlatform: """Test the Google speech component.""" def setup_method(self): diff --git a/tests/components/tts/test_init.py b/tests/components/tts/test_init.py index b6bfa430fd24..e8746ee762f6 100644 --- a/tests/components/tts/test_init.py +++ b/tests/components/tts/test_init.py @@ -29,7 +29,7 @@ def mutagen_mock(): yield -class TestTTS(object): +class TestTTS: """Test the Google speech component.""" def setup_method(self): diff --git a/tests/components/tts/test_marytts.py b/tests/components/tts/test_marytts.py index b55236c5e8ee..7ec2ae39cd61 100644 --- a/tests/components/tts/test_marytts.py +++ b/tests/components/tts/test_marytts.py @@ -14,7 +14,7 @@ from tests.common import ( from .test_init import mutagen_mock # noqa -class TestTTSMaryTTSPlatform(object): +class TestTTSMaryTTSPlatform: """Test the speech component.""" def setup_method(self): diff --git a/tests/components/tts/test_voicerss.py b/tests/components/tts/test_voicerss.py index 2abdc0e69ff1..365cf1ff73ba 100644 --- a/tests/components/tts/test_voicerss.py +++ b/tests/components/tts/test_voicerss.py @@ -14,7 +14,7 @@ from tests.common import ( from .test_init import mutagen_mock # noqa -class TestTTSVoiceRSSPlatform(object): +class TestTTSVoiceRSSPlatform: """Test the voicerss speech component.""" def setup_method(self): diff --git a/tests/components/tts/test_yandextts.py b/tests/components/tts/test_yandextts.py index 5b4ef4dcf538..82d203189287 100644 --- a/tests/components/tts/test_yandextts.py +++ b/tests/components/tts/test_yandextts.py @@ -13,7 +13,7 @@ from tests.common import ( from .test_init import mutagen_mock # noqa -class TestTTSYandexPlatform(object): +class TestTTSYandexPlatform: """Test the speech component.""" def setup_method(self): diff --git a/tests/helpers/test_dispatcher.py b/tests/helpers/test_dispatcher.py index 066e7386c6e2..55e67def2bc6 100644 --- a/tests/helpers/test_dispatcher.py +++ b/tests/helpers/test_dispatcher.py @@ -8,7 +8,7 @@ from homeassistant.helpers.dispatcher import ( from tests.common import get_test_home_assistant -class TestHelpersDispatcher(object): +class TestHelpersDispatcher: """Tests for discovery helper methods.""" def setup_method(self, method): diff --git a/tests/helpers/test_entity.py b/tests/helpers/test_entity.py index 4211e3da31bd..4981ad23cc00 100644 --- a/tests/helpers/test_entity.py +++ b/tests/helpers/test_entity.py @@ -71,7 +71,7 @@ def test_async_update_support(hass): assert len(async_update) == 1 -class TestHelpersEntity(object): +class TestHelpersEntity: """Test homeassistant.helpers.entity module.""" def setup_method(self, method): diff --git a/tests/util/test_init.py b/tests/util/test_init.py index 60b0e68ca59b..1f43c5a4b49b 100644 --- a/tests/util/test_init.py +++ b/tests/util/test_init.py @@ -221,7 +221,7 @@ class TestUtil(unittest.TestCase): def test_throttle_per_instance(self): """Test that the throttle method is done per instance of a class.""" - class Tester(object): + class Tester: """A tester class for the throttle.""" @util.Throttle(timedelta(seconds=1)) @@ -234,7 +234,7 @@ class TestUtil(unittest.TestCase): def test_throttle_on_method(self): """Test that throttle works when wrapping a method.""" - class Tester(object): + class Tester: """A tester class for the throttle.""" def hello(self): @@ -249,7 +249,7 @@ class TestUtil(unittest.TestCase): def test_throttle_on_two_method(self): """Test that throttle works when wrapping two methods.""" - class Tester(object): + class Tester: """A test class for the throttle.""" @util.Throttle(timedelta(seconds=1)) From f2a99e83cd11fd67dcecdeac7fc81998de552ab0 Mon Sep 17 00:00:00 2001 From: huangyupeng Date: Fri, 20 Jul 2018 17:23:09 +0800 Subject: [PATCH 039/113] Add Tuya fan support (#15525) * Add Tuya fan platform * Add Tuya fan platform * fix as review required --- homeassistant/components/fan/tuya.py | 99 ++++++++++++++++++++++++++++ homeassistant/components/tuya.py | 1 + 2 files changed, 100 insertions(+) create mode 100644 homeassistant/components/fan/tuya.py diff --git a/homeassistant/components/fan/tuya.py b/homeassistant/components/fan/tuya.py new file mode 100644 index 000000000000..f19a9e5a5f77 --- /dev/null +++ b/homeassistant/components/fan/tuya.py @@ -0,0 +1,99 @@ +""" +Support for Tuya fans. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/fan.tuya/ +""" + +from homeassistant.components.fan import ( + ENTITY_ID_FORMAT, FanEntity, SUPPORT_OSCILLATE, SUPPORT_SET_SPEED) +from homeassistant.components.tuya import DATA_TUYA, TuyaDevice +from homeassistant.const import STATE_OFF + +DEPENDENCIES = ['tuya'] + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Set up Tuya fan platform.""" + if discovery_info is None: + return + tuya = hass.data[DATA_TUYA] + dev_ids = discovery_info.get('dev_ids') + devices = [] + for dev_id in dev_ids: + device = tuya.get_device_by_id(dev_id) + if device is None: + continue + devices.append(TuyaFanDevice(device)) + add_devices(devices) + + +class TuyaFanDevice(TuyaDevice, FanEntity): + """Tuya fan devices.""" + + def __init__(self, tuya): + """Init Tuya fan device.""" + super().__init__(tuya) + self.entity_id = ENTITY_ID_FORMAT.format(tuya.object_id()) + self.speeds = [STATE_OFF] + + async def async_added_to_hass(self): + """Create fan list when add to hass.""" + await super().async_added_to_hass() + self.speeds.extend(self.tuya.speed_list()) + + def set_speed(self, speed: str) -> None: + """Set the speed of the fan.""" + if speed == STATE_OFF: + self.turn_off() + else: + self.tuya.set_speed(speed) + + def turn_on(self, speed: str = None, **kwargs) -> None: + """Turn on the fan.""" + if speed is not None: + self.set_speed(speed) + else: + self.tuya.turn_on() + + def turn_off(self, **kwargs) -> None: + """Turn the entity off.""" + self.tuya.turn_off() + + def oscillate(self, oscillating) -> None: + """Oscillate the fan.""" + self.tuya.oscillate(oscillating) + + @property + def oscillating(self): + """Return current oscillating status.""" + if self.supported_features & SUPPORT_OSCILLATE == 0: + return None + if self.speed == STATE_OFF: + return False + return self.tuya.oscillating() + + @property + def is_on(self): + """Return true if the entity is on.""" + return self.tuya.state() + + @property + def speed(self) -> str: + """Return the current speed.""" + if self.is_on: + return self.tuya.speed() + return STATE_OFF + + @property + def speed_list(self) -> list: + """Get the list of available speeds.""" + return self.speeds + + @property + def supported_features(self) -> int: + """Flag supported features.""" + supports = SUPPORT_SET_SPEED + if self.tuya.support_oscillate(): + supports = supports | SUPPORT_OSCILLATE + return supports diff --git a/homeassistant/components/tuya.py b/homeassistant/components/tuya.py index ccb5227aead4..337071baf697 100644 --- a/homeassistant/components/tuya.py +++ b/homeassistant/components/tuya.py @@ -34,6 +34,7 @@ SERVICE_PULL_DEVICES = 'pull_devices' TUYA_TYPE_TO_HA = { 'climate': 'climate', + 'fan': 'fan', 'light': 'light', 'switch': 'switch', } From f1286f8e6baea842aebd86330e77c2f8bce503d4 Mon Sep 17 00:00:00 2001 From: Jason Hu Date: Fri, 20 Jul 2018 03:09:48 -0700 Subject: [PATCH 040/113] Reset failed login attempts counter when login success (#15564) --- homeassistant/components/http/ban.py | 27 ++++++++++- homeassistant/components/http/view.py | 8 +++- homeassistant/components/websocket_api.py | 4 +- tests/components/http/test_ban.py | 58 ++++++++++++++++++++++- 4 files changed, 91 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/http/ban.py b/homeassistant/components/http/ban.py index 7bb5194b1ec4..e05f951322e9 100644 --- a/homeassistant/components/http/ban.py +++ b/homeassistant/components/http/ban.py @@ -72,7 +72,11 @@ async def ban_middleware(request, handler): async def process_wrong_login(request): - """Process a wrong login attempt.""" + """Process a wrong login attempt. + + Increase failed login attempts counter for remote IP address. + Add ip ban entry if failed login attempts exceeds threshold. + """ remote_addr = request[KEY_REAL_IP] msg = ('Login attempt or request with invalid authentication ' @@ -107,6 +111,27 @@ async def process_wrong_login(request): 'Banning IP address', NOTIFICATION_ID_BAN) +async def process_success_login(request): + """Process a success login attempt. + + Reset failed login attempts counter for remote IP address. + No release IP address from banned list function, it can only be done by + manual modify ip bans config file. + """ + remote_addr = request[KEY_REAL_IP] + + # Check if ban middleware is loaded + if (KEY_BANNED_IPS not in request.app or + request.app[KEY_LOGIN_THRESHOLD] < 1): + return + + if remote_addr in request.app[KEY_FAILED_LOGIN_ATTEMPTS] and \ + request.app[KEY_FAILED_LOGIN_ATTEMPTS][remote_addr] > 0: + _LOGGER.debug('Login success, reset failed login attempts counter' + ' from %s', remote_addr) + request.app[KEY_FAILED_LOGIN_ATTEMPTS].pop(remote_addr) + + class IpBan: """Represents banned IP address.""" diff --git a/homeassistant/components/http/view.py b/homeassistant/components/http/view.py index 34b72e9ed69f..f3d3cd06e22d 100644 --- a/homeassistant/components/http/view.py +++ b/homeassistant/components/http/view.py @@ -12,6 +12,7 @@ from aiohttp import web from aiohttp.web_exceptions import HTTPUnauthorized, HTTPInternalServerError import homeassistant.remote as rem +from homeassistant.components.http.ban import process_success_login from homeassistant.core import is_callback from homeassistant.const import CONTENT_TYPE_JSON @@ -91,8 +92,11 @@ def request_handler_factory(view, handler): authenticated = request.get(KEY_AUTHENTICATED, False) - if view.requires_auth and not authenticated: - raise HTTPUnauthorized() + if view.requires_auth: + if authenticated: + await process_success_login(request) + else: + raise HTTPUnauthorized() _LOGGER.info('Serving %s to %s (auth: %s)', request.path, request.get(KEY_REAL_IP), authenticated) diff --git a/homeassistant/components/websocket_api.py b/homeassistant/components/websocket_api.py index 6cd169090413..98e3057338a6 100644 --- a/homeassistant/components/websocket_api.py +++ b/homeassistant/components/websocket_api.py @@ -26,7 +26,8 @@ from homeassistant.helpers.service import async_get_all_descriptions from homeassistant.components.http import HomeAssistantView from homeassistant.components.http.auth import validate_password from homeassistant.components.http.const import KEY_AUTHENTICATED -from homeassistant.components.http.ban import process_wrong_login +from homeassistant.components.http.ban import process_wrong_login, \ + process_success_login DOMAIN = 'websocket_api' @@ -360,6 +361,7 @@ class ActiveConnection: return wsock self.debug("Auth OK") + await process_success_login(request) await self.wsock.send_json(auth_ok_message()) # ---------- AUTH PHASE OVER ---------- diff --git a/tests/components/http/test_ban.py b/tests/components/http/test_ban.py index c5691cf3e2ac..a6a07928113b 100644 --- a/tests/components/http/test_ban.py +++ b/tests/components/http/test_ban.py @@ -1,14 +1,18 @@ """The tests for the Home Assistant HTTP component.""" # pylint: disable=protected-access -from unittest.mock import patch, mock_open +from ipaddress import ip_address +from unittest.mock import patch, mock_open, Mock from aiohttp import web from aiohttp.web_exceptions import HTTPUnauthorized +from aiohttp.web_middlewares import middleware +from homeassistant.components.http import KEY_AUTHENTICATED +from homeassistant.components.http.view import request_handler_factory from homeassistant.setup import async_setup_component import homeassistant.components.http as http from homeassistant.components.http.ban import ( - IpBan, IP_BANS_FILE, setup_bans, KEY_BANNED_IPS) + IpBan, IP_BANS_FILE, setup_bans, KEY_BANNED_IPS, KEY_FAILED_LOGIN_ATTEMPTS) from . import mock_real_ip @@ -88,3 +92,53 @@ async def test_ip_bans_file_creation(hass, aiohttp_client): resp = await client.get('/') assert resp.status == 403 assert m.call_count == 1 + + +async def test_failed_login_attempts_counter(hass, aiohttp_client): + """Testing if failed login attempts counter increased.""" + app = web.Application() + app['hass'] = hass + + async def auth_handler(request): + """Return 200 status code.""" + return None, 200 + + app.router.add_get('/auth_true', request_handler_factory( + Mock(requires_auth=True), auth_handler)) + app.router.add_get('/auth_false', request_handler_factory( + Mock(requires_auth=True), auth_handler)) + app.router.add_get('/', request_handler_factory( + Mock(requires_auth=False), auth_handler)) + + setup_bans(hass, app, 5) + remote_ip = ip_address("200.201.202.204") + mock_real_ip(app)("200.201.202.204") + + @middleware + async def mock_auth(request, handler): + """Mock auth middleware.""" + if 'auth_true' in request.path: + request[KEY_AUTHENTICATED] = True + else: + request[KEY_AUTHENTICATED] = False + return await handler(request) + + app.middlewares.append(mock_auth) + + client = await aiohttp_client(app) + + resp = await client.get('/auth_false') + assert resp.status == 401 + assert app[KEY_FAILED_LOGIN_ATTEMPTS][remote_ip] == 1 + + resp = await client.get('/auth_false') + assert resp.status == 401 + assert app[KEY_FAILED_LOGIN_ATTEMPTS][remote_ip] == 2 + + resp = await client.get('/') + assert resp.status == 200 + assert app[KEY_FAILED_LOGIN_ATTEMPTS][remote_ip] == 2 + + resp = await client.get('/auth_true') + assert resp.status == 200 + assert remote_ip not in app[KEY_FAILED_LOGIN_ATTEMPTS] From 3341c5cf2169bd6a1a3a720636df24d534eafc9f Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 20 Jul 2018 12:30:10 +0200 Subject: [PATCH 041/113] Update the frontend to 20180720.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 dc5d1d7bf7e3..68e88406ad69 100644 --- a/homeassistant/components/frontend/__init__.py +++ b/homeassistant/components/frontend/__init__.py @@ -26,7 +26,7 @@ from homeassistant.helpers.translation import async_get_translations from homeassistant.loader import bind_hass from homeassistant.util.yaml import load_yaml -REQUIREMENTS = ['home-assistant-frontend==20180719.0'] +REQUIREMENTS = ['home-assistant-frontend==20180720.0'] DOMAIN = 'frontend' DEPENDENCIES = ['api', 'websocket_api', 'http', 'system_log', 'onboarding'] diff --git a/requirements_all.txt b/requirements_all.txt index ecd28ed71703..75a563c244f7 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -415,7 +415,7 @@ hole==0.3.0 holidays==0.9.5 # homeassistant.components.frontend -home-assistant-frontend==20180719.0 +home-assistant-frontend==20180720.0 # homeassistant.components.homekit_controller # homekit==0.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index e1e1593b8142..3de2285eae9d 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -81,7 +81,7 @@ hbmqtt==0.9.2 holidays==0.9.5 # homeassistant.components.frontend -home-assistant-frontend==20180719.0 +home-assistant-frontend==20180720.0 # homeassistant.components.homematicip_cloud homematicip==0.9.8 From 5cf9cd686c21697a2e03a2724aa6b7e769838b6d Mon Sep 17 00:00:00 2001 From: Teemu R Date: Fri, 20 Jul 2018 14:40:10 +0200 Subject: [PATCH 042/113] light.tplink: initialize min & max mireds only once, avoid i/o outside update (#15571) * light.tplink: initialize min & max mireds only once, avoid i/o outside update * revert the index change * fix indent, sorry for overwriting your fix, balloob --- homeassistant/components/light/tplink.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/light/tplink.py b/homeassistant/components/light/tplink.py index 09a4fa3610d8..669901f5b57c 100644 --- a/homeassistant/components/light/tplink.py +++ b/homeassistant/components/light/tplink.py @@ -66,6 +66,8 @@ class TPLinkSmartBulb(Light): self._brightness = None self._hs = None self._supported_features = 0 + self._min_mireds = None + self._max_mireds = None self._emeter_params = {} @property @@ -107,12 +109,12 @@ class TPLinkSmartBulb(Light): @property def min_mireds(self): """Return minimum supported color temperature.""" - return kelvin_to_mired(self.smartbulb.valid_temperature_range[1]) + return self._min_mireds @property def max_mireds(self): """Return maximum supported color temperature.""" - return kelvin_to_mired(self.smartbulb.valid_temperature_range[0]) + return self._max_mireds @property def color_temp(self): @@ -195,5 +197,9 @@ class TPLinkSmartBulb(Light): self._supported_features += SUPPORT_BRIGHTNESS if self.smartbulb.is_variable_color_temp: self._supported_features += SUPPORT_COLOR_TEMP + self._min_mireds = kelvin_to_mired( + self.smartbulb.valid_temperature_range[1]) + self._max_mireds = kelvin_to_mired( + self.smartbulb.valid_temperature_range[0]) if self.smartbulb.is_color: self._supported_features += SUPPORT_COLOR From 9a8389060c914436edf2fa5367f697952ea81e4a Mon Sep 17 00:00:00 2001 From: Eugenio Panadero Date: Fri, 20 Jul 2018 15:18:02 +0200 Subject: [PATCH 043/113] fix aiohttp InvalidURL exception when fetching media player image (#15572) * fix aiohttp InvalidURL exception when fetching media player image The first call for the HA proxy (`/api/media_player_proxy/media_player.kodi?token=...&cache=...`) is receiving relative urls that are failing, this is a simple fix to precede the base_url when hostname is None. * fix import location and sort stdlib imports --- homeassistant/components/media_player/__init__.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/media_player/__init__.py b/homeassistant/components/media_player/__init__.py index d314dec65ea9..c475291227ac 100644 --- a/homeassistant/components/media_player/__init__.py +++ b/homeassistant/components/media_player/__init__.py @@ -6,12 +6,13 @@ https://home-assistant.io/components/media_player/ """ import asyncio import base64 +import collections from datetime import timedelta import functools as ft -import collections import hashlib import logging from random import SystemRandom +from urllib.parse import urlparse from aiohttp import web from aiohttp.hdrs import CONTENT_TYPE, CACHE_CONTROL @@ -956,6 +957,9 @@ async def _async_fetch_image(hass, url): cache_images = ENTITY_IMAGE_CACHE[CACHE_IMAGES] cache_maxsize = ENTITY_IMAGE_CACHE[CACHE_MAXSIZE] + if urlparse(url).hostname is None: + url = hass.config.api.base_url + url + if url not in cache_images: cache_images[url] = {CACHE_LOCK: asyncio.Lock(loop=hass.loop)} From ee8a815e6b95f86ce45f8f7eb77047491284a94b Mon Sep 17 00:00:00 2001 From: Ryan Davies Date: Sat, 21 Jul 2018 09:04:06 +1200 Subject: [PATCH 044/113] Allow MQTT Switch to have an optional state configuration (#15430) Switches by default use the payload_on and payload_off configuration parameters to specify both the payload the switch should send for a state but also what will be returned for the current state - which isnt always the same As a toggle switch might always send an ON or TOGGLE to toggle the switch, but still receive an ON or an OFF for the state topic - This change allows for splitting them apart --- homeassistant/components/switch/mqtt.py | 19 +++++++++++---- tests/components/switch/test_mqtt.py | 31 +++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/switch/mqtt.py b/homeassistant/components/switch/mqtt.py index 4442b186e301..eb91f8d846ae 100644 --- a/homeassistant/components/switch/mqtt.py +++ b/homeassistant/components/switch/mqtt.py @@ -31,12 +31,16 @@ DEFAULT_PAYLOAD_ON = 'ON' DEFAULT_PAYLOAD_OFF = 'OFF' DEFAULT_OPTIMISTIC = False CONF_UNIQUE_ID = 'unique_id' +CONF_STATE_ON = "state_on" +CONF_STATE_OFF = "state_off" PLATFORM_SCHEMA = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend({ vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, vol.Optional(CONF_ICON): cv.icon, vol.Optional(CONF_PAYLOAD_ON, default=DEFAULT_PAYLOAD_ON): cv.string, vol.Optional(CONF_PAYLOAD_OFF, default=DEFAULT_PAYLOAD_OFF): cv.string, + vol.Optional(CONF_STATE_ON): cv.string, + vol.Optional(CONF_STATE_OFF): cv.string, vol.Optional(CONF_UNIQUE_ID): cv.string, vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean, }).extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema) @@ -62,6 +66,8 @@ async def async_setup_platform(hass, config, async_add_devices, config.get(CONF_RETAIN), config.get(CONF_PAYLOAD_ON), config.get(CONF_PAYLOAD_OFF), + config.get(CONF_STATE_ON), + config.get(CONF_STATE_OFF), config.get(CONF_OPTIMISTIC), config.get(CONF_PAYLOAD_AVAILABLE), config.get(CONF_PAYLOAD_NOT_AVAILABLE), @@ -75,9 +81,10 @@ class MqttSwitch(MqttAvailability, SwitchDevice): def __init__(self, name, icon, state_topic, command_topic, availability_topic, - qos, retain, payload_on, payload_off, optimistic, - payload_available, payload_not_available, - unique_id: Optional[str], value_template): + qos, retain, payload_on, payload_off, state_on, + state_off, optimistic, payload_available, + payload_not_available, unique_id: Optional[str], + value_template): """Initialize the MQTT switch.""" super().__init__(availability_topic, qos, payload_available, payload_not_available) @@ -90,6 +97,8 @@ class MqttSwitch(MqttAvailability, SwitchDevice): self._retain = retain self._payload_on = payload_on self._payload_off = payload_off + self._state_on = state_on if state_on else self._payload_on + self._state_off = state_off if state_off else self._payload_off self._optimistic = optimistic self._template = value_template self._unique_id = unique_id @@ -104,9 +113,9 @@ class MqttSwitch(MqttAvailability, SwitchDevice): if self._template is not None: payload = self._template.async_render_with_possible_json_value( payload) - if payload == self._payload_on: + if payload == self._state_on: self._state = True - elif payload == self._payload_off: + elif payload == self._state_off: self._state = False self.async_schedule_update_ha_state() diff --git a/tests/components/switch/test_mqtt.py b/tests/components/switch/test_mqtt.py index 31f9a729c53d..7cd5a42b4a3e 100644 --- a/tests/components/switch/test_mqtt.py +++ b/tests/components/switch/test_mqtt.py @@ -249,6 +249,37 @@ class TestSwitchMQTT(unittest.TestCase): state = self.hass.states.get('switch.test') self.assertEqual(STATE_ON, state.state) + def test_custom_state_payload(self): + """Test the state payload.""" + assert setup_component(self.hass, switch.DOMAIN, { + switch.DOMAIN: { + 'platform': 'mqtt', + 'name': 'test', + 'state_topic': 'state-topic', + 'command_topic': 'command-topic', + 'payload_on': 1, + 'payload_off': 0, + 'state_on': "HIGH", + 'state_off': "LOW", + } + }) + + state = self.hass.states.get('switch.test') + self.assertEqual(STATE_OFF, state.state) + self.assertFalse(state.attributes.get(ATTR_ASSUMED_STATE)) + + fire_mqtt_message(self.hass, 'state-topic', 'HIGH') + self.hass.block_till_done() + + state = self.hass.states.get('switch.test') + self.assertEqual(STATE_ON, state.state) + + fire_mqtt_message(self.hass, 'state-topic', 'LOW') + self.hass.block_till_done() + + state = self.hass.states.get('switch.test') + self.assertEqual(STATE_OFF, state.state) + def test_unique_id(self): """Test unique id option only creates one switch per unique_id.""" assert setup_component(self.hass, switch.DOMAIN, { From d1b16e287c83cb08a7b89144edf031e5f71b0307 Mon Sep 17 00:00:00 2001 From: Anders Melchiorsen Date: Sat, 21 Jul 2018 10:14:56 +0200 Subject: [PATCH 045/113] Add unique_id to netgear_lte sensors (#15584) --- homeassistant/components/netgear_lte.py | 4 +++- homeassistant/components/sensor/netgear_lte.py | 16 +++++++++++----- requirements_all.txt | 2 +- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/netgear_lte.py b/homeassistant/components/netgear_lte.py index 23a01d37c2b8..d6bd83dd41fe 100644 --- a/homeassistant/components/netgear_lte.py +++ b/homeassistant/components/netgear_lte.py @@ -17,7 +17,7 @@ from homeassistant.helpers import config_validation as cv from homeassistant.helpers.aiohttp_client import async_create_clientsession from homeassistant.util import Throttle -REQUIREMENTS = ['eternalegypt==0.0.2'] +REQUIREMENTS = ['eternalegypt==0.0.3'] MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=10) @@ -37,6 +37,7 @@ class ModemData: """Class for modem state.""" modem = attr.ib() + serial_number = attr.ib(init=False) unread_count = attr.ib(init=False) usage = attr.ib(init=False) @@ -44,6 +45,7 @@ class ModemData: async def async_update(self): """Call the API to update the data.""" information = await self.modem.information() + self.serial_number = information.serial_number self.unread_count = sum(1 for x in information.sms if x.unread) self.usage = information.usage diff --git a/homeassistant/components/sensor/netgear_lte.py b/homeassistant/components/sensor/netgear_lte.py index b4a3e2a11559..dac1f81ad236 100644 --- a/homeassistant/components/sensor/netgear_lte.py +++ b/homeassistant/components/sensor/netgear_lte.py @@ -32,11 +32,11 @@ async def async_setup_platform( modem_data = hass.data[DATA_KEY].get_modem_data(config) sensors = [] - for sensortype in config[CONF_SENSORS]: - if sensortype == SENSOR_SMS: - sensors.append(SMSSensor(modem_data)) - elif sensortype == SENSOR_USAGE: - sensors.append(UsageSensor(modem_data)) + for sensor_type in config[CONF_SENSORS]: + if sensor_type == SENSOR_SMS: + sensors.append(SMSSensor(modem_data, sensor_type)) + elif sensor_type == SENSOR_USAGE: + sensors.append(UsageSensor(modem_data, sensor_type)) async_add_devices(sensors, True) @@ -46,11 +46,17 @@ class LTESensor(Entity): """Data usage sensor entity.""" modem_data = attr.ib() + sensor_type = attr.ib() async def async_update(self): """Update state.""" await self.modem_data.async_update() + @property + def unique_id(self): + """Return a unique ID like 'usage_5TG365AB0078V'.""" + return "{}_{}".format(self.sensor_type, self.modem_data.serial_number) + class SMSSensor(LTESensor): """Unread SMS sensor entity.""" diff --git a/requirements_all.txt b/requirements_all.txt index 75a563c244f7..af60587e0969 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -309,7 +309,7 @@ ephem==3.7.6.0 epson-projector==0.1.3 # homeassistant.components.netgear_lte -eternalegypt==0.0.2 +eternalegypt==0.0.3 # homeassistant.components.keyboard_remote # evdev==0.6.1 From 6f6d86c700813ffb96c3ca9847ae3009a95979a5 Mon Sep 17 00:00:00 2001 From: digiblur <3240875+digiblur@users.noreply.github.com> Date: Sat, 21 Jul 2018 10:31:07 -0500 Subject: [PATCH 046/113] Add relay addr & chan config to alarmdecoder zones (#15242) Add relay addr & chan config to alarmdecoder zones --- homeassistant/components/alarmdecoder.py | 15 +++++++++++- .../components/binary_sensor/alarmdecoder.py | 24 ++++++++++++++++--- 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/alarmdecoder.py b/homeassistant/components/alarmdecoder.py index bc7f1910803b..1377b2a6c3aa 100644 --- a/homeassistant/components/alarmdecoder.py +++ b/homeassistant/components/alarmdecoder.py @@ -34,6 +34,8 @@ CONF_ZONE_NAME = 'name' CONF_ZONE_TYPE = 'type' CONF_ZONE_RFID = 'rfid' CONF_ZONES = 'zones' +CONF_RELAY_ADDR = 'relayaddr' +CONF_RELAY_CHAN = 'relaychan' DEFAULT_DEVICE_TYPE = 'socket' DEFAULT_DEVICE_HOST = 'localhost' @@ -53,6 +55,7 @@ SIGNAL_PANEL_DISARM = 'alarmdecoder.panel_disarm' SIGNAL_ZONE_FAULT = 'alarmdecoder.zone_fault' SIGNAL_ZONE_RESTORE = 'alarmdecoder.zone_restore' SIGNAL_RFX_MESSAGE = 'alarmdecoder.rfx_message' +SIGNAL_REL_MESSAGE = 'alarmdecoder.rel_message' DEVICE_SOCKET_SCHEMA = vol.Schema({ vol.Required(CONF_DEVICE_TYPE): 'socket', @@ -71,7 +74,11 @@ ZONE_SCHEMA = vol.Schema({ vol.Required(CONF_ZONE_NAME): cv.string, vol.Optional(CONF_ZONE_TYPE, default=DEFAULT_ZONE_TYPE): vol.Any(DEVICE_CLASSES_SCHEMA), - vol.Optional(CONF_ZONE_RFID): cv.string}) + vol.Optional(CONF_ZONE_RFID): cv.string, + vol.Inclusive(CONF_RELAY_ADDR, 'relaylocation', + 'Relay address and channel must exist together'): cv.byte, + vol.Inclusive(CONF_RELAY_CHAN, 'relaylocation', + 'Relay address and channel must exist together'): cv.byte}) CONFIG_SCHEMA = vol.Schema({ DOMAIN: vol.Schema({ @@ -153,6 +160,11 @@ def setup(hass, config): hass.helpers.dispatcher.dispatcher_send( SIGNAL_ZONE_RESTORE, zone) + def handle_rel_message(sender, message): + """Handle relay message from AlarmDecoder.""" + hass.helpers.dispatcher.dispatcher_send( + SIGNAL_REL_MESSAGE, message) + controller = False if device_type == 'socket': host = device.get(CONF_DEVICE_HOST) @@ -171,6 +183,7 @@ def setup(hass, config): controller.on_zone_fault += zone_fault_callback controller.on_zone_restore += zone_restore_callback controller.on_close += handle_closed_connection + controller.on_relay_changed += handle_rel_message hass.data[DATA_AD] = controller diff --git a/homeassistant/components/binary_sensor/alarmdecoder.py b/homeassistant/components/binary_sensor/alarmdecoder.py index f0c8ec2d97ce..fcc77d474e19 100644 --- a/homeassistant/components/binary_sensor/alarmdecoder.py +++ b/homeassistant/components/binary_sensor/alarmdecoder.py @@ -11,7 +11,8 @@ from homeassistant.components.binary_sensor import BinarySensorDevice from homeassistant.components.alarmdecoder import ( ZONE_SCHEMA, CONF_ZONES, CONF_ZONE_NAME, CONF_ZONE_TYPE, CONF_ZONE_RFID, SIGNAL_ZONE_FAULT, SIGNAL_ZONE_RESTORE, - SIGNAL_RFX_MESSAGE) + SIGNAL_RFX_MESSAGE, SIGNAL_REL_MESSAGE, CONF_RELAY_ADDR, + CONF_RELAY_CHAN) DEPENDENCIES = ['alarmdecoder'] @@ -37,8 +38,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None): zone_type = device_config_data[CONF_ZONE_TYPE] zone_name = device_config_data[CONF_ZONE_NAME] zone_rfid = device_config_data.get(CONF_ZONE_RFID) + relay_addr = device_config_data.get(CONF_RELAY_ADDR) + relay_chan = device_config_data.get(CONF_RELAY_CHAN) device = AlarmDecoderBinarySensor( - zone_num, zone_name, zone_type, zone_rfid) + zone_num, zone_name, zone_type, zone_rfid, relay_addr, relay_chan) devices.append(device) add_devices(devices) @@ -49,7 +52,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class AlarmDecoderBinarySensor(BinarySensorDevice): """Representation of an AlarmDecoder binary sensor.""" - def __init__(self, zone_number, zone_name, zone_type, zone_rfid): + def __init__(self, zone_number, zone_name, zone_type, zone_rfid, + relay_addr, relay_chan): """Initialize the binary_sensor.""" self._zone_number = zone_number self._zone_type = zone_type @@ -57,6 +61,8 @@ class AlarmDecoderBinarySensor(BinarySensorDevice): self._name = zone_name self._rfid = zone_rfid self._rfstate = None + self._relay_addr = relay_addr + self._relay_chan = relay_chan @asyncio.coroutine def async_added_to_hass(self): @@ -70,6 +76,9 @@ class AlarmDecoderBinarySensor(BinarySensorDevice): self.hass.helpers.dispatcher.async_dispatcher_connect( SIGNAL_RFX_MESSAGE, self._rfx_message_callback) + self.hass.helpers.dispatcher.async_dispatcher_connect( + SIGNAL_REL_MESSAGE, self._rel_message_callback) + @property def name(self): """Return the name of the entity.""" @@ -122,3 +131,12 @@ class AlarmDecoderBinarySensor(BinarySensorDevice): if self._rfid and message and message.serial_number == self._rfid: self._rfstate = message.value self.schedule_update_ha_state() + + def _rel_message_callback(self, message): + """Update relay state.""" + if (self._relay_addr == message.address and + self._relay_chan == message.channel): + _LOGGER.debug("Relay %d:%d value:%d", message.address, + message.channel, message.value) + self._state = message.value + self.schedule_update_ha_state() From ae2ee8f00618a4081c1ac59fd512df444ec87588 Mon Sep 17 00:00:00 2001 From: Daniel Perna Date: Sun, 22 Jul 2018 00:18:50 +0200 Subject: [PATCH 047/113] Update pyhomematic, fixes #15054, #15190 (#15603) --- homeassistant/components/homematic/__init__.py | 11 +++++++---- requirements_all.txt | 2 +- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/homematic/__init__.py b/homeassistant/components/homematic/__init__.py index 6754db05f770..f737e2ad7d23 100644 --- a/homeassistant/components/homematic/__init__.py +++ b/homeassistant/components/homematic/__init__.py @@ -20,7 +20,7 @@ import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity from homeassistant.loader import bind_hass -REQUIREMENTS = ['pyhomematic==0.1.45'] +REQUIREMENTS = ['pyhomematic==0.1.46'] _LOGGER = logging.getLogger(__name__) @@ -61,7 +61,8 @@ SERVICE_SET_INSTALL_MODE = 'set_install_mode' HM_DEVICE_TYPES = { DISCOVER_SWITCHES: [ 'Switch', 'SwitchPowermeter', 'IOSwitch', 'IPSwitch', 'RFSiren', - 'IPSwitchPowermeter', 'HMWIOSwitch', 'Rain', 'EcoLogic'], + 'IPSwitchPowermeter', 'HMWIOSwitch', 'Rain', 'EcoLogic', + 'IPKeySwitchPowermeter'], DISCOVER_LIGHTS: ['Dimmer', 'KeyDimmer', 'IPKeyDimmer'], DISCOVER_SENSORS: [ 'SwitchPowermeter', 'Motion', 'MotionV2', 'RemoteMotion', 'MotionIP', @@ -71,7 +72,8 @@ HM_DEVICE_TYPES = { 'TemperatureSensor', 'CO2Sensor', 'IPSwitchPowermeter', 'HMWIOSwitch', 'FillingLevel', 'ValveDrive', 'EcoLogic', 'IPThermostatWall', 'IPSmoke', 'RFSiren', 'PresenceIP', 'IPAreaThermostat', - 'IPWeatherSensor', 'RotaryHandleSensorIP', 'IPPassageSensor'], + 'IPWeatherSensor', 'RotaryHandleSensorIP', 'IPPassageSensor', + 'IPKeySwitchPowermeter'], DISCOVER_CLIMATE: [ 'Thermostat', 'ThermostatWall', 'MAXThermostat', 'ThermostatWall2', 'MAXWallThermostat', 'IPThermostat', 'IPThermostatWall', @@ -80,7 +82,8 @@ HM_DEVICE_TYPES = { 'ShutterContact', 'Smoke', 'SmokeV2', 'Motion', 'MotionV2', 'MotionIP', 'RemoteMotion', 'WeatherSensor', 'TiltSensor', 'IPShutterContact', 'HMWIOSwitch', 'MaxShutterContact', 'Rain', - 'WiredSensor', 'PresenceIP', 'IPWeatherSensor', 'IPPassageSensor'], + 'WiredSensor', 'PresenceIP', 'IPWeatherSensor', 'IPPassageSensor', + 'SmartwareMotion'], DISCOVER_COVER: ['Blind', 'KeyBlind', 'IPKeyBlind', 'IPKeyBlindTilt'], DISCOVER_LOCKS: ['KeyMatic'] } diff --git a/requirements_all.txt b/requirements_all.txt index af60587e0969..0bb763a3faed 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -847,7 +847,7 @@ pyhik==0.1.8 pyhiveapi==0.2.14 # homeassistant.components.homematic -pyhomematic==0.1.45 +pyhomematic==0.1.46 # homeassistant.components.sensor.hydroquebec pyhydroquebec==2.2.2 From ef3a83048cd839e2dfcaa570b342af82c9274e72 Mon Sep 17 00:00:00 2001 From: Anders Melchiorsen Date: Sun, 22 Jul 2018 00:51:45 +0200 Subject: [PATCH 048/113] Throttle unavailability warnings for tplink light/switch (#15591) --- homeassistant/components/light/tplink.py | 10 ++++++---- homeassistant/components/switch/tplink.py | 10 ++++++---- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/light/tplink.py b/homeassistant/components/light/tplink.py index 669901f5b57c..9374c1418f0b 100644 --- a/homeassistant/components/light/tplink.py +++ b/homeassistant/components/light/tplink.py @@ -140,8 +140,6 @@ class TPLinkSmartBulb(Light): """Update the TP-Link Bulb's state.""" from pyHS100 import SmartDeviceException try: - self._available = True - if self._supported_features == 0: self.get_features() @@ -182,9 +180,13 @@ class TPLinkSmartBulb(Light): # device returned no daily/monthly history pass + self._available = True + except (SmartDeviceException, OSError) as ex: - _LOGGER.warning("Could not read state for %s: %s", self._name, ex) - self._available = False + if self._available: + _LOGGER.warning( + "Could not read state for %s: %s", self._name, ex) + self._available = False @property def supported_features(self): diff --git a/homeassistant/components/switch/tplink.py b/homeassistant/components/switch/tplink.py index eb54e7982a7c..0cacdfe1539d 100644 --- a/homeassistant/components/switch/tplink.py +++ b/homeassistant/components/switch/tplink.py @@ -87,8 +87,6 @@ class SmartPlugSwitch(SwitchDevice): """Update the TP-Link switch's state.""" from pyHS100 import SmartDeviceException try: - self._available = True - self._state = self.smartplug.state == \ self.smartplug.SWITCH_STATE_ON @@ -121,6 +119,10 @@ class SmartPlugSwitch(SwitchDevice): # Device returned no daily history pass + self._available = True + except (SmartDeviceException, OSError) as ex: - _LOGGER.warning("Could not read state for %s: %s", self.name, ex) - self._available = False + if self._available: + _LOGGER.warning( + "Could not read state for %s: %s", self.name, ex) + self._available = False From 33f1577dac314e13b6af9c62fc5b60aab23aef00 Mon Sep 17 00:00:00 2001 From: Jason Hu Date: Sun, 22 Jul 2018 00:49:58 -0700 Subject: [PATCH 049/113] Frontend component should auto load auth coomponent (#15606) --- homeassistant/components/frontend/__init__.py | 3 ++- tests/components/frontend/test_init.py | 12 ++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/frontend/__init__.py b/homeassistant/components/frontend/__init__.py index 68e88406ad69..fb59d6254b04 100644 --- a/homeassistant/components/frontend/__init__.py +++ b/homeassistant/components/frontend/__init__.py @@ -29,7 +29,8 @@ from homeassistant.util.yaml import load_yaml REQUIREMENTS = ['home-assistant-frontend==20180720.0'] DOMAIN = 'frontend' -DEPENDENCIES = ['api', 'websocket_api', 'http', 'system_log', 'onboarding'] +DEPENDENCIES = ['api', 'websocket_api', 'http', 'system_log', + 'auth', 'onboarding'] CONF_THEMES = 'themes' CONF_EXTRA_HTML_URL = 'extra_html_url' diff --git a/tests/components/frontend/test_init.py b/tests/components/frontend/test_init.py index 2125668facb8..4a950910809d 100644 --- a/tests/components/frontend/test_init.py +++ b/tests/components/frontend/test_init.py @@ -336,3 +336,15 @@ async def test_lovelace_ui_load_err(hass, hass_ws_client): assert msg['type'] == wapi.TYPE_RESULT assert msg['success'] is False assert msg['error']['code'] == 'load_error' + + +async def test_auth_load(mock_http_client): + """Test auth component loaded by default.""" + resp = await mock_http_client.get('/auth/providers') + assert resp.status == 200 + + +async def test_onboarding_load(mock_http_client): + """Test onboarding component loaded by default.""" + resp = await mock_http_client.get('/api/onboarding') + assert resp.status == 200 From 4de847f84e4f652cbaa5cfa02115f3429f7a42db Mon Sep 17 00:00:00 2001 From: cdce8p <30130371+cdce8p@users.noreply.github.com> Date: Sun, 22 Jul 2018 09:51:42 +0200 Subject: [PATCH 050/113] Bugfix HomeKit name and serial_number (#15600) * Bugfix HomeKit name and serial_number * Revert serial_number changes --- homeassistant/components/homekit/__init__.py | 19 ++++++++----- .../components/homekit/accessories.py | 4 +-- tests/components/homekit/test_accessories.py | 2 +- tests/components/homekit/test_homekit.py | 27 ++++++++++--------- 4 files changed, 31 insertions(+), 21 deletions(-) diff --git a/homeassistant/components/homekit/__init__.py b/homeassistant/components/homekit/__init__.py index 32d0956e8782..ad2f8b4ac6d0 100644 --- a/homeassistant/components/homekit/__init__.py +++ b/homeassistant/components/homekit/__init__.py @@ -21,9 +21,10 @@ from homeassistant.helpers.entityfilter import FILTER_SCHEMA from homeassistant.util import get_local_ip from homeassistant.util.decorator import Registry from .const import ( - CONF_AUTO_START, CONF_ENTITY_CONFIG, CONF_FEATURE_LIST, CONF_FILTER, - DEFAULT_AUTO_START, DEFAULT_PORT, DEVICE_CLASS_CO2, DEVICE_CLASS_PM25, - DOMAIN, HOMEKIT_FILE, SERVICE_HOMEKIT_START, TYPE_OUTLET, TYPE_SWITCH) + BRIDGE_NAME, CONF_AUTO_START, CONF_ENTITY_CONFIG, CONF_FEATURE_LIST, + CONF_FILTER, DEFAULT_AUTO_START, DEFAULT_PORT, DEVICE_CLASS_CO2, + DEVICE_CLASS_PM25, DOMAIN, HOMEKIT_FILE, SERVICE_HOMEKIT_START, + TYPE_OUTLET, TYPE_SWITCH) from .util import ( show_setup_message, validate_entity_config, validate_media_player_features) @@ -43,6 +44,8 @@ SWITCH_TYPES = {TYPE_OUTLET: 'Outlet', CONFIG_SCHEMA = vol.Schema({ DOMAIN: vol.All({ + vol.Optional(CONF_NAME, default=BRIDGE_NAME): + vol.All(cv.string, vol.Length(min=3, max=25)), vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, vol.Optional(CONF_IP_ADDRESS): vol.All(ipaddress.ip_address, cv.string), @@ -58,13 +61,15 @@ async def async_setup(hass, config): _LOGGER.debug('Begin setup HomeKit') conf = config[DOMAIN] + name = conf[CONF_NAME] port = conf[CONF_PORT] ip_address = conf.get(CONF_IP_ADDRESS) auto_start = conf[CONF_AUTO_START] entity_filter = conf[CONF_FILTER] entity_config = conf[CONF_ENTITY_CONFIG] - homekit = HomeKit(hass, port, ip_address, entity_filter, entity_config) + homekit = HomeKit(hass, name, port, ip_address, entity_filter, + entity_config) await hass.async_add_job(homekit.setup) if auto_start: @@ -176,9 +181,11 @@ def generate_aid(entity_id): class HomeKit(): """Class to handle all actions between HomeKit and Home Assistant.""" - def __init__(self, hass, port, ip_address, entity_filter, entity_config): + def __init__(self, hass, name, port, ip_address, entity_filter, + entity_config): """Initialize a HomeKit object.""" self.hass = hass + self._name = name self._port = port self._ip_address = ip_address self._filter = entity_filter @@ -199,7 +206,7 @@ class HomeKit(): path = self.hass.config.path(HOMEKIT_FILE) self.driver = HomeDriver(self.hass, address=ip_addr, port=self._port, persist_file=path) - self.bridge = HomeBridge(self.hass, self.driver) + self.bridge = HomeBridge(self.hass, self.driver, self._name) def add_bridge_accessory(self, state): """Try adding accessory to bridge if configured beforehand.""" diff --git a/homeassistant/components/homekit/accessories.py b/homeassistant/components/homekit/accessories.py index d4e6d48c29f2..a7e895f49e2a 100644 --- a/homeassistant/components/homekit/accessories.py +++ b/homeassistant/components/homekit/accessories.py @@ -17,7 +17,7 @@ from homeassistant.helpers.event import ( from homeassistant.util import dt as dt_util from .const import ( - BRIDGE_MODEL, BRIDGE_NAME, BRIDGE_SERIAL_NUMBER, CHAR_BATTERY_LEVEL, + BRIDGE_MODEL, BRIDGE_SERIAL_NUMBER, CHAR_BATTERY_LEVEL, CHAR_CHARGING_STATE, CHAR_STATUS_LOW_BATTERY, DEBOUNCE_TIMEOUT, MANUFACTURER, SERV_BATTERY_SERVICE) from .util import ( @@ -141,7 +141,7 @@ class HomeAccessory(Accessory): class HomeBridge(Bridge): """Adapter class for Bridge.""" - def __init__(self, hass, driver, name=BRIDGE_NAME): + def __init__(self, hass, driver, name): """Initialize a Bridge object.""" super().__init__(driver, name) self.set_info_service( diff --git a/tests/components/homekit/test_accessories.py b/tests/components/homekit/test_accessories.py index 59da90cc75bb..23706f02e754 100644 --- a/tests/components/homekit/test_accessories.py +++ b/tests/components/homekit/test_accessories.py @@ -146,7 +146,7 @@ async def test_battery_service(hass, hk_driver): def test_home_bridge(hk_driver): """Test HomeBridge class.""" - bridge = HomeBridge('hass', hk_driver) + bridge = HomeBridge('hass', hk_driver, BRIDGE_NAME) assert bridge.hass == 'hass' assert bridge.display_name == BRIDGE_NAME assert bridge.category == 2 # Category.BRIDGE diff --git a/tests/components/homekit/test_homekit.py b/tests/components/homekit/test_homekit.py index cc0370f01b18..f8afb4a49ab4 100644 --- a/tests/components/homekit/test_homekit.py +++ b/tests/components/homekit/test_homekit.py @@ -9,9 +9,10 @@ from homeassistant.components.homekit import ( STATUS_STOPPED, STATUS_WAIT) from homeassistant.components.homekit.accessories import HomeBridge from homeassistant.components.homekit.const import ( - CONF_AUTO_START, DEFAULT_PORT, DOMAIN, HOMEKIT_FILE, SERVICE_HOMEKIT_START) + CONF_AUTO_START, BRIDGE_NAME, DEFAULT_PORT, DOMAIN, HOMEKIT_FILE, + SERVICE_HOMEKIT_START) from homeassistant.const import ( - CONF_IP_ADDRESS, CONF_PORT, + CONF_NAME, CONF_IP_ADDRESS, CONF_PORT, EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP) from homeassistant.core import State from homeassistant.helpers.entityfilter import generate_filter @@ -47,7 +48,8 @@ async def test_setup_min(hass): assert await setup.async_setup_component( hass, DOMAIN, {DOMAIN: {}}) - mock_homekit.assert_any_call(hass, DEFAULT_PORT, None, ANY, {}) + mock_homekit.assert_any_call(hass, BRIDGE_NAME, DEFAULT_PORT, None, ANY, + {}) assert mock_homekit().setup.called is True # Test auto start enabled @@ -60,15 +62,16 @@ async def test_setup_min(hass): async def test_setup_auto_start_disabled(hass): """Test async_setup with auto start disabled and test service calls.""" - config = {DOMAIN: {CONF_AUTO_START: False, CONF_PORT: 11111, - CONF_IP_ADDRESS: '172.0.0.0'}} + config = {DOMAIN: {CONF_AUTO_START: False, CONF_NAME: 'Test Name', + CONF_PORT: 11111, CONF_IP_ADDRESS: '172.0.0.0'}} with patch(PATH_HOMEKIT + '.HomeKit') as mock_homekit: mock_homekit.return_value = homekit = Mock() assert await setup.async_setup_component( hass, DOMAIN, config) - mock_homekit.assert_any_call(hass, 11111, '172.0.0.0', ANY, {}) + mock_homekit.assert_any_call(hass, 'Test Name', 11111, '172.0.0.0', ANY, + {}) assert mock_homekit().setup.called is True # Test auto_start disabled @@ -96,7 +99,7 @@ async def test_setup_auto_start_disabled(hass): async def test_homekit_setup(hass, hk_driver): """Test setup of bridge and driver.""" - homekit = HomeKit(hass, DEFAULT_PORT, None, {}, {}) + homekit = HomeKit(hass, BRIDGE_NAME, DEFAULT_PORT, None, {}, {}) with patch(PATH_HOMEKIT + '.accessories.HomeDriver', return_value=hk_driver) as mock_driver, \ @@ -115,7 +118,7 @@ async def test_homekit_setup(hass, hk_driver): async def test_homekit_setup_ip_address(hass, hk_driver): """Test setup with given IP address.""" - homekit = HomeKit(hass, DEFAULT_PORT, '172.0.0.0', {}, {}) + homekit = HomeKit(hass, BRIDGE_NAME, DEFAULT_PORT, '172.0.0.0', {}, {}) with patch(PATH_HOMEKIT + '.accessories.HomeDriver', return_value=hk_driver) as mock_driver: @@ -126,7 +129,7 @@ async def test_homekit_setup_ip_address(hass, hk_driver): async def test_homekit_add_accessory(): """Add accessory if config exists and get_acc returns an accessory.""" - homekit = HomeKit('hass', None, None, lambda entity_id: True, {}) + homekit = HomeKit('hass', None, None, None, lambda entity_id: True, {}) homekit.driver = 'driver' homekit.bridge = mock_bridge = Mock() @@ -149,7 +152,7 @@ async def test_homekit_add_accessory(): async def test_homekit_entity_filter(hass): """Test the entity filter.""" entity_filter = generate_filter(['cover'], ['demo.test'], [], []) - homekit = HomeKit(hass, None, None, entity_filter, {}) + homekit = HomeKit(hass, None, None, None, entity_filter, {}) with patch(PATH_HOMEKIT + '.get_accessory') as mock_get_acc: mock_get_acc.return_value = None @@ -169,7 +172,7 @@ async def test_homekit_entity_filter(hass): async def test_homekit_start(hass, hk_driver, debounce_patcher): """Test HomeKit start method.""" pin = b'123-45-678' - homekit = HomeKit(hass, None, None, {}, {'cover.demo': {}}) + homekit = HomeKit(hass, None, None, None, {}, {'cover.demo': {}}) homekit.bridge = 'bridge' homekit.driver = hk_driver @@ -199,7 +202,7 @@ async def test_homekit_start(hass, hk_driver, debounce_patcher): async def test_homekit_stop(hass): """Test HomeKit stop method.""" - homekit = HomeKit(hass, None, None, None, None) + homekit = HomeKit(hass, None, None, None, None, None) homekit.driver = Mock() assert homekit.status == STATUS_READY From 75f40ccb067685094226888874c83e0c91c3f3ee Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 22 Jul 2018 12:10:32 +0200 Subject: [PATCH 051/113] Remove entity picture of Tuya entity (#15611) --- homeassistant/components/tuya.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/homeassistant/components/tuya.py b/homeassistant/components/tuya.py index 337071baf697..f55fe7a03b39 100644 --- a/homeassistant/components/tuya.py +++ b/homeassistant/components/tuya.py @@ -142,11 +142,6 @@ class TuyaDevice(Entity): """Return Tuya device name.""" return self.tuya.name() - @property - def entity_picture(self): - """Return the entity picture to use in the frontend, if any.""" - return self.tuya.iconurl() - @property def available(self): """Return if the device is available.""" From a38c0d6d1501dbbeb77ae9b602f68f758700ebae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Sun, 22 Jul 2018 14:37:26 +0300 Subject: [PATCH 052/113] Upgrade mypy to 0.620 (#15612) --- homeassistant/core.py | 2 +- requirements_test.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/core.py b/homeassistant/core.py index 947ab78b673b..f868c52cfb00 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -126,7 +126,7 @@ class HomeAssistant: else: self.loop = loop or asyncio.get_event_loop() - executor_opts = {'max_workers': None} + executor_opts = {'max_workers': None} # type: Dict[str, Any] if sys.version_info[:2] >= (3, 6): executor_opts['thread_name_prefix'] = 'SyncWorker' diff --git a/requirements_test.txt b/requirements_test.txt index db53699379cf..4d1f86059fcd 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -6,7 +6,7 @@ coveralls==1.2.0 flake8-docstrings==1.0.3 flake8==3.5 mock-open==1.3.1 -mypy==0.610 +mypy==0.620 pydocstyle==1.1.1 pylint==1.9.2 pytest-aiohttp==0.3.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 3de2285eae9d..aad851a93cb4 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -7,7 +7,7 @@ coveralls==1.2.0 flake8-docstrings==1.0.3 flake8==3.5 mock-open==1.3.1 -mypy==0.610 +mypy==0.620 pydocstyle==1.1.1 pylint==1.9.2 pytest-aiohttp==0.3.0 From b7c336a687356c8f4f81bbfd2f1b53af7b94c250 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Mon, 23 Jul 2018 11:16:05 +0300 Subject: [PATCH 053/113] Pylint cleanups (#15626) * Pylint 2 no-else-return fixes * Remove unneeded abstract-class-not-used pylint disable --- .../alarm_control_panel/alarmdotcom.py | 6 +-- .../components/alarm_control_panel/arlo.py | 6 +-- .../components/alarm_control_panel/canary.py | 4 +- .../alarm_control_panel/homematicip_cloud.py | 2 +- .../components/alarm_control_panel/ifttt.py | 2 +- .../components/alarm_control_panel/manual.py | 2 +- .../alarm_control_panel/manual_mqtt.py | 2 +- .../components/alarm_control_panel/mqtt.py | 2 +- .../alarm_control_panel/simplisafe.py | 2 +- homeassistant/components/alexa/smart_home.py | 2 +- homeassistant/components/august.py | 4 +- homeassistant/components/auth/__init__.py | 2 +- .../components/automation/__init__.py | 2 +- .../components/automation/homeassistant.py | 2 +- .../components/binary_sensor/netatmo.py | 2 +- .../components/binary_sensor/rachio.py | 7 ++- .../components/binary_sensor/raincloud.py | 2 +- .../components/binary_sensor/threshold.py | 4 +- .../components/binary_sensor/volvooncall.py | 2 +- .../components/binary_sensor/workday.py | 4 +- .../components/binary_sensor/xiaomi_aqara.py | 10 ++-- homeassistant/components/camera/__init__.py | 2 +- homeassistant/components/camera/amcrest.py | 2 +- homeassistant/components/camera/axis.py | 2 +- homeassistant/components/camera/netatmo.py | 2 +- homeassistant/components/camera/uvc.py | 7 ++- homeassistant/components/climate/ecobee.py | 10 ++-- homeassistant/components/climate/homematic.py | 2 +- homeassistant/components/climate/melissa.py | 34 ++++++------- homeassistant/components/climate/nest.py | 2 +- homeassistant/components/climate/netatmo.py | 2 +- homeassistant/components/climate/proliphix.py | 4 +- homeassistant/components/climate/tuya.py | 2 +- homeassistant/components/climate/venstar.py | 6 +-- homeassistant/components/climate/vera.py | 10 ++-- homeassistant/components/climate/wink.py | 6 +-- homeassistant/components/climate/zwave.py | 2 +- homeassistant/components/cover/demo.py | 4 +- homeassistant/components/cover/zwave.py | 9 ++-- .../components/deconz/config_flow.py | 2 +- .../components/device_tracker/__init__.py | 2 +- .../components/device_tracker/aruba.py | 4 +- .../device_tracker/bt_home_hub_5.py | 3 +- .../components/device_tracker/ddwrt.py | 5 +- .../components/device_tracker/geofency.py | 15 +++--- .../components/device_tracker/locative.py | 4 +- .../components/device_tracker/meraki.py | 15 +++--- .../components/device_tracker/sky_hub.py | 3 +- .../components/device_tracker/tomato.py | 2 +- .../components/google_assistant/trait.py | 4 +- homeassistant/components/graphite.py | 4 +- homeassistant/components/hassio/__init__.py | 2 +- .../components/homekit/type_thermostats.py | 3 +- homeassistant/components/homekit/util.py | 6 +-- .../homematicip_cloud/config_flow.py | 3 +- homeassistant/components/http/auth.py | 5 +- homeassistant/components/http/static.py | 5 +- homeassistant/components/hue/config_flow.py | 2 +- homeassistant/components/knx.py | 2 +- homeassistant/components/light/abode.py | 2 +- homeassistant/components/light/flux_led.py | 2 +- homeassistant/components/logbook.py | 6 +-- .../components/media_player/anthemav.py | 2 +- .../components/media_player/apple_tv.py | 14 +++--- .../components/media_player/bluesound.py | 7 ++- .../components/media_player/braviatv.py | 30 +++++------ homeassistant/components/media_player/cast.py | 14 +++--- .../components/media_player/channels.py | 2 +- homeassistant/components/media_player/cmus.py | 2 +- .../components/media_player/denonavr.py | 2 +- .../components/media_player/directv.py | 2 +- homeassistant/components/media_player/emby.py | 16 +++--- homeassistant/components/media_player/kodi.py | 2 +- .../components/media_player/liveboxplaytv.py | 2 +- .../components/media_player/mpchc.py | 2 +- homeassistant/components/media_player/mpd.py | 10 ++-- homeassistant/components/media_player/plex.py | 12 ++--- homeassistant/components/media_player/roku.py | 14 +++--- .../components/media_player/russound_rio.py | 5 +- .../components/media_player/soundtouch.py | 2 +- .../components/media_player/vizio.py | 2 +- .../components/media_player/volumio.py | 2 +- .../components/mysensors/__init__.py | 5 +- homeassistant/components/mysensors/gateway.py | 6 +-- homeassistant/components/nest/config_flow.py | 6 +-- homeassistant/components/netgear_lte.py | 2 +- homeassistant/components/notify/html5.py | 2 +- homeassistant/components/notify/rest.py | 2 +- homeassistant/components/notify/telegram.py | 6 +-- homeassistant/components/notify/twitter.py | 4 +- homeassistant/components/octoprint.py | 2 +- homeassistant/components/rachio.py | 3 +- homeassistant/components/recorder/__init__.py | 2 +- homeassistant/components/recorder/models.py | 2 +- homeassistant/components/rflink.py | 2 +- homeassistant/components/scene/lifx_cloud.py | 2 +- homeassistant/components/sensor/abode.py | 8 +-- .../components/sensor/api_streams.py | 4 +- homeassistant/components/sensor/arlo.py | 2 +- homeassistant/components/sensor/buienradar.py | 14 +++--- .../components/sensor/currencylayer.py | 3 +- homeassistant/components/sensor/darksky.py | 12 ++--- homeassistant/components/sensor/dovado.py | 9 ++-- homeassistant/components/sensor/dsmr.py | 4 +- .../components/sensor/eight_sleep.py | 2 +- .../components/sensor/fritzbox_netmonitor.py | 3 +- homeassistant/components/sensor/gpsd.py | 2 +- .../components/sensor/history_stats.py | 2 +- homeassistant/components/sensor/hive.py | 2 +- homeassistant/components/sensor/isy994.py | 6 +-- homeassistant/components/sensor/mfi.py | 6 +-- .../components/sensor/mold_indicator.py | 7 ++- homeassistant/components/sensor/moon.py | 12 ++--- homeassistant/components/sensor/nut.py | 13 +++-- homeassistant/components/sensor/octoprint.py | 3 +- .../components/sensor/openhardwaremonitor.py | 9 ++-- homeassistant/components/sensor/qnap.py | 2 +- homeassistant/components/sensor/snmp.py | 9 ++-- .../components/sensor/synologydsm.py | 2 +- homeassistant/components/sensor/tado.py | 6 +-- homeassistant/components/sensor/tahoma.py | 6 +-- .../components/sensor/tellduslive.py | 6 +-- homeassistant/components/sensor/time_date.py | 4 +- homeassistant/components/sensor/vera.py | 8 +-- .../components/sensor/volvooncall.py | 2 +- .../components/sensor/worldtidesinfo.py | 2 +- .../components/sensor/worxlandroid.py | 6 +-- .../components/sensor/xiaomi_aqara.py | 4 +- homeassistant/components/sensor/zwave.py | 4 +- homeassistant/components/switch/wemo.py | 4 +- .../components/telegram_bot/__init__.py | 2 +- .../components/telegram_bot/polling.py | 3 +- homeassistant/components/tellduslive.py | 10 ++-- homeassistant/components/volvooncall.py | 7 ++- homeassistant/components/wink/__init__.py | 7 ++- homeassistant/components/zwave/util.py | 2 +- homeassistant/config.py | 3 +- homeassistant/helpers/condition.py | 4 +- homeassistant/helpers/config_validation.py | 19 ++++--- homeassistant/helpers/data_entry_flow.py | 2 +- homeassistant/helpers/deprecation.py | 3 +- homeassistant/helpers/event.py | 6 +-- homeassistant/helpers/script.py | 4 +- homeassistant/helpers/service.py | 8 ++- homeassistant/helpers/state.py | 6 +-- homeassistant/helpers/template.py | 6 +-- homeassistant/remote.py | 6 +-- homeassistant/scripts/macos/__init__.py | 4 +- homeassistant/setup.py | 4 +- homeassistant/util/__init__.py | 2 +- homeassistant/util/dt.py | 4 +- homeassistant/util/temperature.py | 2 +- homeassistant/util/yaml.py | 7 ++- pylintrc | 2 - tests/common.py | 13 +++-- tests/components/alexa/test_smart_home.py | 2 +- tests/components/camera/test_uvc.py | 3 +- .../components/device_tracker/test_tomato.py | 4 +- .../components/device_tracker/test_xiaomi.py | 11 ++-- tests/components/light/test_hue.py | 2 +- tests/components/notify/test_group.py | 3 +- tests/components/recorder/models_original.py | 5 +- tests/components/sensor/test_radarr.py | 15 +++--- tests/components/sensor/test_sonarr.py | 19 ++++--- tests/components/switch/test_flux.py | 50 +++++++------------ tests/components/test_graphite.py | 7 ++- 166 files changed, 425 insertions(+), 490 deletions(-) diff --git a/homeassistant/components/alarm_control_panel/alarmdotcom.py b/homeassistant/components/alarm_control_panel/alarmdotcom.py index 87e85f09da0a..736334c956ae 100644 --- a/homeassistant/components/alarm_control_panel/alarmdotcom.py +++ b/homeassistant/components/alarm_control_panel/alarmdotcom.py @@ -83,7 +83,7 @@ class AlarmDotCom(alarm.AlarmControlPanel): """Return one or more digits/characters.""" if self._code is None: return None - elif isinstance(self._code, str) and re.search('^\\d+$', self._code): + if isinstance(self._code, str) and re.search('^\\d+$', self._code): return 'Number' return 'Any' @@ -92,9 +92,9 @@ class AlarmDotCom(alarm.AlarmControlPanel): """Return the state of the device.""" if self._alarm.state.lower() == 'disarmed': return STATE_ALARM_DISARMED - elif self._alarm.state.lower() == 'armed stay': + if self._alarm.state.lower() == 'armed stay': return STATE_ALARM_ARMED_HOME - elif self._alarm.state.lower() == 'armed away': + if self._alarm.state.lower() == 'armed away': return STATE_ALARM_ARMED_AWAY return STATE_UNKNOWN diff --git a/homeassistant/components/alarm_control_panel/arlo.py b/homeassistant/components/alarm_control_panel/arlo.py index 20887157cb4d..0f8913f85a01 100644 --- a/homeassistant/components/alarm_control_panel/arlo.py +++ b/homeassistant/components/alarm_control_panel/arlo.py @@ -122,10 +122,10 @@ class ArloBaseStation(AlarmControlPanel): """Convert Arlo mode to Home Assistant state.""" if mode == ARMED: return STATE_ALARM_ARMED_AWAY - elif mode == DISARMED: + if mode == DISARMED: return STATE_ALARM_DISARMED - elif mode == self._home_mode_name: + if mode == self._home_mode_name: return STATE_ALARM_ARMED_HOME - elif mode == self._away_mode_name: + if mode == self._away_mode_name: return STATE_ALARM_ARMED_AWAY return mode diff --git a/homeassistant/components/alarm_control_panel/canary.py b/homeassistant/components/alarm_control_panel/canary.py index 2e0e9994e100..3cd44dcc84ca 100644 --- a/homeassistant/components/alarm_control_panel/canary.py +++ b/homeassistant/components/alarm_control_panel/canary.py @@ -55,9 +55,9 @@ class CanaryAlarm(AlarmControlPanel): mode = location.mode if mode.name == LOCATION_MODE_AWAY: return STATE_ALARM_ARMED_AWAY - elif mode.name == LOCATION_MODE_HOME: + if mode.name == LOCATION_MODE_HOME: return STATE_ALARM_ARMED_HOME - elif mode.name == LOCATION_MODE_NIGHT: + if mode.name == LOCATION_MODE_NIGHT: return STATE_ALARM_ARMED_NIGHT return None diff --git a/homeassistant/components/alarm_control_panel/homematicip_cloud.py b/homeassistant/components/alarm_control_panel/homematicip_cloud.py index 43cb44949787..79f872951dbe 100644 --- a/homeassistant/components/alarm_control_panel/homematicip_cloud.py +++ b/homeassistant/components/alarm_control_panel/homematicip_cloud.py @@ -66,7 +66,7 @@ class HomematicipSecurityZone(HomematicipGenericDevice, AlarmControlPanel): active = self._home.get_security_zones_activation() if active == (True, True): return STATE_ALARM_ARMED_AWAY - elif active == (False, True): + if active == (False, True): return STATE_ALARM_ARMED_HOME return STATE_ALARM_DISARMED diff --git a/homeassistant/components/alarm_control_panel/ifttt.py b/homeassistant/components/alarm_control_panel/ifttt.py index 209c5367c927..9941f70a2e4a 100644 --- a/homeassistant/components/alarm_control_panel/ifttt.py +++ b/homeassistant/components/alarm_control_panel/ifttt.py @@ -128,7 +128,7 @@ class IFTTTAlarmPanel(alarm.AlarmControlPanel): """Return one or more digits/characters.""" if self._code is None: return None - elif isinstance(self._code, str) and re.search('^\\d+$', self._code): + if isinstance(self._code, str) and re.search('^\\d+$', self._code): return 'Number' return 'Any' diff --git a/homeassistant/components/alarm_control_panel/manual.py b/homeassistant/components/alarm_control_panel/manual.py index 2f2f89b9dfc5..b2b7c45d4101 100644 --- a/homeassistant/components/alarm_control_panel/manual.py +++ b/homeassistant/components/alarm_control_panel/manual.py @@ -205,7 +205,7 @@ class ManualAlarm(alarm.AlarmControlPanel): """Return one or more digits/characters.""" if self._code is None: return None - elif isinstance(self._code, str) and re.search('^\\d+$', self._code): + if isinstance(self._code, str) and re.search('^\\d+$', self._code): return 'Number' return 'Any' diff --git a/homeassistant/components/alarm_control_panel/manual_mqtt.py b/homeassistant/components/alarm_control_panel/manual_mqtt.py index e313f96fb7cb..942d0dc159a8 100644 --- a/homeassistant/components/alarm_control_panel/manual_mqtt.py +++ b/homeassistant/components/alarm_control_panel/manual_mqtt.py @@ -241,7 +241,7 @@ class ManualMQTTAlarm(alarm.AlarmControlPanel): """Return one or more digits/characters.""" if self._code is None: return None - elif isinstance(self._code, str) and re.search('^\\d+$', self._code): + if isinstance(self._code, str) and re.search('^\\d+$', self._code): return 'Number' return 'Any' diff --git a/homeassistant/components/alarm_control_panel/mqtt.py b/homeassistant/components/alarm_control_panel/mqtt.py index d6198301d765..c5408304018f 100644 --- a/homeassistant/components/alarm_control_panel/mqtt.py +++ b/homeassistant/components/alarm_control_panel/mqtt.py @@ -123,7 +123,7 @@ class MqttAlarm(MqttAvailability, alarm.AlarmControlPanel): """Return one or more digits/characters.""" if self._code is None: return None - elif isinstance(self._code, str) and re.search('^\\d+$', self._code): + if isinstance(self._code, str) and re.search('^\\d+$', self._code): return 'Number' return 'Any' diff --git a/homeassistant/components/alarm_control_panel/simplisafe.py b/homeassistant/components/alarm_control_panel/simplisafe.py index f818df4bd71a..b400a927b5e4 100644 --- a/homeassistant/components/alarm_control_panel/simplisafe.py +++ b/homeassistant/components/alarm_control_panel/simplisafe.py @@ -82,7 +82,7 @@ class SimpliSafeAlarm(AlarmControlPanel): """Return one or more digits/characters.""" if self._code is None: return None - elif isinstance(self._code, str) and re.search('^\\d+$', self._code): + if isinstance(self._code, str) and re.search('^\\d+$', self._code): return 'Number' return 'Any' diff --git a/homeassistant/components/alexa/smart_home.py b/homeassistant/components/alexa/smart_home.py index 80fa4ccb4bfb..042d878fceb0 100644 --- a/homeassistant/components/alexa/smart_home.py +++ b/homeassistant/components/alexa/smart_home.py @@ -315,7 +315,7 @@ class _AlexaLockController(_AlexaInterface): if self.entity.state == STATE_LOCKED: return 'LOCKED' - elif self.entity.state == STATE_UNLOCKED: + if self.entity.state == STATE_UNLOCKED: return 'UNLOCKED' return 'JAMMED' diff --git a/homeassistant/components/august.py b/homeassistant/components/august.py index 2a7da86c6cf0..eb25ee8fb082 100644 --- a/homeassistant/components/august.py +++ b/homeassistant/components/august.py @@ -123,9 +123,9 @@ def setup_august(hass, config, api, authenticator): discovery.load_platform(hass, component, DOMAIN, {}, config) return True - elif state == AuthenticationState.BAD_PASSWORD: + if state == AuthenticationState.BAD_PASSWORD: return False - elif state == AuthenticationState.REQUIRES_VALIDATION: + if state == AuthenticationState.REQUIRES_VALIDATION: request_configuration(hass, config, api, authenticator) return True diff --git a/homeassistant/components/auth/__init__.py b/homeassistant/components/auth/__init__.py index 435555c2e314..3eac6a370d2e 100644 --- a/homeassistant/components/auth/__init__.py +++ b/homeassistant/components/auth/__init__.py @@ -264,7 +264,7 @@ class GrantTokenView(HomeAssistantView): if grant_type == 'authorization_code': return await self._async_handle_auth_code(hass, client_id, data) - elif grant_type == 'refresh_token': + if grant_type == 'refresh_token': return await self._async_handle_refresh_token( hass, client_id, data) diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index 2a7a3887b347..8b1cd3cad845 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -297,7 +297,7 @@ class AutomationEntity(ToggleEntity): return # HomeAssistant is starting up - elif self.hass.state == CoreState.not_running: + if self.hass.state == CoreState.not_running: @asyncio.coroutine def async_enable_automation(event): """Start automation on startup.""" diff --git a/homeassistant/components/automation/homeassistant.py b/homeassistant/components/automation/homeassistant.py index 6b8ee577a09d..74cf195bc61b 100644 --- a/homeassistant/components/automation/homeassistant.py +++ b/homeassistant/components/automation/homeassistant.py @@ -44,7 +44,7 @@ def async_trigger(hass, config, action): # Automation are enabled while hass is starting up, fire right away # Check state because a config reload shouldn't trigger it. - elif hass.state == CoreState.starting: + if hass.state == CoreState.starting: hass.async_run_job(action, { 'trigger': { 'platform': 'homeassistant', diff --git a/homeassistant/components/binary_sensor/netatmo.py b/homeassistant/components/binary_sensor/netatmo.py index 7c3a3e1dd306..73a373a15ffa 100644 --- a/homeassistant/components/binary_sensor/netatmo.py +++ b/homeassistant/components/binary_sensor/netatmo.py @@ -142,7 +142,7 @@ class NetatmoBinarySensor(BinarySensorDevice): """Return the class of this sensor, from DEVICE_CLASSES.""" if self._cameratype == 'NACamera': return WELCOME_SENSOR_TYPES.get(self._sensor_name) - elif self._cameratype == 'NOC': + if self._cameratype == 'NOC': return PRESENCE_SENSOR_TYPES.get(self._sensor_name) return TAG_SENSOR_TYPES.get(self._sensor_name) diff --git a/homeassistant/components/binary_sensor/rachio.py b/homeassistant/components/binary_sensor/rachio.py index cc3079c6e530..59bf8a210647 100644 --- a/homeassistant/components/binary_sensor/rachio.py +++ b/homeassistant/components/binary_sensor/rachio.py @@ -111,11 +111,10 @@ class RachioControllerOnlineBinarySensor(RachioControllerBinarySensor): if data[KEY_STATUS] == STATUS_ONLINE: return True - elif data[KEY_STATUS] == STATUS_OFFLINE: + if data[KEY_STATUS] == STATUS_OFFLINE: return False - else: - _LOGGER.warning('"%s" reported in unknown state "%s"', self.name, - data[KEY_STATUS]) + _LOGGER.warning('"%s" reported in unknown state "%s"', self.name, + data[KEY_STATUS]) def _handle_update(self, *args, **kwargs) -> None: """Handle an update to the state of this sensor.""" diff --git a/homeassistant/components/binary_sensor/raincloud.py b/homeassistant/components/binary_sensor/raincloud.py index 288b46c23709..3cbd179154fd 100644 --- a/homeassistant/components/binary_sensor/raincloud.py +++ b/homeassistant/components/binary_sensor/raincloud.py @@ -67,6 +67,6 @@ class RainCloudBinarySensor(RainCloudEntity, BinarySensorDevice): """Return the icon of this device.""" if self._sensor_type == 'is_watering': return 'mdi:water' if self.is_on else 'mdi:water-off' - elif self._sensor_type == 'status': + if self._sensor_type == 'status': return 'mdi:pipe' if self.is_on else 'mdi:pipe-disconnected' return ICON_MAP.get(self._sensor_type) diff --git a/homeassistant/components/binary_sensor/threshold.py b/homeassistant/components/binary_sensor/threshold.py index 79c36fb2ef23..360671d1cea3 100644 --- a/homeassistant/components/binary_sensor/threshold.py +++ b/homeassistant/components/binary_sensor/threshold.py @@ -129,9 +129,9 @@ class ThresholdSensor(BinarySensorDevice): if self._threshold_lower is not None and \ self._threshold_upper is not None: return TYPE_RANGE - elif self._threshold_lower is not None: + if self._threshold_lower is not None: return TYPE_LOWER - elif self._threshold_upper is not None: + if self._threshold_upper is not None: return TYPE_UPPER @property diff --git a/homeassistant/components/binary_sensor/volvooncall.py b/homeassistant/components/binary_sensor/volvooncall.py index 39f520ddc6de..402feefa99f0 100644 --- a/homeassistant/components/binary_sensor/volvooncall.py +++ b/homeassistant/components/binary_sensor/volvooncall.py @@ -28,7 +28,7 @@ class VolvoSensor(VolvoEntity, BinarySensorDevice): val = getattr(self.vehicle, self._attribute) if self._attribute == 'bulb_failures': return bool(val) - elif self._attribute in ['doors', 'windows']: + if self._attribute in ['doors', 'windows']: return any([val[key] for key in val if 'Open' in key]) return val != 'Normal' diff --git a/homeassistant/components/binary_sensor/workday.py b/homeassistant/components/binary_sensor/workday.py index b37be3f6cb69..00d2a95e356b 100644 --- a/homeassistant/components/binary_sensor/workday.py +++ b/homeassistant/components/binary_sensor/workday.py @@ -135,7 +135,7 @@ class IsWorkdaySensor(BinarySensorDevice): """Check if given day is in the includes list.""" if day in self._workdays: return True - elif 'holiday' in self._workdays and now in self._obj_holidays: + if 'holiday' in self._workdays and now in self._obj_holidays: return True return False @@ -144,7 +144,7 @@ class IsWorkdaySensor(BinarySensorDevice): """Check if given day is in the excludes list.""" if day in self._excludes: return True - elif 'holiday' in self._excludes and now in self._obj_holidays: + if 'holiday' in self._excludes and now in self._obj_holidays: return True return False diff --git a/homeassistant/components/binary_sensor/xiaomi_aqara.py b/homeassistant/components/binary_sensor/xiaomi_aqara.py index be5d9a689d11..2a9746b4a01a 100644 --- a/homeassistant/components/binary_sensor/xiaomi_aqara.py +++ b/homeassistant/components/binary_sensor/xiaomi_aqara.py @@ -124,7 +124,7 @@ class XiaomiNatgasSensor(XiaomiBinarySensor): return False self._state = True return True - elif value == '0': + if value == '0': if self._state: self._state = False return True @@ -184,7 +184,7 @@ class XiaomiMotionSensor(XiaomiBinarySensor): return False self._state = True return True - elif value == NO_MOTION: + if value == NO_MOTION: if not self._state: return False self._state = False @@ -224,7 +224,7 @@ class XiaomiDoorSensor(XiaomiBinarySensor): return False self._state = True return True - elif value == 'close': + if value == 'close': self._open_since = 0 if self._state: self._state = False @@ -254,7 +254,7 @@ class XiaomiWaterLeakSensor(XiaomiBinarySensor): return False self._state = True return True - elif value == 'no_leak': + if value == 'no_leak': if self._state: self._state = False return True @@ -290,7 +290,7 @@ class XiaomiSmokeSensor(XiaomiBinarySensor): return False self._state = True return True - elif value == '0': + if value == '0': if self._state: self._state = False return True diff --git a/homeassistant/components/camera/__init__.py b/homeassistant/components/camera/__init__.py index 22354b51956b..e84377ee419b 100644 --- a/homeassistant/components/camera/__init__.py +++ b/homeassistant/components/camera/__init__.py @@ -342,7 +342,7 @@ class Camera(Entity): """Return the camera state.""" if self.is_recording: return STATE_RECORDING - elif self.is_streaming: + if self.is_streaming: return STATE_STREAMING return STATE_IDLE diff --git a/homeassistant/components/camera/amcrest.py b/homeassistant/components/camera/amcrest.py index 3c63e56b3191..4cb218bc0197 100644 --- a/homeassistant/components/camera/amcrest.py +++ b/homeassistant/components/camera/amcrest.py @@ -64,7 +64,7 @@ class AmcrestCam(Camera): yield from super().handle_async_mjpeg_stream(request) return - elif self._stream_source == STREAM_SOURCE_LIST['mjpeg']: + if self._stream_source == STREAM_SOURCE_LIST['mjpeg']: # stream an MJPEG image stream directly from the camera websession = async_get_clientsession(self.hass) streaming_url = self._camera.mjpeg_url(typeno=self._resolution) diff --git a/homeassistant/components/camera/axis.py b/homeassistant/components/camera/axis.py index 51c3bc89b053..5b39718939ab 100644 --- a/homeassistant/components/camera/axis.py +++ b/homeassistant/components/camera/axis.py @@ -23,7 +23,7 @@ def _get_image_url(host, port, mode): """Set the URL to get the image.""" if mode == 'mjpeg': return 'http://{}:{}/axis-cgi/mjpg/video.cgi'.format(host, port) - elif mode == 'single': + if mode == 'single': return 'http://{}:{}/axis-cgi/jpg/image.cgi'.format(host, port) diff --git a/homeassistant/components/camera/netatmo.py b/homeassistant/components/camera/netatmo.py index 34a78e19f9fc..1c7dc4c7ce00 100644 --- a/homeassistant/components/camera/netatmo.py +++ b/homeassistant/components/camera/netatmo.py @@ -105,6 +105,6 @@ class NetatmoCamera(Camera): """Return the camera model.""" if self._cameratype == "NOC": return "Presence" - elif self._cameratype == "NACamera": + if self._cameratype == "NACamera": return "Welcome" return None diff --git a/homeassistant/components/camera/uvc.py b/homeassistant/components/camera/uvc.py index e992020e2b27..b5306c31c84b 100644 --- a/homeassistant/components/camera/uvc.py +++ b/homeassistant/components/camera/uvc.py @@ -171,10 +171,9 @@ class UnifiVideoCamera(Camera): if retry: self._login() return _get_image(retry=False) - else: - _LOGGER.error( - "Unable to log into camera, unable to get snapshot") - raise + _LOGGER.error( + "Unable to log into camera, unable to get snapshot") + raise return _get_image() diff --git a/homeassistant/components/climate/ecobee.py b/homeassistant/components/climate/ecobee.py index e64c2d5000e7..718788271535 100644 --- a/homeassistant/components/climate/ecobee.py +++ b/homeassistant/components/climate/ecobee.py @@ -177,7 +177,7 @@ class Thermostat(ClimateDevice): return None if self.current_operation == STATE_HEAT: return self.thermostat['runtime']['desiredHeat'] / 10.0 - elif self.current_operation == STATE_COOL: + if self.current_operation == STATE_COOL: return self.thermostat['runtime']['desiredCool'] / 10.0 return None @@ -217,15 +217,15 @@ class Thermostat(ClimateDevice): return 'away' # A permanent hold from away climate return AWAY_MODE - elif event['holdClimateRef'] != "": + if event['holdClimateRef'] != "": # Any other hold based on climate return event['holdClimateRef'] # Any hold not based on a climate is a temp hold return TEMPERATURE_HOLD - elif event['type'].startswith('auto'): + if event['type'].startswith('auto'): # All auto modes are treated as holds return event['type'][4:].lower() - elif event['type'] == 'vacation': + if event['type'] == 'vacation': self.vacation = event['name'] return VACATION_HOLD return None @@ -317,7 +317,7 @@ class Thermostat(ClimateDevice): if hold == hold_mode: # no change, so no action required return - elif hold_mode == 'None' or hold_mode is None: + if hold_mode == 'None' or hold_mode is None: if hold == VACATION_HOLD: self.data.ecobee.delete_vacation( self.thermostat_index, self.vacation) diff --git a/homeassistant/components/climate/homematic.py b/homeassistant/components/climate/homematic.py index b8fb7a984fa3..a2725f6f3aa5 100644 --- a/homeassistant/components/climate/homematic.py +++ b/homeassistant/components/climate/homematic.py @@ -87,7 +87,7 @@ class HMThermostat(HMDevice, ClimateDevice): # HM ip etrv 2 uses the set_point_mode to say if its # auto or manual - elif not set_point_mode == -1: + if not set_point_mode == -1: code = set_point_mode # Other devices use the control_mode else: diff --git a/homeassistant/components/climate/melissa.py b/homeassistant/components/climate/melissa.py index 9c005b62dccf..a0adc12bfbfb 100644 --- a/homeassistant/components/climate/melissa.py +++ b/homeassistant/components/climate/melissa.py @@ -192,9 +192,9 @@ class MelissaClimate(ClimateDevice): """Translate Melissa states to hass states.""" if state == self._api.STATE_ON: return STATE_ON - elif state == self._api.STATE_OFF: + if state == self._api.STATE_OFF: return STATE_OFF - elif state == self._api.STATE_IDLE: + if state == self._api.STATE_IDLE: return STATE_IDLE return None @@ -202,11 +202,11 @@ class MelissaClimate(ClimateDevice): """Translate Melissa modes to hass states.""" if mode == self._api.MODE_HEAT: return STATE_HEAT - elif mode == self._api.MODE_COOL: + if mode == self._api.MODE_COOL: return STATE_COOL - elif mode == self._api.MODE_DRY: + if mode == self._api.MODE_DRY: return STATE_DRY - elif mode == self._api.MODE_FAN: + if mode == self._api.MODE_FAN: return STATE_FAN_ONLY _LOGGER.warning( "Operation mode %s could not be mapped to hass", mode) @@ -216,11 +216,11 @@ class MelissaClimate(ClimateDevice): """Translate Melissa fan modes to hass modes.""" if fan == self._api.FAN_AUTO: return STATE_AUTO - elif fan == self._api.FAN_LOW: + if fan == self._api.FAN_LOW: return SPEED_LOW - elif fan == self._api.FAN_MEDIUM: + if fan == self._api.FAN_MEDIUM: return SPEED_MEDIUM - elif fan == self._api.FAN_HIGH: + if fan == self._api.FAN_HIGH: return SPEED_HIGH _LOGGER.warning("Fan mode %s could not be mapped to hass", fan) return None @@ -229,24 +229,22 @@ class MelissaClimate(ClimateDevice): """Translate hass states to melissa modes.""" if mode == STATE_HEAT: return self._api.MODE_HEAT - elif mode == STATE_COOL: + if mode == STATE_COOL: return self._api.MODE_COOL - elif mode == STATE_DRY: + if mode == STATE_DRY: return self._api.MODE_DRY - elif mode == STATE_FAN_ONLY: + if mode == STATE_FAN_ONLY: return self._api.MODE_FAN - else: - _LOGGER.warning("Melissa have no setting for %s mode", mode) + _LOGGER.warning("Melissa have no setting for %s mode", mode) def hass_fan_to_melissa(self, fan): """Translate hass fan modes to melissa modes.""" if fan == STATE_AUTO: return self._api.FAN_AUTO - elif fan == SPEED_LOW: + if fan == SPEED_LOW: return self._api.FAN_LOW - elif fan == SPEED_MEDIUM: + if fan == SPEED_MEDIUM: return self._api.FAN_MEDIUM - elif fan == SPEED_HIGH: + if fan == SPEED_HIGH: return self._api.FAN_HIGH - else: - _LOGGER.warning("Melissa have no setting for %s fan mode", fan) + _LOGGER.warning("Melissa have no setting for %s fan mode", fan) diff --git a/homeassistant/components/climate/nest.py b/homeassistant/components/climate/nest.py index dc1f74613bcb..fa3943c3e276 100644 --- a/homeassistant/components/climate/nest.py +++ b/homeassistant/components/climate/nest.py @@ -147,7 +147,7 @@ class NestThermostat(ClimateDevice): """Return current operation ie. heat, cool, idle.""" if self._mode in [STATE_HEAT, STATE_COOL, STATE_OFF, STATE_ECO]: return self._mode - elif self._mode == NEST_MODE_HEAT_COOL: + if self._mode == NEST_MODE_HEAT_COOL: return STATE_AUTO return STATE_UNKNOWN diff --git a/homeassistant/components/climate/netatmo.py b/homeassistant/components/climate/netatmo.py index 431834151fd1..b4bed3678785 100644 --- a/homeassistant/components/climate/netatmo.py +++ b/homeassistant/components/climate/netatmo.py @@ -99,7 +99,7 @@ class NetatmoThermostat(ClimateDevice): state = self._data.thermostatdata.relay_cmd if state == 0: return STATE_IDLE - elif state == 100: + if state == 100: return STATE_HEAT @property diff --git a/homeassistant/components/climate/proliphix.py b/homeassistant/components/climate/proliphix.py index 34fcfd667b6a..9338c219fe5e 100644 --- a/homeassistant/components/climate/proliphix.py +++ b/homeassistant/components/climate/proliphix.py @@ -102,9 +102,9 @@ class ProliphixThermostat(ClimateDevice): state = self._pdp.hvac_state if state in (1, 2): return STATE_IDLE - elif state == 3: + if state == 3: return STATE_HEAT - elif state == 6: + if state == 6: return STATE_COOL def set_temperature(self, **kwargs): diff --git a/homeassistant/components/climate/tuya.py b/homeassistant/components/climate/tuya.py index 9a114c243b68..19267d693a04 100644 --- a/homeassistant/components/climate/tuya.py +++ b/homeassistant/components/climate/tuya.py @@ -87,7 +87,7 @@ class TuyaClimateDevice(TuyaDevice, ClimateDevice): unit = self.tuya.temperature_unit() if unit == 'CELSIUS': return TEMP_CELSIUS - elif unit == 'FAHRENHEIT': + if unit == 'FAHRENHEIT': return TEMP_FAHRENHEIT return TEMP_CELSIUS diff --git a/homeassistant/components/climate/venstar.py b/homeassistant/components/climate/venstar.py index c2b82e1cc844..4bacf64cf9e3 100644 --- a/homeassistant/components/climate/venstar.py +++ b/homeassistant/components/climate/venstar.py @@ -152,9 +152,9 @@ class VenstarThermostat(ClimateDevice): """Return current operation ie. heat, cool, idle.""" if self._client.mode == self._client.MODE_HEAT: return STATE_HEAT - elif self._client.mode == self._client.MODE_COOL: + if self._client.mode == self._client.MODE_COOL: return STATE_COOL - elif self._client.mode == self._client.MODE_AUTO: + if self._client.mode == self._client.MODE_AUTO: return STATE_AUTO return STATE_OFF @@ -178,7 +178,7 @@ class VenstarThermostat(ClimateDevice): """Return the target temperature we try to reach.""" if self._client.mode == self._client.MODE_HEAT: return self._client.heattemp - elif self._client.mode == self._client.MODE_COOL: + if self._client.mode == self._client.MODE_COOL: return self._client.cooltemp return None diff --git a/homeassistant/components/climate/vera.py b/homeassistant/components/climate/vera.py index 4deb4d9ea2ea..0f89b15e5a18 100644 --- a/homeassistant/components/climate/vera.py +++ b/homeassistant/components/climate/vera.py @@ -55,11 +55,11 @@ class VeraThermostat(VeraDevice, ClimateDevice): mode = self.vera_device.get_hvac_mode() if mode == 'HeatOn': return OPERATION_LIST[0] # heat - elif mode == 'CoolOn': + if mode == 'CoolOn': return OPERATION_LIST[1] # cool - elif mode == 'AutoChangeOver': + if mode == 'AutoChangeOver': return OPERATION_LIST[2] # auto - elif mode == 'Off': + if mode == 'Off': return OPERATION_LIST[3] # off return 'Off' @@ -74,9 +74,9 @@ class VeraThermostat(VeraDevice, ClimateDevice): mode = self.vera_device.get_fan_mode() if mode == "ContinuousOn": return FAN_OPERATION_LIST[0] # on - elif mode == "Auto": + if mode == "Auto": return FAN_OPERATION_LIST[1] # auto - elif mode == "PeriodicOn": + if mode == "PeriodicOn": return FAN_OPERATION_LIST[2] # cycle return "Auto" diff --git a/homeassistant/components/climate/wink.py b/homeassistant/components/climate/wink.py index 12a6960f8334..15e555db8b9f 100644 --- a/homeassistant/components/climate/wink.py +++ b/homeassistant/components/climate/wink.py @@ -224,7 +224,7 @@ class WinkThermostat(WinkDevice, ClimateDevice): if self.current_operation != STATE_AUTO and not self.is_away_mode_on: if self.current_operation == STATE_COOL: return self.wink.current_max_set_point() - elif self.current_operation == STATE_HEAT: + if self.current_operation == STATE_HEAT: return self.wink.current_min_set_point() return None @@ -311,7 +311,7 @@ class WinkThermostat(WinkDevice, ClimateDevice): """Return whether the fan is on.""" if self.wink.current_fan_mode() == 'on': return STATE_ON - elif self.wink.current_fan_mode() == 'auto': + if self.wink.current_fan_mode() == 'auto': return STATE_AUTO # No Fan available so disable slider return None @@ -483,7 +483,7 @@ class WinkAC(WinkDevice, ClimateDevice): speed = self.wink.current_fan_speed() if speed <= 0.33: return SPEED_LOW - elif speed <= 0.66: + if speed <= 0.66: return SPEED_MEDIUM return SPEED_HIGH diff --git a/homeassistant/components/climate/zwave.py b/homeassistant/components/climate/zwave.py index b1dcacb56543..f87f2e83f5da 100644 --- a/homeassistant/components/climate/zwave.py +++ b/homeassistant/components/climate/zwave.py @@ -186,7 +186,7 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice): """Return the unit of measurement.""" if self._unit == 'C': return TEMP_CELSIUS - elif self._unit == 'F': + if self._unit == 'F': return TEMP_FAHRENHEIT return self._unit diff --git a/homeassistant/components/cover/demo.py b/homeassistant/components/cover/demo.py index b1533bd68c8a..b81ac4e45e1a 100644 --- a/homeassistant/components/cover/demo.py +++ b/homeassistant/components/cover/demo.py @@ -97,7 +97,7 @@ class DemoCover(CoverDevice): """Close the cover.""" if self._position == 0: return - elif self._position is None: + if self._position is None: self._closed = True self.schedule_update_ha_state() return @@ -119,7 +119,7 @@ class DemoCover(CoverDevice): """Open the cover.""" if self._position == 100: return - elif self._position is None: + if self._position is None: self._closed = False self.schedule_update_ha_state() return diff --git a/homeassistant/components/cover/zwave.py b/homeassistant/components/cover/zwave.py index d5de8863543b..8c8c88ecb87f 100644 --- a/homeassistant/components/cover/zwave.py +++ b/homeassistant/components/cover/zwave.py @@ -27,11 +27,10 @@ def get_device(hass, values, node_config, **kwargs): zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL and values.primary.index == 0): return ZwaveRollershutter(hass, values, invert_buttons) - elif (values.primary.command_class == - zwave.const.COMMAND_CLASS_SWITCH_BINARY): + if values.primary.command_class == zwave.const.COMMAND_CLASS_SWITCH_BINARY: return ZwaveGarageDoorSwitch(values) - elif (values.primary.command_class == - zwave.const.COMMAND_CLASS_BARRIER_OPERATOR): + if values.primary.command_class == \ + zwave.const.COMMAND_CLASS_BARRIER_OPERATOR: return ZwaveGarageDoorBarrier(values) return None @@ -84,7 +83,7 @@ class ZwaveRollershutter(zwave.ZWaveDeviceEntity, CoverDevice): if self._current_position is not None: if self._current_position <= 5: return 0 - elif self._current_position >= 95: + if self._current_position >= 95: return 100 return self._current_position diff --git a/homeassistant/components/deconz/config_flow.py b/homeassistant/components/deconz/config_flow.py index b67d32508be9..a6f675062275 100644 --- a/homeassistant/components/deconz/config_flow.py +++ b/homeassistant/components/deconz/config_flow.py @@ -57,7 +57,7 @@ class DeconzFlowHandler(data_entry_flow.FlowHandler): if len(self.bridges) == 1: self.deconz_config = self.bridges[0] return await self.async_step_link() - elif len(self.bridges) > 1: + if len(self.bridges) > 1: hosts = [] for bridge in self.bridges: hosts.append(bridge[CONF_HOST]) diff --git a/homeassistant/components/device_tracker/__init__.py b/homeassistant/components/device_tracker/__init__.py index 391f36ad6234..74cb0a77fef5 100644 --- a/homeassistant/components/device_tracker/__init__.py +++ b/homeassistant/components/device_tracker/__init__.py @@ -537,7 +537,7 @@ class Device(Entity): """ if not self.last_seen: return - elif self.location_name: + if self.location_name: self._state = self.location_name elif self.gps is not None and self.source_type == SOURCE_TYPE_GPS: zone_state = async_active_zone( diff --git a/homeassistant/components/device_tracker/aruba.py b/homeassistant/components/device_tracker/aruba.py index 61eee99e7216..142842b12d28 100644 --- a/homeassistant/components/device_tracker/aruba.py +++ b/homeassistant/components/device_tracker/aruba.py @@ -94,10 +94,10 @@ class ArubaDeviceScanner(DeviceScanner): if query == 1: _LOGGER.error("Timeout") return - elif query == 2: + if query == 2: _LOGGER.error("Unexpected response from router") return - elif query == 3: + if query == 3: ssh.sendline('yes') ssh.expect('password:') elif query == 4: diff --git a/homeassistant/components/device_tracker/bt_home_hub_5.py b/homeassistant/components/device_tracker/bt_home_hub_5.py index 707850d2215c..93bc9270650f 100644 --- a/homeassistant/components/device_tracker/bt_home_hub_5.py +++ b/homeassistant/components/device_tracker/bt_home_hub_5.py @@ -94,8 +94,7 @@ def _get_homehub_data(url): return if response.status_code == 200: return _parse_homehub_response(response.text) - else: - _LOGGER.error("Invalid response from Home Hub: %s", response) + _LOGGER.error("Invalid response from Home Hub: %s", response) def _parse_homehub_response(data_str): diff --git a/homeassistant/components/device_tracker/ddwrt.py b/homeassistant/components/device_tracker/ddwrt.py index 3e17fdd33294..539d4fde5efe 100644 --- a/homeassistant/components/device_tracker/ddwrt.py +++ b/homeassistant/components/device_tracker/ddwrt.py @@ -131,13 +131,12 @@ class DdWrtDeviceScanner(DeviceScanner): return if response.status_code == 200: return _parse_ddwrt_response(response.text) - elif response.status_code == 401: + if response.status_code == 401: # Authentication error _LOGGER.exception( "Failed to authenticate, check your username and password") return - else: - _LOGGER.error("Invalid response from DD-WRT: %s", response) + _LOGGER.error("Invalid response from DD-WRT: %s", response) def _parse_ddwrt_response(data_str): diff --git a/homeassistant/components/device_tracker/geofency.py b/homeassistant/components/device_tracker/geofency.py index adb5c6f6d28d..7231c5127bef 100644 --- a/homeassistant/components/device_tracker/geofency.py +++ b/homeassistant/components/device_tracker/geofency.py @@ -70,16 +70,15 @@ class GeofencyView(HomeAssistantView): if self._is_mobile_beacon(data): return (yield from self._set_location(hass, data, None)) + if data['entry'] == LOCATION_ENTRY: + location_name = data['name'] else: - if data['entry'] == LOCATION_ENTRY: - location_name = data['name'] - else: - location_name = STATE_NOT_HOME - if ATTR_CURRENT_LATITUDE in data: - data[ATTR_LATITUDE] = data[ATTR_CURRENT_LATITUDE] - data[ATTR_LONGITUDE] = data[ATTR_CURRENT_LONGITUDE] + location_name = STATE_NOT_HOME + if ATTR_CURRENT_LATITUDE in data: + data[ATTR_LATITUDE] = data[ATTR_CURRENT_LATITUDE] + data[ATTR_LONGITUDE] = data[ATTR_CURRENT_LONGITUDE] - return (yield from self._set_location(hass, data, location_name)) + return (yield from self._set_location(hass, data, location_name)) @staticmethod def _validate_data(data): diff --git a/homeassistant/components/device_tracker/locative.py b/homeassistant/components/device_tracker/locative.py index aee584aa9531..354d3b0980cb 100644 --- a/homeassistant/components/device_tracker/locative.py +++ b/homeassistant/components/device_tracker/locative.py @@ -84,7 +84,7 @@ class LocativeView(HomeAssistantView): gps=gps_location)) return 'Setting location to {}'.format(location_name) - elif direction == 'exit': + if direction == 'exit': current_state = hass.states.get( '{}.{}'.format(DOMAIN, device)) @@ -102,7 +102,7 @@ class LocativeView(HomeAssistantView): return 'Ignoring exit from {} (already in {})'.format( location_name, current_state) - elif direction == 'test': + if direction == 'test': # In the app, a test message can be sent. Just return something to # the user to let them know that it works. return 'Received test message.' diff --git a/homeassistant/components/device_tracker/meraki.py b/homeassistant/components/device_tracker/meraki.py index 9bbc6bf9ffed..c996b7e643bf 100644 --- a/homeassistant/components/device_tracker/meraki.py +++ b/homeassistant/components/device_tracker/meraki.py @@ -74,17 +74,16 @@ class MerakiView(HomeAssistantView): _LOGGER.error("Invalid Secret received from Meraki") return self.json_message('Invalid secret', HTTP_UNPROCESSABLE_ENTITY) - elif data['version'] != VERSION: + if data['version'] != VERSION: _LOGGER.error("Invalid API version: %s", data['version']) return self.json_message('Invalid version', HTTP_UNPROCESSABLE_ENTITY) - else: - _LOGGER.debug('Valid Secret') - if data['type'] not in ('DevicesSeen', 'BluetoothDevicesSeen'): - _LOGGER.error("Unknown Device %s", data['type']) - return self.json_message('Invalid device type', - HTTP_UNPROCESSABLE_ENTITY) - _LOGGER.debug("Processing %s", data['type']) + _LOGGER.debug('Valid Secret') + if data['type'] not in ('DevicesSeen', 'BluetoothDevicesSeen'): + _LOGGER.error("Unknown Device %s", data['type']) + return self.json_message('Invalid device type', + HTTP_UNPROCESSABLE_ENTITY) + _LOGGER.debug("Processing %s", data['type']) if not data["data"]["observations"]: _LOGGER.debug("No observations found") return diff --git a/homeassistant/components/device_tracker/sky_hub.py b/homeassistant/components/device_tracker/sky_hub.py index 0c289ce9a82e..deab486ec6e4 100644 --- a/homeassistant/components/device_tracker/sky_hub.py +++ b/homeassistant/components/device_tracker/sky_hub.py @@ -91,8 +91,7 @@ def _get_skyhub_data(url): return if response.status_code == 200: return _parse_skyhub_response(response.text) - else: - _LOGGER.error("Invalid response from Sky Hub: %s", response) + _LOGGER.error("Invalid response from Sky Hub: %s", response) def _parse_skyhub_response(data_str): diff --git a/homeassistant/components/device_tracker/tomato.py b/homeassistant/components/device_tracker/tomato.py index 12e1cb0099ad..718adad42123 100644 --- a/homeassistant/components/device_tracker/tomato.py +++ b/homeassistant/components/device_tracker/tomato.py @@ -107,7 +107,7 @@ class TomatoDeviceScanner(DeviceScanner): json.loads(value.replace("'", '"')) return True - elif response.status_code == 401: + if response.status_code == 401: # Authentication error _LOGGER.exception(( "Failed to authenticate, " diff --git a/homeassistant/components/google_assistant/trait.py b/homeassistant/components/google_assistant/trait.py index 2f60f226042b..74ee55c5e933 100644 --- a/homeassistant/components/google_assistant/trait.py +++ b/homeassistant/components/google_assistant/trait.py @@ -106,9 +106,9 @@ class BrightnessTrait(_Trait): """Test if state is supported.""" if domain == light.DOMAIN: return features & light.SUPPORT_BRIGHTNESS - elif domain == cover.DOMAIN: + if domain == cover.DOMAIN: return features & cover.SUPPORT_SET_POSITION - elif domain == media_player.DOMAIN: + if domain == media_player.DOMAIN: return features & media_player.SUPPORT_VOLUME_SET return False diff --git a/homeassistant/components/graphite.py b/homeassistant/components/graphite.py index e4626d0f016e..2b768bc37868 100644 --- a/homeassistant/components/graphite.py +++ b/homeassistant/components/graphite.py @@ -137,8 +137,8 @@ class GraphiteFeeder(threading.Thread): _LOGGER.debug("Event processing thread stopped") self._queue.task_done() return - elif (event.event_type == EVENT_STATE_CHANGED and - event.data.get('new_state')): + if event.event_type == EVENT_STATE_CHANGED and \ + event.data.get('new_state'): _LOGGER.debug("Processing STATE_CHANGED event for %s", event.data['entity_id']) try: diff --git a/homeassistant/components/hassio/__init__.py b/homeassistant/components/hassio/__init__.py index 6ab86435371f..13bc6e9c5585 100644 --- a/homeassistant/components/hassio/__init__.py +++ b/homeassistant/components/hassio/__init__.py @@ -140,7 +140,7 @@ def async_check_config(hass): if not result: return "Hass.io config check API error" - elif result['result'] == "error": + if result['result'] == "error": return result['message'] return None diff --git a/homeassistant/components/homekit/type_thermostats.py b/homeassistant/components/homekit/type_thermostats.py index 73a29990fba0..8517122f6a88 100644 --- a/homeassistant/components/homekit/type_thermostats.py +++ b/homeassistant/components/homekit/type_thermostats.py @@ -124,8 +124,7 @@ class Thermostat(HomeAccessory): if hass_value == STATE_OFF: self.hass.services.call(DOMAIN, SERVICE_TURN_OFF, params) return - else: - self.hass.services.call(DOMAIN, SERVICE_TURN_ON, params) + self.hass.services.call(DOMAIN, SERVICE_TURN_ON, params) params = {ATTR_ENTITY_ID: self.entity_id, ATTR_OPERATION_MODE: hass_value} self.hass.services.call(DOMAIN, SERVICE_SET_OPERATION_MODE, params) diff --git a/homeassistant/components/homekit/util.py b/homeassistant/components/homekit/util.py index c2f8951c8d83..23a907d43f73 100644 --- a/homeassistant/components/homekit/util.py +++ b/homeassistant/components/homekit/util.py @@ -142,10 +142,10 @@ def density_to_air_quality(density): """Map PM2.5 density to HomeKit AirQuality level.""" if density <= 35: return 1 - elif density <= 75: + if density <= 75: return 2 - elif density <= 115: + if density <= 115: return 3 - elif density <= 150: + if density <= 150: return 4 return 5 diff --git a/homeassistant/components/homematicip_cloud/config_flow.py b/homeassistant/components/homematicip_cloud/config_flow.py index 9e5356d914a5..3be89172e27f 100644 --- a/homeassistant/components/homematicip_cloud/config_flow.py +++ b/homeassistant/components/homematicip_cloud/config_flow.py @@ -70,8 +70,7 @@ class HomematicipCloudFlowHandler(data_entry_flow.FlowHandler): HMIPC_NAME: self.auth.config.get(HMIPC_NAME) }) return self.async_abort(reason='conection_aborted') - else: - errors['base'] = 'press_the_button' + errors['base'] = 'press_the_button' return self.async_show_form(step_id='link', errors=errors) diff --git a/homeassistant/components/http/auth.py b/homeassistant/components/http/auth.py index 2cc62dce38ed..4c71504104e4 100644 --- a/homeassistant/components/http/auth.py +++ b/homeassistant/components/http/auth.py @@ -109,7 +109,7 @@ async def async_validate_auth_header(request, api_password=None): request['hass_user'] = access_token.refresh_token.user return True - elif auth_type == 'Basic' and api_password is not None: + if auth_type == 'Basic' and api_password is not None: decoded = base64.b64decode(auth_val).decode('utf-8') try: username, password = decoded.split(':', 1) @@ -123,5 +123,4 @@ async def async_validate_auth_header(request, api_password=None): return hmac.compare_digest(api_password.encode('utf-8'), password.encode('utf-8')) - else: - return False + return False diff --git a/homeassistant/components/http/static.py b/homeassistant/components/http/static.py index cd07ab6df691..8b28a7cf2880 100644 --- a/homeassistant/components/http/static.py +++ b/homeassistant/components/http/static.py @@ -31,10 +31,9 @@ class CachingStaticResource(StaticResource): if filepath.is_dir(): return await super()._handle(request) - elif filepath.is_file(): + if filepath.is_file(): return CachingFileResponse(filepath, chunk_size=self._chunk_size) - else: - raise HTTPNotFound + raise HTTPNotFound # pylint: disable=too-many-ancestors diff --git a/homeassistant/components/hue/config_flow.py b/homeassistant/components/hue/config_flow.py index af67a594495e..a7fe3ff04e0a 100644 --- a/homeassistant/components/hue/config_flow.py +++ b/homeassistant/components/hue/config_flow.py @@ -84,7 +84,7 @@ class HueFlowHandler(data_entry_flow.FlowHandler): reason='all_configured' ) - elif len(hosts) == 1: + if len(hosts) == 1: self.host = hosts[0] return await self.async_step_link() diff --git a/homeassistant/components/knx.py b/homeassistant/components/knx.py index f8db21118ecd..c6101230031c 100644 --- a/homeassistant/components/knx.py +++ b/homeassistant/components/knx.py @@ -172,7 +172,7 @@ class KNXModule: """Return the connection_config.""" if CONF_KNX_TUNNELING in self.config[DOMAIN]: return self.connection_config_tunneling() - elif CONF_KNX_ROUTING in self.config[DOMAIN]: + if CONF_KNX_ROUTING in self.config[DOMAIN]: return self.connection_config_routing() return self.connection_config_auto() diff --git a/homeassistant/components/light/abode.py b/homeassistant/components/light/abode.py index 8b7e09d86bce..431f5d12ff0d 100644 --- a/homeassistant/components/light/abode.py +++ b/homeassistant/components/light/abode.py @@ -88,7 +88,7 @@ class AbodeLight(AbodeDevice, Light): """Flag supported features.""" if self._device.is_dimmable and self._device.has_color: return SUPPORT_BRIGHTNESS | SUPPORT_COLOR - elif self._device.is_dimmable: + if self._device.is_dimmable: return SUPPORT_BRIGHTNESS return 0 diff --git a/homeassistant/components/light/flux_led.py b/homeassistant/components/light/flux_led.py index c5cd9a8c4fdd..2b53fb650540 100644 --- a/homeassistant/components/light/flux_led.py +++ b/homeassistant/components/light/flux_led.py @@ -245,7 +245,7 @@ class FluxLight(Light): return # Effect selection - elif effect in EFFECT_MAP: + if effect in EFFECT_MAP: self._bulb.setPresetPattern(EFFECT_MAP[effect], 50) return diff --git a/homeassistant/components/logbook.py b/homeassistant/components/logbook.py index b9970e3466e8..c4fcf53a9c1f 100644 --- a/homeassistant/components/logbook.py +++ b/homeassistant/components/logbook.py @@ -380,16 +380,16 @@ def _entry_message_from_state(domain, state): return 'is away' return 'is at {}'.format(state.state) - elif domain == 'sun': + if domain == 'sun': if state.state == sun.STATE_ABOVE_HORIZON: return 'has risen' return 'has set' - elif state.state == STATE_ON: + if state.state == STATE_ON: # Future: combine groups and its entity entries ? return "turned on" - elif state.state == STATE_OFF: + if state.state == STATE_OFF: return "turned off" return "changed to {}".format(state.state) diff --git a/homeassistant/components/media_player/anthemav.py b/homeassistant/components/media_player/anthemav.py index 474751c2574f..a74629917b34 100644 --- a/homeassistant/components/media_player/anthemav.py +++ b/homeassistant/components/media_player/anthemav.py @@ -100,7 +100,7 @@ class AnthemAVR(MediaPlayerDevice): if pwrstate is True: return STATE_ON - elif pwrstate is False: + if pwrstate is False: return STATE_OFF return STATE_UNKNOWN diff --git a/homeassistant/components/media_player/apple_tv.py b/homeassistant/components/media_player/apple_tv.py index 97b9b64c7cb0..d4a7ad198072 100644 --- a/homeassistant/components/media_player/apple_tv.py +++ b/homeassistant/components/media_player/apple_tv.py @@ -103,11 +103,11 @@ class AppleTvDevice(MediaPlayerDevice): if state in (const.PLAY_STATE_IDLE, const.PLAY_STATE_NO_MEDIA, const.PLAY_STATE_LOADING): return STATE_IDLE - elif state == const.PLAY_STATE_PLAYING: + if state == const.PLAY_STATE_PLAYING: return STATE_PLAYING - elif state in (const.PLAY_STATE_PAUSED, - const.PLAY_STATE_FAST_FORWARD, - const.PLAY_STATE_FAST_BACKWARD): + if state in (const.PLAY_STATE_PAUSED, + const.PLAY_STATE_FAST_FORWARD, + const.PLAY_STATE_FAST_BACKWARD): # Catch fast forward/backward here so "play" is default action return STATE_PAUSED return STATE_STANDBY # Bad or unknown state? @@ -140,9 +140,9 @@ class AppleTvDevice(MediaPlayerDevice): media_type = self._playing.media_type if media_type == const.MEDIA_TYPE_VIDEO: return MEDIA_TYPE_VIDEO - elif media_type == const.MEDIA_TYPE_MUSIC: + if media_type == const.MEDIA_TYPE_MUSIC: return MEDIA_TYPE_MUSIC - elif media_type == const.MEDIA_TYPE_TV: + if media_type == const.MEDIA_TYPE_TV: return MEDIA_TYPE_TVSHOW @property @@ -221,7 +221,7 @@ class AppleTvDevice(MediaPlayerDevice): state = self.state if state == STATE_PAUSED: return self.atv.remote_control.play() - elif state == STATE_PLAYING: + if state == STATE_PLAYING: return self.atv.remote_control.pause() def async_media_play(self): diff --git a/homeassistant/components/media_player/bluesound.py b/homeassistant/components/media_player/bluesound.py index c96889f4fe4f..a6b345b1d3bc 100644 --- a/homeassistant/components/media_player/bluesound.py +++ b/homeassistant/components/media_player/bluesound.py @@ -530,7 +530,7 @@ class BluesoundPlayer(MediaPlayerDevice): status = self._status.get('state', None) if status in ('pause', 'stop'): return STATE_PAUSED - elif status in ('stream', 'play'): + if status in ('stream', 'play'): return STATE_PLAYING return STATE_IDLE @@ -974,6 +974,5 @@ class BluesoundPlayer(MediaPlayerDevice): if volume > 0: self._lastvol = volume return await self.send_bluesound_command('Volume?level=0') - else: - return await self.send_bluesound_command( - 'Volume?level=' + str(float(self._lastvol) * 100)) + return await self.send_bluesound_command( + 'Volume?level=' + str(float(self._lastvol) * 100)) diff --git a/homeassistant/components/media_player/braviatv.py b/homeassistant/components/media_player/braviatv.py index 464baed16868..07a379db45cd 100644 --- a/homeassistant/components/media_player/braviatv.py +++ b/homeassistant/components/media_player/braviatv.py @@ -88,23 +88,23 @@ def setup_bravia(config, pin, hass, add_devices): if pin is None: request_configuration(config, hass, add_devices) return - else: - mac = _get_mac_address(host) - if mac is not None: - mac = mac.decode('utf8') - # If we came here and configuring this host, mark as done - if host in _CONFIGURING: - request_id = _CONFIGURING.pop(host) - configurator = hass.components.configurator - configurator.request_done(request_id) - _LOGGER.info("Discovery configuration done") - # Save config - save_json( - hass.config.path(BRAVIA_CONFIG_FILE), - {host: {'pin': pin, 'host': host, 'mac': mac}}) + mac = _get_mac_address(host) + if mac is not None: + mac = mac.decode('utf8') + # If we came here and configuring this host, mark as done + if host in _CONFIGURING: + request_id = _CONFIGURING.pop(host) + configurator = hass.components.configurator + configurator.request_done(request_id) + _LOGGER.info("Discovery configuration done") - add_devices([BraviaTVDevice(host, mac, name, pin)]) + # Save config + save_json( + hass.config.path(BRAVIA_CONFIG_FILE), + {host: {'pin': pin, 'host': host, 'mac': mac}}) + + add_devices([BraviaTVDevice(host, mac, name, pin)]) def request_configuration(config, hass, add_devices): diff --git a/homeassistant/components/media_player/cast.py b/homeassistant/components/media_player/cast.py index 32ceadf248fe..099b365c50b1 100644 --- a/homeassistant/components/media_player/cast.py +++ b/homeassistant/components/media_player/cast.py @@ -499,13 +499,13 @@ class CastDevice(MediaPlayerDevice): """Return the state of the player.""" if self.media_status is None: return None - elif self.media_status.player_is_playing: + if self.media_status.player_is_playing: return STATE_PLAYING - elif self.media_status.player_is_paused: + if self.media_status.player_is_paused: return STATE_PAUSED - elif self.media_status.player_is_idle: + if self.media_status.player_is_idle: return STATE_IDLE - elif self._chromecast is not None and self._chromecast.is_idle: + if self._chromecast is not None and self._chromecast.is_idle: return STATE_OFF return None @@ -534,11 +534,11 @@ class CastDevice(MediaPlayerDevice): """Content type of current playing media.""" if self.media_status is None: return None - elif self.media_status.media_is_tvshow: + if self.media_status.media_is_tvshow: return MEDIA_TYPE_TVSHOW - elif self.media_status.media_is_movie: + if self.media_status.media_is_movie: return MEDIA_TYPE_MOVIE - elif self.media_status.media_is_musictrack: + if self.media_status.media_is_musictrack: return MEDIA_TYPE_MUSIC return None diff --git a/homeassistant/components/media_player/channels.py b/homeassistant/components/media_player/channels.py index 41713e0c5bc6..6ccc60617031 100644 --- a/homeassistant/components/media_player/channels.py +++ b/homeassistant/components/media_player/channels.py @@ -217,7 +217,7 @@ class ChannelsPlayer(MediaPlayerDevice): """Image url of current playing media.""" if self.now_playing_image_url: return self.now_playing_image_url - elif self.channel_image_url: + if self.channel_image_url: return self.channel_image_url return 'https://getchannels.com/assets/img/icon-1024.png' diff --git a/homeassistant/components/media_player/cmus.py b/homeassistant/components/media_player/cmus.py index 0758b5f3058e..978a1088aa6b 100644 --- a/homeassistant/components/media_player/cmus.py +++ b/homeassistant/components/media_player/cmus.py @@ -91,7 +91,7 @@ class CmusDevice(MediaPlayerDevice): """Return the media state.""" if self.status.get('status') == 'playing': return STATE_PLAYING - elif self.status.get('status') == 'paused': + if self.status.get('status') == 'paused': return STATE_PAUSED return STATE_OFF diff --git a/homeassistant/components/media_player/denonavr.py b/homeassistant/components/media_player/denonavr.py index 2b2b9eb5c281..654374de08a0 100644 --- a/homeassistant/components/media_player/denonavr.py +++ b/homeassistant/components/media_player/denonavr.py @@ -261,7 +261,7 @@ class DenonDevice(MediaPlayerDevice): """Title of current playing media.""" if self._current_source not in self._receiver.playing_func_list: return self._current_source - elif self._title is not None: + if self._title is not None: return self._title return self._frequency diff --git a/homeassistant/components/media_player/directv.py b/homeassistant/components/media_player/directv.py index 0adb02b6a65e..89547892550b 100644 --- a/homeassistant/components/media_player/directv.py +++ b/homeassistant/components/media_player/directv.py @@ -140,7 +140,7 @@ class DirecTvDevice(MediaPlayerDevice): """Return the title of current episode of TV show.""" if self._is_standby: return None - elif 'episodeTitle' in self._current: + if 'episodeTitle' in self._current: return self._current['episodeTitle'] return None diff --git a/homeassistant/components/media_player/emby.py b/homeassistant/components/media_player/emby.py index 4f9a40192682..1dfb19a33bee 100644 --- a/homeassistant/components/media_player/emby.py +++ b/homeassistant/components/media_player/emby.py @@ -206,11 +206,11 @@ class EmbyDevice(MediaPlayerDevice): state = self.device.state if state == 'Paused': return STATE_PAUSED - elif state == 'Playing': + if state == 'Playing': return STATE_PLAYING - elif state == 'Idle': + if state == 'Idle': return STATE_IDLE - elif state == 'Off': + if state == 'Off': return STATE_OFF @property @@ -230,15 +230,15 @@ class EmbyDevice(MediaPlayerDevice): media_type = self.device.media_type if media_type == 'Episode': return MEDIA_TYPE_TVSHOW - elif media_type == 'Movie': + if media_type == 'Movie': return MEDIA_TYPE_MOVIE - elif media_type == 'Trailer': + if media_type == 'Trailer': return MEDIA_TYPE_TRAILER - elif media_type == 'Music': + if media_type == 'Music': return MEDIA_TYPE_MUSIC - elif media_type == 'Video': + if media_type == 'Video': return MEDIA_TYPE_GENERIC_VIDEO - elif media_type == 'Audio': + if media_type == 'Audio': return MEDIA_TYPE_MUSIC return None diff --git a/homeassistant/components/media_player/kodi.py b/homeassistant/components/media_player/kodi.py index 0585692e77d7..8758e969db14 100644 --- a/homeassistant/components/media_player/kodi.py +++ b/homeassistant/components/media_player/kodi.py @@ -749,7 +749,7 @@ class KodiDevice(MediaPlayerDevice): if media_type == "CHANNEL": return self.server.Player.Open( {"item": {"channelid": int(media_id)}}) - elif media_type == "PLAYLIST": + if media_type == "PLAYLIST": return self.server.Player.Open( {"item": {"playlistid": int(media_id)}}) diff --git a/homeassistant/components/media_player/liveboxplaytv.py b/homeassistant/components/media_player/liveboxplaytv.py index 6b161f86ab05..1b5948c964a0 100644 --- a/homeassistant/components/media_player/liveboxplaytv.py +++ b/homeassistant/components/media_player/liveboxplaytv.py @@ -202,7 +202,7 @@ class LiveboxPlayTvDevice(MediaPlayerDevice): state = self._client.media_state if state == 'PLAY': return STATE_PLAYING - elif state == 'PAUSE': + if state == 'PAUSE': return STATE_PAUSED return STATE_ON if self._client.is_on else STATE_OFF diff --git a/homeassistant/components/media_player/mpchc.py b/homeassistant/components/media_player/mpchc.py index ad8dd0bf0564..773825e0d57d 100644 --- a/homeassistant/components/media_player/mpchc.py +++ b/homeassistant/components/media_player/mpchc.py @@ -93,7 +93,7 @@ class MpcHcDevice(MediaPlayerDevice): return STATE_OFF if state == 'playing': return STATE_PLAYING - elif state == 'paused': + if state == 'paused': return STATE_PAUSED return STATE_IDLE diff --git a/homeassistant/components/media_player/mpd.py b/homeassistant/components/media_player/mpd.py index 73417e5f25d7..4b3dfc2ccbb5 100644 --- a/homeassistant/components/media_player/mpd.py +++ b/homeassistant/components/media_player/mpd.py @@ -141,11 +141,11 @@ class MpdDevice(MediaPlayerDevice): """Return the media state.""" if self._status is None: return STATE_OFF - elif self._status['state'] == 'play': + if self._status['state'] == 'play': return STATE_PLAYING - elif self._status['state'] == 'pause': + if self._status['state'] == 'pause': return STATE_PAUSED - elif self._status['state'] == 'stop': + if self._status['state'] == 'stop': return STATE_OFF return STATE_OFF @@ -182,9 +182,9 @@ class MpdDevice(MediaPlayerDevice): if file_name is None: return "None" return os.path.basename(file_name) - elif name is None: + if name is None: return title - elif title is None: + if title is None: return name return '{}: {}'.format(name, title) diff --git a/homeassistant/components/media_player/plex.py b/homeassistant/components/media_player/plex.py index ca6b9722a496..e3c6f453c35b 100644 --- a/homeassistant/components/media_player/plex.py +++ b/homeassistant/components/media_player/plex.py @@ -573,11 +573,11 @@ class PlexClient(MediaPlayerDevice): _LOGGER.debug("Clip content type detected, " "compatibility may vary: %s", self.entity_id) return MEDIA_TYPE_TVSHOW - elif self._session_type == 'episode': + if self._session_type == 'episode': return MEDIA_TYPE_TVSHOW - elif self._session_type == 'movie': + if self._session_type == 'movie': return MEDIA_TYPE_MOVIE - elif self._session_type == 'track': + if self._session_type == 'track': return MEDIA_TYPE_MUSIC return None @@ -654,7 +654,7 @@ class PlexClient(MediaPlayerDevice): if not self._make: return None # no mute support - elif self.make.lower() == "shield android tv": + if self.make.lower() == "shield android tv": _LOGGER.debug( "Shield Android TV client detected, disabling mute " "controls: %s", self.entity_id) @@ -663,7 +663,7 @@ class PlexClient(MediaPlayerDevice): SUPPORT_VOLUME_SET | SUPPORT_PLAY | SUPPORT_TURN_OFF) # Only supports play,pause,stop (and off which really is stop) - elif self.make.lower().startswith("tivo"): + if self.make.lower().startswith("tivo"): _LOGGER.debug( "Tivo client detected, only enabling pause, play, " "stop, and off controls: %s", self.entity_id) @@ -671,7 +671,7 @@ class PlexClient(MediaPlayerDevice): SUPPORT_TURN_OFF) # Not all devices support playback functionality # Playback includes volume, stop/play/pause, etc. - elif self.device and 'playback' in self._device_protocol_capabilities: + if self.device and 'playback' in self._device_protocol_capabilities: return (SUPPORT_PAUSE | SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK | SUPPORT_STOP | SUPPORT_VOLUME_SET | SUPPORT_PLAY | diff --git a/homeassistant/components/media_player/roku.py b/homeassistant/components/media_player/roku.py index a46e781de595..5f28660f4bdf 100644 --- a/homeassistant/components/media_player/roku.py +++ b/homeassistant/components/media_player/roku.py @@ -134,9 +134,9 @@ class RokuDevice(MediaPlayerDevice): if (self.current_app.name == "Power Saver" or self.current_app.is_screensaver): return STATE_IDLE - elif self.current_app.name == "Roku": + if self.current_app.name == "Roku": return STATE_HOME - elif self.current_app.name is not None: + if self.current_app.name is not None: return STATE_PLAYING return STATE_UNKNOWN @@ -156,9 +156,9 @@ class RokuDevice(MediaPlayerDevice): """Content type of current playing media.""" if self.current_app is None: return None - elif self.current_app.name == "Power Saver": + if self.current_app.name == "Power Saver": return None - elif self.current_app.name == "Roku": + if self.current_app.name == "Roku": return None return MEDIA_TYPE_MOVIE @@ -167,11 +167,11 @@ class RokuDevice(MediaPlayerDevice): """Image url of current playing media.""" if self.current_app is None: return None - elif self.current_app.name == "Roku": + if self.current_app.name == "Roku": return None - elif self.current_app.name == "Power Saver": + if self.current_app.name == "Power Saver": return None - elif self.current_app.id is None: + if self.current_app.id is None: return None return 'http://{0}:{1}/query/icon/{2}'.format( diff --git a/homeassistant/components/media_player/russound_rio.py b/homeassistant/components/media_player/russound_rio.py index 31b04ceb3cde..e9f8ab5f199a 100644 --- a/homeassistant/components/media_player/russound_rio.py +++ b/homeassistant/components/media_player/russound_rio.py @@ -100,8 +100,7 @@ class RussoundZoneDevice(MediaPlayerDevice): if value in (None, "", "------"): return None return value - else: - return None + return None def _zone_callback_handler(self, zone_id, *args): if zone_id == self._zone_id: @@ -134,7 +133,7 @@ class RussoundZoneDevice(MediaPlayerDevice): status = self._zone_var('status', "OFF") if status == 'ON': return STATE_ON - elif status == 'OFF': + if status == 'OFF': return STATE_OFF @property diff --git a/homeassistant/components/media_player/soundtouch.py b/homeassistant/components/media_player/soundtouch.py index 9c4a0e9fa17e..8f14031481ad 100644 --- a/homeassistant/components/media_player/soundtouch.py +++ b/homeassistant/components/media_player/soundtouch.py @@ -269,7 +269,7 @@ class SoundTouchDevice(MediaPlayerDevice): """Title of current playing media.""" if self._status.station_name is not None: return self._status.station_name - elif self._status.artist is not None: + if self._status.artist is not None: return self._status.artist + " - " + self._status.track return None diff --git a/homeassistant/components/media_player/vizio.py b/homeassistant/components/media_player/vizio.py index 203a2c2b4089..046aecbb92e2 100644 --- a/homeassistant/components/media_player/vizio.py +++ b/homeassistant/components/media_player/vizio.py @@ -95,7 +95,7 @@ class VizioDevice(MediaPlayerDevice): if is_on is None: self._state = STATE_UNKNOWN return - elif is_on is False: + if is_on is False: self._state = STATE_OFF else: self._state = STATE_ON diff --git a/homeassistant/components/media_player/volumio.py b/homeassistant/components/media_player/volumio.py index 11ab16156172..c4ddd38fc4f4 100644 --- a/homeassistant/components/media_player/volumio.py +++ b/homeassistant/components/media_player/volumio.py @@ -142,7 +142,7 @@ class Volumio(MediaPlayerDevice): status = self._state.get('status', None) if status == 'pause': return STATE_PAUSED - elif status == 'play': + if status == 'play': return STATE_PLAYING return STATE_IDLE diff --git a/homeassistant/components/mysensors/__init__.py b/homeassistant/components/mysensors/__init__.py index 3066819638f5..980efcf5805d 100644 --- a/homeassistant/components/mysensors/__init__.py +++ b/homeassistant/components/mysensors/__init__.py @@ -52,9 +52,8 @@ def is_persistence_file(value): """Validate that persistence file path ends in either .pickle or .json.""" if value.endswith(('.json', '.pickle')): return value - else: - raise vol.Invalid( - '{} does not end in either `.json` or `.pickle`'.format(value)) + raise vol.Invalid( + '{} does not end in either `.json` or `.pickle`'.format(value)) def deprecated(key): diff --git a/homeassistant/components/mysensors/gateway.py b/homeassistant/components/mysensors/gateway.py index 73222cb6be21..a7de62cd3292 100644 --- a/homeassistant/components/mysensors/gateway.py +++ b/homeassistant/components/mysensors/gateway.py @@ -38,10 +38,8 @@ def is_serial_port(value): ports = ('COM{}'.format(idx + 1) for idx in range(256)) if value in ports: return value - else: - raise vol.Invalid('{} is not a serial port'.format(value)) - else: - return cv.isdevice(value) + raise vol.Invalid('{} is not a serial port'.format(value)) + return cv.isdevice(value) def is_socket_address(value): diff --git a/homeassistant/components/nest/config_flow.py b/homeassistant/components/nest/config_flow.py index b5c095f34b80..f97e0dc8ff5c 100644 --- a/homeassistant/components/nest/config_flow.py +++ b/homeassistant/components/nest/config_flow.py @@ -65,14 +65,14 @@ class NestFlowHandler(data_entry_flow.FlowHandler): if self.hass.config_entries.async_entries(DOMAIN): return self.async_abort(reason='already_setup') - elif not flows: + if not flows: return self.async_abort(reason='no_flows') - elif len(flows) == 1: + if len(flows) == 1: self.flow_impl = list(flows)[0] return await self.async_step_link() - elif user_input is not None: + if user_input is not None: self.flow_impl = user_input['flow_impl'] return await self.async_step_link() diff --git a/homeassistant/components/netgear_lte.py b/homeassistant/components/netgear_lte.py index d6bd83dd41fe..7f54e6fd6f91 100644 --- a/homeassistant/components/netgear_lte.py +++ b/homeassistant/components/netgear_lte.py @@ -61,7 +61,7 @@ class LTEData: """Get the requested or the only modem_data value.""" if CONF_HOST in config: return self.modem_data.get(config[CONF_HOST]) - elif len(self.modem_data) == 1: + if len(self.modem_data) == 1: return next(iter(self.modem_data.values())) return None diff --git a/homeassistant/components/notify/html5.py b/homeassistant/components/notify/html5.py index 7529608387d8..e280aa67e40a 100644 --- a/homeassistant/components/notify/html5.py +++ b/homeassistant/components/notify/html5.py @@ -280,7 +280,7 @@ class HTML5PushCallbackView(HomeAssistantView): return self.json_message('Authorization header must ' 'start with Bearer', status_code=HTTP_UNAUTHORIZED) - elif len(parts) != 2: + if len(parts) != 2: return self.json_message('Authorization header must ' 'be Bearer token', status_code=HTTP_UNAUTHORIZED) diff --git a/homeassistant/components/notify/rest.py b/homeassistant/components/notify/rest.py index 40b09dc3c72f..dd35f986f78a 100644 --- a/homeassistant/components/notify/rest.py +++ b/homeassistant/components/notify/rest.py @@ -95,7 +95,7 @@ class RestNotificationService(BaseNotificationService): """Recursive template creator helper function.""" if isinstance(value, list): return [_data_template_creator(item) for item in value] - elif isinstance(value, dict): + if isinstance(value, dict): return {key: _data_template_creator(item) for key, item in value.items()} value.hass = self._hass diff --git a/homeassistant/components/notify/telegram.py b/homeassistant/components/notify/telegram.py index 899ccf9b09af..b012506acd9d 100644 --- a/homeassistant/components/notify/telegram.py +++ b/homeassistant/components/notify/telegram.py @@ -73,7 +73,7 @@ class TelegramNotificationService(BaseNotificationService): self.hass.services.call( DOMAIN, 'send_photo', service_data=service_data) return - elif data is not None and ATTR_VIDEO in data: + if data is not None and ATTR_VIDEO in data: videos = data.get(ATTR_VIDEO, None) videos = videos if isinstance(videos, list) else [videos] for video_data in videos: @@ -81,11 +81,11 @@ class TelegramNotificationService(BaseNotificationService): self.hass.services.call( DOMAIN, 'send_video', service_data=service_data) return - elif data is not None and ATTR_LOCATION in data: + if data is not None and ATTR_LOCATION in data: service_data.update(data.get(ATTR_LOCATION)) return self.hass.services.call( DOMAIN, 'send_location', service_data=service_data) - elif data is not None and ATTR_DOCUMENT in data: + if data is not None and ATTR_DOCUMENT in data: service_data.update(data.get(ATTR_DOCUMENT)) return self.hass.services.call( DOMAIN, 'send_document', service_data=service_data) diff --git a/homeassistant/components/notify/twitter.py b/homeassistant/components/notify/twitter.py index e38e7fcaa0fe..6076cd5393a2 100644 --- a/homeassistant/components/notify/twitter.py +++ b/homeassistant/components/notify/twitter.py @@ -194,9 +194,9 @@ class TwitterNotificationService(BaseNotificationService): if media_type.startswith('image/gif'): return 'tweet_gif' - elif media_type.startswith('video/'): + if media_type.startswith('video/'): return 'tweet_video' - elif media_type.startswith('image/'): + if media_type.startswith('image/'): return 'tweet_image' return None diff --git a/homeassistant/components/octoprint.py b/homeassistant/components/octoprint.py index bc936265f6ff..ff52ad94d8b8 100644 --- a/homeassistant/components/octoprint.py +++ b/homeassistant/components/octoprint.py @@ -154,7 +154,7 @@ def get_value_from_json(json_dict, sensor_type, group, tool): return 0 return json_dict[group][sensor_type] - elif tool is not None: + if tool is not None: if sensor_type in json_dict[group][tool]: return json_dict[group][tool][sensor_type] diff --git a/homeassistant/components/rachio.py b/homeassistant/components/rachio.py index 90854efe8b1f..7162913087d7 100644 --- a/homeassistant/components/rachio.py +++ b/homeassistant/components/rachio.py @@ -122,8 +122,7 @@ def setup(hass, config) -> bool: _LOGGER.error("No Rachio devices found in account %s", person.username) return False - else: - _LOGGER.info("%d Rachio device(s) found", len(person.controllers)) + _LOGGER.info("%d Rachio device(s) found", len(person.controllers)) # Enable component hass.data[DOMAIN] = person diff --git a/homeassistant/components/recorder/__init__.py b/homeassistant/components/recorder/__init__.py index 43c2aa5c7b11..60df63270098 100644 --- a/homeassistant/components/recorder/__init__.py +++ b/homeassistant/components/recorder/__init__.py @@ -284,7 +284,7 @@ class Recorder(threading.Thread): self._close_connection() self.queue.task_done() return - elif isinstance(event, PurgeTask): + if isinstance(event, PurgeTask): purge.purge_old_data(self, event.keep_days, event.repack) self.queue.task_done() continue diff --git a/homeassistant/components/recorder/models.py b/homeassistant/components/recorder/models.py index 32d6291b90ca..e79484462314 100644 --- a/homeassistant/components/recorder/models.py +++ b/homeassistant/components/recorder/models.py @@ -168,7 +168,7 @@ def _process_timestamp(ts): """Process a timestamp into datetime object.""" if ts is None: return None - elif ts.tzinfo is None: + if ts.tzinfo is None: return dt_util.UTC.localize(ts) return dt_util.as_utc(ts) diff --git a/homeassistant/components/rflink.py b/homeassistant/components/rflink.py index 272a5b868ec8..b8af971b3fff 100644 --- a/homeassistant/components/rflink.py +++ b/homeassistant/components/rflink.py @@ -100,7 +100,7 @@ def identify_event_type(event): """ if EVENT_KEY_COMMAND in event: return EVENT_KEY_COMMAND - elif EVENT_KEY_SENSOR in event: + if EVENT_KEY_SENSOR in event: return EVENT_KEY_SENSOR return 'unknown' diff --git a/homeassistant/components/scene/lifx_cloud.py b/homeassistant/components/scene/lifx_cloud.py index 6fe91d0acd2e..a9ec1ef679cb 100644 --- a/homeassistant/components/scene/lifx_cloud.py +++ b/homeassistant/components/scene/lifx_cloud.py @@ -58,7 +58,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): devices.append(LifxCloudScene(hass, headers, timeout, scene)) async_add_devices(devices) return True - elif status == 401: + if status == 401: _LOGGER.error("Unauthorized (bad token?) on %s", url) return False diff --git a/homeassistant/components/sensor/abode.py b/homeassistant/components/sensor/abode.py index b51ab288c1aa..26247c774547 100644 --- a/homeassistant/components/sensor/abode.py +++ b/homeassistant/components/sensor/abode.py @@ -67,9 +67,9 @@ class AbodeSensor(AbodeDevice): """Return the state of the sensor.""" if self._sensor_type == 'temp': return self._device.temp - elif self._sensor_type == 'humidity': + if self._sensor_type == 'humidity': return self._device.humidity - elif self._sensor_type == 'lux': + if self._sensor_type == 'lux': return self._device.lux @property @@ -77,7 +77,7 @@ class AbodeSensor(AbodeDevice): """Return the units of measurement.""" if self._sensor_type == 'temp': return self._device.temp_unit - elif self._sensor_type == 'humidity': + if self._sensor_type == 'humidity': return self._device.humidity_unit - elif self._sensor_type == 'lux': + if self._sensor_type == 'lux': return self._device.lux_unit diff --git a/homeassistant/components/sensor/api_streams.py b/homeassistant/components/sensor/api_streams.py index a8ef179280bb..0d193dee79bb 100644 --- a/homeassistant/components/sensor/api_streams.py +++ b/homeassistant/components/sensor/api_streams.py @@ -38,9 +38,9 @@ class StreamHandler(logging.Handler): else: if not record.msg.startswith('WS'): return - elif len(record.args) < 2: + if len(record.args) < 2: return - elif record.args[1] == 'Connected': + if record.args[1] == 'Connected': self.entity.count += 1 elif record.args[1] == 'Closed connection': self.entity.count -= 1 diff --git a/homeassistant/components/sensor/arlo.py b/homeassistant/components/sensor/arlo.py index eeb0c966ab44..6d764b1c9164 100644 --- a/homeassistant/components/sensor/arlo.py +++ b/homeassistant/components/sensor/arlo.py @@ -123,7 +123,7 @@ class ArloSensor(Entity): """Return the device class of the sensor.""" if self._sensor_type == 'temperature': return DEVICE_CLASS_TEMPERATURE - elif self._sensor_type == 'humidity': + if self._sensor_type == 'humidity': return DEVICE_CLASS_HUMIDITY return None diff --git a/homeassistant/components/sensor/buienradar.py b/homeassistant/components/sensor/buienradar.py index e366a83a65d6..33c7e432fca3 100644 --- a/homeassistant/components/sensor/buienradar.py +++ b/homeassistant/components/sensor/buienradar.py @@ -262,13 +262,13 @@ class BrSensor(Entity): self._entity_picture = img return True return False - else: - try: - self._state = data.get(FORECAST)[fcday].get(self.type[:-3]) - return True - except IndexError: - _LOGGER.warning("No forecast for fcday=%s...", fcday) - return False + + try: + self._state = data.get(FORECAST)[fcday].get(self.type[:-3]) + return True + except IndexError: + _LOGGER.warning("No forecast for fcday=%s...", fcday) + return False if self.type == SYMBOL or self.type.startswith(CONDITION): # update weather symbol & status text diff --git a/homeassistant/components/sensor/currencylayer.py b/homeassistant/components/sensor/currencylayer.py index 0834bbc99d85..4a7face01567 100644 --- a/homeassistant/components/sensor/currencylayer.py +++ b/homeassistant/components/sensor/currencylayer.py @@ -54,8 +54,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): sensors.append(CurrencylayerSensor(rest, base, variable)) if 'error' in response.json(): return False - else: - add_devices(sensors, True) + add_devices(sensors, True) class CurrencylayerSensor(Entity): diff --git a/homeassistant/components/sensor/darksky.py b/homeassistant/components/sensor/darksky.py index 2e57e1bd4475..b2bb7bb4da23 100644 --- a/homeassistant/components/sensor/darksky.py +++ b/homeassistant/components/sensor/darksky.py @@ -352,12 +352,12 @@ class DarkSkySensor(Entity): # percentages if self.type in ['precip_probability', 'cloud_cover', 'humidity']: return round(state * 100, 1) - elif (self.type in ['dew_point', 'temperature', 'apparent_temperature', - 'temperature_min', 'temperature_max', - 'apparent_temperature_min', - 'apparent_temperature_max', - 'precip_accumulation', - 'pressure', 'ozone', 'uvIndex']): + if self.type in ['dew_point', 'temperature', 'apparent_temperature', + 'temperature_min', 'temperature_max', + 'apparent_temperature_min', + 'apparent_temperature_max', + 'precip_accumulation', + 'pressure', 'ozone', 'uvIndex']: return round(state, 1) return state diff --git a/homeassistant/components/sensor/dovado.py b/homeassistant/components/sensor/dovado.py index ee2292d41221..2a78d4ad8648 100644 --- a/homeassistant/components/sensor/dovado.py +++ b/homeassistant/components/sensor/dovado.py @@ -129,17 +129,16 @@ class DovadoSensor(Entity): if self._sensor == SENSOR_NETWORK: match = re.search(r"\((.+)\)", state) return match.group(1) if match else None - elif self._sensor == SENSOR_SIGNAL: + if self._sensor == SENSOR_SIGNAL: try: return int(state.split()[0]) except ValueError: return 0 - elif self._sensor == SENSOR_SMS_UNREAD: + if self._sensor == SENSOR_SMS_UNREAD: return int(state) - elif self._sensor in [SENSOR_UPLOAD, SENSOR_DOWNLOAD]: + if self._sensor in [SENSOR_UPLOAD, SENSOR_DOWNLOAD]: return round(float(state) / 1e6, 1) - else: - return state + return state def update(self): """Update sensor values.""" diff --git a/homeassistant/components/sensor/dsmr.py b/homeassistant/components/sensor/dsmr.py index d7982f1c9dba..3a1bf1da39e1 100644 --- a/homeassistant/components/sensor/dsmr.py +++ b/homeassistant/components/sensor/dsmr.py @@ -255,7 +255,7 @@ class DSMREntity(Entity): return ICON_POWER_FAILURE if 'Power' in self._name: return ICON_POWER - elif 'Gas' in self._name: + if 'Gas' in self._name: return ICON_GAS @property @@ -285,7 +285,7 @@ class DSMREntity(Entity): # used for normal rate. if value == '0002': return 'normal' - elif value == '0001': + if value == '0001': return 'low' return STATE_UNKNOWN diff --git a/homeassistant/components/sensor/eight_sleep.py b/homeassistant/components/sensor/eight_sleep.py index fd7c1aee3ae6..5899ef267cb8 100644 --- a/homeassistant/components/sensor/eight_sleep.py +++ b/homeassistant/components/sensor/eight_sleep.py @@ -149,7 +149,7 @@ class EightUserSensor(EightSleepUserEntity): """Return the unit the value is expressed in.""" if 'current_sleep' in self._sensor or 'last_sleep' in self._sensor: return 'Score' - elif 'bed_temp' in self._sensor: + if 'bed_temp' in self._sensor: if self._units == 'si': return '°C' return '°F' diff --git a/homeassistant/components/sensor/fritzbox_netmonitor.py b/homeassistant/components/sensor/fritzbox_netmonitor.py index 857e6cc4a074..b980323abe1e 100644 --- a/homeassistant/components/sensor/fritzbox_netmonitor.py +++ b/homeassistant/components/sensor/fritzbox_netmonitor.py @@ -65,8 +65,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): if fstatus is None: _LOGGER.error("Failed to establish connection to FRITZ!Box: %s", host) return 1 - else: - _LOGGER.info("Successfully connected to FRITZ!Box") + _LOGGER.info("Successfully connected to FRITZ!Box") add_devices([FritzboxMonitorSensor(name, fstatus)], True) diff --git a/homeassistant/components/sensor/gpsd.py b/homeassistant/components/sensor/gpsd.py index 1d270419933a..f463d0fb8d1a 100644 --- a/homeassistant/components/sensor/gpsd.py +++ b/homeassistant/components/sensor/gpsd.py @@ -91,7 +91,7 @@ class GpsdSensor(Entity): """Return the state of GPSD.""" if self.agps_thread.data_stream.mode == 3: return "3D Fix" - elif self.agps_thread.data_stream.mode == 2: + if self.agps_thread.data_stream.mode == 2: return "2D Fix" return STATE_UNKNOWN diff --git a/homeassistant/components/sensor/history_stats.py b/homeassistant/components/sensor/history_stats.py index f1f12b6ecabd..c3d0fe8f1b6e 100644 --- a/homeassistant/components/sensor/history_stats.py +++ b/homeassistant/components/sensor/history_stats.py @@ -294,7 +294,7 @@ class HistoryStatsHelper: minutes, seconds = divmod(seconds, 60) if days > 0: return '%dd %dh %dm' % (days, hours, minutes) - elif hours > 0: + if hours > 0: return '%dh %dm' % (hours, minutes) return '%dm' % minutes diff --git a/homeassistant/components/sensor/hive.py b/homeassistant/components/sensor/hive.py index 8c9409ef5ffc..2d6090704158 100644 --- a/homeassistant/components/sensor/hive.py +++ b/homeassistant/components/sensor/hive.py @@ -55,7 +55,7 @@ class HiveSensorEntity(Entity): """Return the state of the sensor.""" if self.device_type == "Hub_OnlineStatus": return self.session.sensor.hub_online_status(self.node_id) - elif self.device_type == "Hive_OutsideTemperature": + if self.device_type == "Hive_OutsideTemperature": return self.session.weather.temperature() @property diff --git a/homeassistant/components/sensor/isy994.py b/homeassistant/components/sensor/isy994.py index 3eabce9458c9..19dcfc870140 100644 --- a/homeassistant/components/sensor/isy994.py +++ b/homeassistant/components/sensor/isy994.py @@ -262,10 +262,8 @@ class ISYSensorDevice(ISYDevice): if friendly_name in (TEMP_CELSIUS, TEMP_FAHRENHEIT): friendly_name = self.hass.config.units.temperature_unit return friendly_name - else: - return self._node.uom[0] - else: - return None + return self._node.uom[0] + return None @property def state(self) -> str: diff --git a/homeassistant/components/sensor/mfi.py b/homeassistant/components/sensor/mfi.py index ab6bd8270ce1..f575768b505d 100644 --- a/homeassistant/components/sensor/mfi.py +++ b/homeassistant/components/sensor/mfi.py @@ -96,7 +96,7 @@ class MfiSensor(Entity): tag = None if tag is None: return STATE_OFF - elif self._port.model == 'Input Digital': + if self._port.model == 'Input Digital': return STATE_ON if self._port.value > 0 else STATE_OFF digits = DIGITS.get(self._port.tag, 0) return round(self._port.value, digits) @@ -111,9 +111,9 @@ class MfiSensor(Entity): if tag == 'temperature': return TEMP_CELSIUS - elif tag == 'active_pwr': + if tag == 'active_pwr': return 'Watts' - elif self._port.model == 'Input Digital': + if self._port.model == 'Input Digital': return 'State' return tag diff --git a/homeassistant/components/sensor/mold_indicator.py b/homeassistant/components/sensor/mold_indicator.py index 62d8af2ee8fc..319185923cd1 100644 --- a/homeassistant/components/sensor/mold_indicator.py +++ b/homeassistant/components/sensor/mold_indicator.py @@ -107,11 +107,10 @@ class MoldIndicator(Entity): # convert to celsius if necessary if unit == TEMP_FAHRENHEIT: return util.temperature.fahrenheit_to_celsius(temp) - elif unit == TEMP_CELSIUS: + if unit == TEMP_CELSIUS: return temp - else: - _LOGGER.error("Temp sensor has unsupported unit: %s (allowed: %s, " - "%s)", unit, TEMP_CELSIUS, TEMP_FAHRENHEIT) + _LOGGER.error("Temp sensor has unsupported unit: %s (allowed: %s, " + "%s)", unit, TEMP_CELSIUS, TEMP_FAHRENHEIT) return None diff --git a/homeassistant/components/sensor/moon.py b/homeassistant/components/sensor/moon.py index d909eb3c0f22..50f4f72078cb 100644 --- a/homeassistant/components/sensor/moon.py +++ b/homeassistant/components/sensor/moon.py @@ -51,17 +51,17 @@ class MoonSensor(Entity): """Return the state of the device.""" if self._state == 0: return 'new_moon' - elif self._state < 7: + if self._state < 7: return 'waxing_crescent' - elif self._state == 7: + if self._state == 7: return 'first_quarter' - elif self._state < 14: + if self._state < 14: return 'waxing_gibbous' - elif self._state == 14: + if self._state == 14: return 'full_moon' - elif self._state < 21: + if self._state < 21: return 'waning_gibbous' - elif self._state == 21: + if self._state == 21: return 'last_quarter' return 'waning_crescent' diff --git a/homeassistant/components/sensor/nut.py b/homeassistant/components/sensor/nut.py index ff0cd8ef891c..7126bd89ef9f 100644 --- a/homeassistant/components/sensor/nut.py +++ b/homeassistant/components/sensor/nut.py @@ -236,13 +236,12 @@ class NUTSensor(Entity): """Return UPS display state.""" if self._data.status is None: return STATE_TYPES['OFF'] - else: - try: - return " ".join( - STATE_TYPES[state] - for state in self._data.status[KEY_STATUS].split()) - except KeyError: - return STATE_UNKNOWN + try: + return " ".join( + STATE_TYPES[state] + for state in self._data.status[KEY_STATUS].split()) + except KeyError: + return STATE_UNKNOWN def update(self): """Get the latest status and use it to update our sensor state.""" diff --git a/homeassistant/components/sensor/octoprint.py b/homeassistant/components/sensor/octoprint.py index df181c67c033..9e62846e4d3e 100644 --- a/homeassistant/components/sensor/octoprint.py +++ b/homeassistant/components/sensor/octoprint.py @@ -112,8 +112,7 @@ class OctoPrintSensor(Entity): if self._state is None: self._state = 0 return round(self._state, 2) - else: - return self._state + return self._state @property def unit_of_measurement(self): diff --git a/homeassistant/components/sensor/openhardwaremonitor.py b/homeassistant/components/sensor/openhardwaremonitor.py index dc221f6b07d6..1b345c752fff 100644 --- a/homeassistant/components/sensor/openhardwaremonitor.py +++ b/homeassistant/components/sensor/openhardwaremonitor.py @@ -101,11 +101,10 @@ class OpenHardwareMonitorDevice(Entity): self.attributes = _attributes return - else: - array = array[path_number][OHM_CHILDREN] - _attributes.update({ - 'level_%s' % path_index: values[OHM_NAME] - }) + array = array[path_number][OHM_CHILDREN] + _attributes.update({ + 'level_%s' % path_index: values[OHM_NAME] + }) class OpenHardwareMonitorData: diff --git a/homeassistant/components/sensor/qnap.py b/homeassistant/components/sensor/qnap.py index a44823d86ba7..8b25eb3de31a 100644 --- a/homeassistant/components/sensor/qnap.py +++ b/homeassistant/components/sensor/qnap.py @@ -241,7 +241,7 @@ class QNAPCPUSensor(QNAPSensor): """Return the state of the sensor.""" if self.var_id == 'cpu_temp': return self._api.data['system_stats']['cpu']['temp_c'] - elif self.var_id == 'cpu_usage': + if self.var_id == 'cpu_usage': return self._api.data['system_stats']['cpu']['usage_percent'] diff --git a/homeassistant/components/sensor/snmp.py b/homeassistant/components/sensor/snmp.py index 0ba74f0e7ed0..5600f906f342 100644 --- a/homeassistant/components/sensor/snmp.py +++ b/homeassistant/components/sensor/snmp.py @@ -83,11 +83,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None): if errindication and not accept_errors: _LOGGER.error("Please check the details in the configuration file") return False - else: - data = SnmpData( - host, port, community, baseoid, version, accept_errors, - default_value) - add_devices([SnmpSensor(data, name, unit, value_template)], True) + data = SnmpData( + host, port, community, baseoid, version, accept_errors, + default_value) + add_devices([SnmpSensor(data, name, unit, value_template)], True) class SnmpSensor(Entity): diff --git a/homeassistant/components/sensor/synologydsm.py b/homeassistant/components/sensor/synologydsm.py index 124cec97617a..d431805ab191 100644 --- a/homeassistant/components/sensor/synologydsm.py +++ b/homeassistant/components/sensor/synologydsm.py @@ -214,7 +214,7 @@ class SynoNasUtilSensor(SynoNasSensor): if self.var_id in network_sensors: return round(attr / 1024.0, 1) - elif self.var_id in memory_sensors: + if self.var_id in memory_sensors: return round(attr / 1024.0 / 1024.0, 1) else: return getattr(self._api.utilisation, self.var_id) diff --git a/homeassistant/components/sensor/tado.py b/homeassistant/components/sensor/tado.py index 737b3d083689..aa6314b8c5bb 100644 --- a/homeassistant/components/sensor/tado.py +++ b/homeassistant/components/sensor/tado.py @@ -122,9 +122,9 @@ class TadoSensor(Entity): """Return the unit of measurement.""" if self.zone_variable == "temperature": return self.hass.config.units.temperature_unit - elif self.zone_variable == "humidity": + if self.zone_variable == "humidity": return '%' - elif self.zone_variable == "heating": + if self.zone_variable == "heating": return '%' @property @@ -132,7 +132,7 @@ class TadoSensor(Entity): """Icon for the sensor.""" if self.zone_variable == "temperature": return 'mdi:thermometer' - elif self.zone_variable == "humidity": + if self.zone_variable == "humidity": return 'mdi:water-percent' def update(self): diff --git a/homeassistant/components/sensor/tahoma.py b/homeassistant/components/sensor/tahoma.py index aedecfe61e56..6c6c296652a5 100644 --- a/homeassistant/components/sensor/tahoma.py +++ b/homeassistant/components/sensor/tahoma.py @@ -46,11 +46,11 @@ class TahomaSensor(TahomaDevice, Entity): """Return the unit of measurement of this entity, if any.""" if self.tahoma_device.type == 'Temperature Sensor': return None - elif self.tahoma_device.type == 'io:SomfyContactIOSystemSensor': + if self.tahoma_device.type == 'io:SomfyContactIOSystemSensor': return None - elif self.tahoma_device.type == 'io:LightIOSystemSensor': + if self.tahoma_device.type == 'io:LightIOSystemSensor': return 'lx' - elif self.tahoma_device.type == 'Humidity Sensor': + if self.tahoma_device.type == 'Humidity Sensor': return '%' def update(self): diff --git a/homeassistant/components/sensor/tellduslive.py b/homeassistant/components/sensor/tellduslive.py index 048ca988e3d8..123c11021b40 100644 --- a/homeassistant/components/sensor/tellduslive.py +++ b/homeassistant/components/sensor/tellduslive.py @@ -96,11 +96,11 @@ class TelldusLiveSensor(TelldusLiveEntity): """Return the state of the sensor.""" if not self.available: return None - elif self._type == SENSOR_TYPE_TEMPERATURE: + if self._type == SENSOR_TYPE_TEMPERATURE: return self._value_as_temperature - elif self._type == SENSOR_TYPE_HUMIDITY: + if self._type == SENSOR_TYPE_HUMIDITY: return self._value_as_humidity - elif self._type == SENSOR_TYPE_LUMINANCE: + if self._type == SENSOR_TYPE_LUMINANCE: return self._value_as_luminance return self._value diff --git a/homeassistant/components/sensor/time_date.py b/homeassistant/components/sensor/time_date.py index bfdf0c3c3aad..0668b5bdbce2 100644 --- a/homeassistant/components/sensor/time_date.py +++ b/homeassistant/components/sensor/time_date.py @@ -81,7 +81,7 @@ class TimeDateSensor(Entity): """Icon to use in the frontend, if any.""" if 'date' in self.type and 'time' in self.type: return 'mdi:calendar-clock' - elif 'date' in self.type: + if 'date' in self.type: return 'mdi:calendar' return 'mdi:clock' @@ -92,7 +92,7 @@ class TimeDateSensor(Entity): if self.type == 'date': now = dt_util.start_of_local_day(dt_util.as_local(now)) return now + timedelta(seconds=86400) - elif self.type == 'beat': + if self.type == 'beat': interval = 86.4 else: interval = 60 diff --git a/homeassistant/components/sensor/vera.py b/homeassistant/components/sensor/vera.py index 4fc92db1d900..eaef3dcf7f73 100644 --- a/homeassistant/components/sensor/vera.py +++ b/homeassistant/components/sensor/vera.py @@ -51,13 +51,13 @@ class VeraSensor(VeraDevice, Entity): import pyvera as veraApi if self.vera_device.category == veraApi.CATEGORY_TEMPERATURE_SENSOR: return self._temperature_units - elif self.vera_device.category == veraApi.CATEGORY_LIGHT_SENSOR: + if self.vera_device.category == veraApi.CATEGORY_LIGHT_SENSOR: return 'lx' - elif self.vera_device.category == veraApi.CATEGORY_UV_SENSOR: + if self.vera_device.category == veraApi.CATEGORY_UV_SENSOR: return 'level' - elif self.vera_device.category == veraApi.CATEGORY_HUMIDITY_SENSOR: + if self.vera_device.category == veraApi.CATEGORY_HUMIDITY_SENSOR: return '%' - elif self.vera_device.category == veraApi.CATEGORY_POWER_METER: + if self.vera_device.category == veraApi.CATEGORY_POWER_METER: return 'watts' def update(self): diff --git a/homeassistant/components/sensor/volvooncall.py b/homeassistant/components/sensor/volvooncall.py index 343bcdf20333..78e8a7e76c63 100644 --- a/homeassistant/components/sensor/volvooncall.py +++ b/homeassistant/components/sensor/volvooncall.py @@ -43,7 +43,7 @@ class VolvoSensor(VolvoEntity): if 'mil' in self.unit_of_measurement: return round(val, 2) return round(val, 1) - elif self._attribute == 'distance_to_empty': + if self._attribute == 'distance_to_empty': return int(floor(val)) return int(round(val)) diff --git a/homeassistant/components/sensor/worldtidesinfo.py b/homeassistant/components/sensor/worldtidesinfo.py index 05d61173da00..597a971e208f 100644 --- a/homeassistant/components/sensor/worldtidesinfo.py +++ b/homeassistant/components/sensor/worldtidesinfo.py @@ -85,7 +85,7 @@ class WorldTidesInfoSensor(Entity): tidetime = time.strftime('%I:%M %p', time.localtime( self.data['extremes'][0]['dt'])) return "High tide at %s" % (tidetime) - elif "Low" in str(self.data['extremes'][0]['type']): + if "Low" in str(self.data['extremes'][0]['type']): tidetime = time.strftime('%I:%M %p', time.localtime( self.data['extremes'][0]['dt'])) return "Low tide at %s" % (tidetime) diff --git a/homeassistant/components/sensor/worxlandroid.py b/homeassistant/components/sensor/worxlandroid.py index ddf506bf4eb7..c49ce36bd499 100644 --- a/homeassistant/components/sensor/worxlandroid.py +++ b/homeassistant/components/sensor/worxlandroid.py @@ -152,11 +152,11 @@ class WorxLandroidSensor(Entity): if state_obj[14] == 1: return 'manual-stop' - elif state_obj[5] == 1 and state_obj[13] == 0: + if state_obj[5] == 1 and state_obj[13] == 0: return 'charging' - elif state_obj[5] == 1 and state_obj[13] == 1: + if state_obj[5] == 1 and state_obj[13] == 1: return 'charging-complete' - elif state_obj[15] == 1: + if state_obj[15] == 1: return 'going-home' return 'mowing' diff --git a/homeassistant/components/sensor/xiaomi_aqara.py b/homeassistant/components/sensor/xiaomi_aqara.py index 3192d0d2f60f..32139b219764 100644 --- a/homeassistant/components/sensor/xiaomi_aqara.py +++ b/homeassistant/components/sensor/xiaomi_aqara.py @@ -91,9 +91,9 @@ class XiaomiSensor(XiaomiDevice): value = max(value - 300, 0) if self._data_key == 'temperature' and (value < -50 or value > 60): return False - elif self._data_key == 'humidity' and (value <= 0 or value > 100): + if self._data_key == 'humidity' and (value <= 0 or value > 100): return False - elif self._data_key == 'pressure' and value == 0: + if self._data_key == 'pressure' and value == 0: return False self._state = round(value, 1) return True diff --git a/homeassistant/components/sensor/zwave.py b/homeassistant/components/sensor/zwave.py index d2166dde64e2..c6356efe1578 100644 --- a/homeassistant/components/sensor/zwave.py +++ b/homeassistant/components/sensor/zwave.py @@ -64,7 +64,7 @@ class ZWaveMultilevelSensor(ZWaveSensor): """Return the state of the sensor.""" if self._units in ('C', 'F'): return round(self._state, 1) - elif isinstance(self._state, float): + if isinstance(self._state, float): return round(self._state, 2) return self._state @@ -74,7 +74,7 @@ class ZWaveMultilevelSensor(ZWaveSensor): """Return the unit the value is expressed in.""" if self._units == 'C': return TEMP_CELSIUS - elif self._units == 'F': + if self._units == 'F': return TEMP_FAHRENHEIT return self._units diff --git a/homeassistant/components/switch/wemo.py b/homeassistant/components/switch/wemo.py index c189c312b25c..35ea435bf483 100644 --- a/homeassistant/components/switch/wemo.py +++ b/homeassistant/components/switch/wemo.py @@ -166,9 +166,9 @@ class WemoSwitch(SwitchDevice): standby_state = int(self.insight_params['state']) if standby_state == WEMO_ON: return STATE_ON - elif standby_state == WEMO_OFF: + if standby_state == WEMO_OFF: return STATE_OFF - elif standby_state == WEMO_STANDBY: + if standby_state == WEMO_STANDBY: return STATE_STANDBY return STATE_UNKNOWN diff --git a/homeassistant/components/telegram_bot/__init__.py b/homeassistant/components/telegram_bot/__init__.py index b9329a46b723..536951026015 100644 --- a/homeassistant/components/telegram_bot/__init__.py +++ b/homeassistant/components/telegram_bot/__init__.py @@ -502,7 +502,7 @@ class TelegramNotificationService: text, chat_id=chat_id, message_id=message_id, inline_message_id=inline_message_id, **params) - elif type_edit == SERVICE_EDIT_CAPTION: + if type_edit == SERVICE_EDIT_CAPTION: func_send = self.bot.editMessageCaption params[ATTR_CAPTION] = kwargs.get(ATTR_CAPTION) else: diff --git a/homeassistant/components/telegram_bot/polling.py b/homeassistant/components/telegram_bot/polling.py index ba8dc54b2649..6ee42b325046 100644 --- a/homeassistant/components/telegram_bot/polling.py +++ b/homeassistant/components/telegram_bot/polling.py @@ -92,8 +92,7 @@ class TelegramPoll(BaseTelegramBotEntity): if resp.status == 200: _json = yield from resp.json() return _json - else: - raise WrongHttpStatus('wrong status {}'.format(resp.status)) + raise WrongHttpStatus('wrong status {}'.format(resp.status)) finally: if resp is not None: yield from resp.release() diff --git a/homeassistant/components/tellduslive.py b/homeassistant/components/tellduslive.py index bb235647d1b3..c2b7ba9ba0f5 100644 --- a/homeassistant/components/tellduslive.py +++ b/homeassistant/components/tellduslive.py @@ -240,11 +240,11 @@ class TelldusLiveClient: from tellduslive import (DIM, UP, TURNON) if device.methods & DIM: return 'light' - elif device.methods & UP: + if device.methods & UP: return 'cover' - elif device.methods & TURNON: + if device.methods & TURNON: return 'switch' - elif device.methods == 0: + if device.methods == 0: return 'binary_sensor' _LOGGER.warning( "Unidentified device type (methods: %d)", device.methods) @@ -349,9 +349,9 @@ class TelldusLiveEntity(Entity): BATTERY_OK) if self.device.battery == BATTERY_LOW: return 1 - elif self.device.battery == BATTERY_UNKNOWN: + if self.device.battery == BATTERY_UNKNOWN: return None - elif self.device.battery == BATTERY_OK: + if self.device.battery == BATTERY_OK: return 100 return self.device.battery # Percentage diff --git a/homeassistant/components/volvooncall.py b/homeassistant/components/volvooncall.py index 6557be2fb1be..0ce8870bedfa 100644 --- a/homeassistant/components/volvooncall.py +++ b/homeassistant/components/volvooncall.py @@ -136,12 +136,11 @@ class VolvoData: if (vehicle.registration_number and vehicle.registration_number.lower()) in self.names: return self.names[vehicle.registration_number.lower()] - elif (vehicle.vin and - vehicle.vin.lower() in self.names): + if vehicle.vin and vehicle.vin.lower() in self.names: return self.names[vehicle.vin.lower()] - elif vehicle.registration_number: + if vehicle.registration_number: return vehicle.registration_number - elif vehicle.vin: + if vehicle.vin: return vehicle.vin return '' diff --git a/homeassistant/components/wink/__init__.py b/homeassistant/components/wink/__init__.py index a8acc4375466..c996572bf510 100644 --- a/homeassistant/components/wink/__init__.py +++ b/homeassistant/components/wink/__init__.py @@ -173,10 +173,9 @@ def _request_app_setup(hass, config): ATTR_CLIENT_SECRET: client_secret}) setup(hass, config) return - else: - error_msg = "Your input was invalid. Please try again." - _configurator = hass.data[DOMAIN]['configuring'][DOMAIN] - configurator.notify_errors(_configurator, error_msg) + error_msg = "Your input was invalid. Please try again." + _configurator = hass.data[DOMAIN]['configuring'][DOMAIN] + configurator.notify_errors(_configurator, error_msg) start_url = "{}{}".format(hass.config.api.base_url, WINK_AUTH_CALLBACK_PATH) diff --git a/homeassistant/components/zwave/util.py b/homeassistant/components/zwave/util.py index b62eeb67d328..312d72575a94 100644 --- a/homeassistant/components/zwave/util.py +++ b/homeassistant/components/zwave/util.py @@ -82,7 +82,7 @@ async def check_has_unique_id(entity, ready_callback, timeout_callback, loop): if entity.unique_id: ready_callback(waited) return - elif waited >= const.NODE_READY_WAIT_SECS: + if waited >= const.NODE_READY_WAIT_SECS: # Wait up to NODE_READY_WAIT_SECS seconds for unique_id to appear. timeout_callback(waited) return diff --git a/homeassistant/config.py b/homeassistant/config.py index 230a10498f34..d458ce44d370 100644 --- a/homeassistant/config.py +++ b/homeassistant/config.py @@ -572,8 +572,7 @@ def _recursive_merge(conf, package): else: if conf.get(key) is not None: return key - else: - conf[key] = pack_conf + conf[key] = pack_conf return error diff --git a/homeassistant/helpers/condition.py b/homeassistant/helpers/condition.py index 504c9d4b067b..930f68c3da47 100644 --- a/homeassistant/helpers/condition.py +++ b/homeassistant/helpers/condition.py @@ -257,13 +257,13 @@ def sun(hass, before=None, after=None, before_offset=None, after_offset=None): if before == SUN_EVENT_SUNRISE and utcnow > sunrise + before_offset: return False - elif before == SUN_EVENT_SUNSET and utcnow > sunset + before_offset: + if before == SUN_EVENT_SUNSET and utcnow > sunset + before_offset: return False if after == SUN_EVENT_SUNRISE and utcnow < sunrise + after_offset: return False - elif after == SUN_EVENT_SUNSET and utcnow < sunset + after_offset: + if after == SUN_EVENT_SUNSET and utcnow < sunset + after_offset: return False return True diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py index 0bd490940a92..056d45ad6565 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -361,7 +361,7 @@ def temperature_unit(value) -> str: value = str(value).upper() if value == 'C': return TEMP_CELSIUS - elif value == 'F': + if value == 'F': return TEMP_FAHRENHEIT raise vol.Invalid('invalid temperature unit (expected C or F)') @@ -435,15 +435,14 @@ def socket_timeout(value): """ if value is None: return _GLOBAL_DEFAULT_TIMEOUT - else: - try: - float_value = float(value) - if float_value > 0.0: - return float_value - raise vol.Invalid('Invalid socket timeout value.' - ' float > 0.0 required.') - except Exception as _: - raise vol.Invalid('Invalid socket timeout: {err}'.format(err=_)) + try: + float_value = float(value) + if float_value > 0.0: + return float_value + raise vol.Invalid('Invalid socket timeout value.' + ' float > 0.0 required.') + except Exception as _: + raise vol.Invalid('Invalid socket timeout: {err}'.format(err=_)) # pylint: disable=no-value-for-parameter diff --git a/homeassistant/helpers/data_entry_flow.py b/homeassistant/helpers/data_entry_flow.py index 5a0b2ca56eaa..4f412eb58e79 100644 --- a/homeassistant/helpers/data_entry_flow.py +++ b/homeassistant/helpers/data_entry_flow.py @@ -23,7 +23,7 @@ class _BaseFlowManagerView(HomeAssistantView): data.pop('data') return data - elif result['type'] != data_entry_flow.RESULT_TYPE_FORM: + if result['type'] != data_entry_flow.RESULT_TYPE_FORM: return result import voluptuous_serialize diff --git a/homeassistant/helpers/deprecation.py b/homeassistant/helpers/deprecation.py index 73a094644398..8b621b2f01c6 100644 --- a/homeassistant/helpers/deprecation.py +++ b/homeassistant/helpers/deprecation.py @@ -33,8 +33,7 @@ def deprecated_substitute(substitute_name): # Return the old property return getattr(self, substitute_name) - else: - return func(self) + return func(self) return func_wrapper return decorator diff --git a/homeassistant/helpers/event.py b/homeassistant/helpers/event.py index 712b48da0d76..c8488fa3334d 100644 --- a/homeassistant/helpers/event.py +++ b/homeassistant/helpers/event.py @@ -374,7 +374,7 @@ def _process_state_match(parameter): if parameter is None or parameter == MATCH_ALL: return lambda _: True - elif isinstance(parameter, str) or not hasattr(parameter, '__iter__'): + if isinstance(parameter, str) or not hasattr(parameter, '__iter__'): return lambda state: state == parameter parameter = tuple(parameter) @@ -386,11 +386,11 @@ def _process_time_match(parameter): if parameter is None or parameter == MATCH_ALL: return lambda _: True - elif isinstance(parameter, str) and parameter.startswith('/'): + if isinstance(parameter, str) and parameter.startswith('/'): parameter = float(parameter[1:]) return lambda time: time % parameter == 0 - elif isinstance(parameter, str) or not hasattr(parameter, '__iter__'): + if isinstance(parameter, str) or not hasattr(parameter, '__iter__'): return lambda time: time == parameter parameter = tuple(parameter) diff --git a/homeassistant/helpers/script.py b/homeassistant/helpers/script.py index f2ae36e7fd08..a139be4b2607 100644 --- a/homeassistant/helpers/script.py +++ b/homeassistant/helpers/script.py @@ -119,7 +119,7 @@ class Script(): self.hass.async_add_job(self._change_listener) return - elif CONF_WAIT_TEMPLATE in action: + if CONF_WAIT_TEMPLATE in action: # Call ourselves in the future to continue work wait_template = action[CONF_WAIT_TEMPLATE] wait_template.hass = self.hass @@ -147,7 +147,7 @@ class Script(): return - elif CONF_CONDITION in action: + if CONF_CONDITION in action: if not self._async_check_condition(action, variables): break diff --git a/homeassistant/helpers/service.py b/homeassistant/helpers/service.py index e10d608fc62d..8aa3b553f3a4 100644 --- a/homeassistant/helpers/service.py +++ b/homeassistant/helpers/service.py @@ -103,12 +103,10 @@ def extract_entity_ids(hass, service_call, expand_group=True): return [ent_id for ent_id in group.expand_entity_ids(service_ent_id)] - else: + if isinstance(service_ent_id, str): + return [service_ent_id] - if isinstance(service_ent_id, str): - return [service_ent_id] - - return service_ent_id + return service_ent_id @bind_hass diff --git a/homeassistant/helpers/state.py b/homeassistant/helpers/state.py index b2360f3aca52..4a3f915e8105 100644 --- a/homeassistant/helpers/state.py +++ b/homeassistant/helpers/state.py @@ -214,9 +214,9 @@ def state_as_number(state): if state.state in (STATE_ON, STATE_LOCKED, STATE_ABOVE_HORIZON, STATE_OPEN, STATE_HOME, STATE_HEAT, STATE_COOL): return 1 - elif state.state in (STATE_OFF, STATE_UNLOCKED, STATE_UNKNOWN, - STATE_BELOW_HORIZON, STATE_CLOSED, STATE_NOT_HOME, - STATE_IDLE): + if state.state in (STATE_OFF, STATE_UNLOCKED, STATE_UNKNOWN, + STATE_BELOW_HORIZON, STATE_CLOSED, STATE_NOT_HOME, + STATE_IDLE): return 0 return float(state.state) diff --git a/homeassistant/helpers/template.py b/homeassistant/helpers/template.py index 4d09416398b3..ea620c9bccd1 100644 --- a/homeassistant/helpers/template.py +++ b/homeassistant/helpers/template.py @@ -51,7 +51,7 @@ def render_complex(value, variables=None): if isinstance(value, list): return [render_complex(item, variables) for item in value] - elif isinstance(value, dict): + if isinstance(value, dict): return {key: render_complex(item, variables) for key, item in value.items()} return value.async_render(variables) @@ -318,7 +318,7 @@ class TemplateMethods: if point_state is None: _LOGGER.warning("Closest:Unable to find state %s", args[0]) return None - elif not loc_helper.has_location(point_state): + if not loc_helper.has_location(point_state): _LOGGER.warning( "Closest:State does not contain valid location: %s", point_state) @@ -420,7 +420,7 @@ class TemplateMethods: """Return state or entity_id if given.""" if isinstance(entity_id_or_state, State): return entity_id_or_state - elif isinstance(entity_id_or_state, str): + if isinstance(entity_id_or_state, str): return self._hass.states.get(entity_id_or_state) return None diff --git a/homeassistant/remote.py b/homeassistant/remote.py index b8e6a862b46d..7147fab10806 100644 --- a/homeassistant/remote.py +++ b/homeassistant/remote.py @@ -117,9 +117,9 @@ class JSONEncoder(json.JSONEncoder): """ if isinstance(o, datetime): return o.isoformat() - elif isinstance(o, set): + if isinstance(o, set): return list(o) - elif hasattr(o, 'as_dict'): + if hasattr(o, 'as_dict'): return o.as_dict() return json.JSONEncoder.default(self, o) @@ -133,7 +133,7 @@ def validate_api(api): if req.status_code == 200: return APIStatus.OK - elif req.status_code == 401: + if req.status_code == 401: return APIStatus.INVALID_PASSWORD return APIStatus.UNKNOWN diff --git a/homeassistant/scripts/macos/__init__.py b/homeassistant/scripts/macos/__init__.py index 275a33627a97..6c6557897ee4 100644 --- a/homeassistant/scripts/macos/__init__.py +++ b/homeassistant/scripts/macos/__init__.py @@ -52,10 +52,10 @@ def run(args): if args[0] == 'install': install_osx() return 0 - elif args[0] == 'uninstall': + if args[0] == 'uninstall': uninstall_osx() return 0 - elif args[0] == 'restart': + if args[0] == 'restart': uninstall_osx() # A small delay is needed on some systems to let the unload finish. time.sleep(0.5) diff --git a/homeassistant/setup.py b/homeassistant/setup.py index 45482d5d14e7..0641a461130b 100644 --- a/homeassistant/setup.py +++ b/homeassistant/setup.py @@ -157,7 +157,7 @@ async def _async_setup_component(hass: core.HomeAssistant, if result is False: log_error("Component failed to initialize.") return False - elif result is not True: + if result is not True: log_error("Component did not return boolean if setup was successful. " "Disabling component.") loader.set_component(hass, domain, None) @@ -204,7 +204,7 @@ async def async_prepare_setup_platform(hass: core.HomeAssistant, config, return None # Already loaded - elif platform_path in hass.config.components: + if platform_path in hass.config.components: return platform try: diff --git a/homeassistant/util/__init__.py b/homeassistant/util/__init__.py index a1c0fb0024c6..6b539e991865 100644 --- a/homeassistant/util/__init__.py +++ b/homeassistant/util/__init__.py @@ -56,7 +56,7 @@ def repr_helper(inp: Any) -> str: return ", ".join( repr_helper(key)+"="+repr_helper(item) for key, item in inp.items()) - elif isinstance(inp, datetime): + if isinstance(inp, datetime): return as_local(inp).isoformat() return str(inp) diff --git a/homeassistant/util/dt.py b/homeassistant/util/dt.py index 0f07a90e9bbd..bae38f27ee25 100644 --- a/homeassistant/util/dt.py +++ b/homeassistant/util/dt.py @@ -65,7 +65,7 @@ def as_utc(dattim: dt.datetime) -> dt.datetime: """ if dattim.tzinfo == UTC: return dattim - elif dattim.tzinfo is None: + if dattim.tzinfo is None: dattim = DEFAULT_TIME_ZONE.localize(dattim) # type: ignore return dattim.astimezone(UTC) @@ -86,7 +86,7 @@ def as_local(dattim: dt.datetime) -> dt.datetime: """Convert a UTC datetime object to local time zone.""" if dattim.tzinfo == DEFAULT_TIME_ZONE: return dattim - elif dattim.tzinfo is None: + if dattim.tzinfo is None: dattim = UTC.localize(dattim) return dattim.astimezone(DEFAULT_TIME_ZONE) diff --git a/homeassistant/util/temperature.py b/homeassistant/util/temperature.py index 913d64569062..6e2b378b2180 100644 --- a/homeassistant/util/temperature.py +++ b/homeassistant/util/temperature.py @@ -29,6 +29,6 @@ def convert(temperature: float, from_unit: str, to_unit: str, if from_unit == to_unit: return temperature - elif from_unit == TEMP_CELSIUS: + if from_unit == TEMP_CELSIUS: return celsius_to_fahrenheit(temperature, interval) return fahrenheit_to_celsius(temperature, interval) diff --git a/homeassistant/util/yaml.py b/homeassistant/util/yaml.py index ddf7eb44601b..7ce16600e1b6 100644 --- a/homeassistant/util/yaml.py +++ b/homeassistant/util/yaml.py @@ -216,11 +216,10 @@ def _env_var_yaml(loader: SafeLineLoader, # Check for a default value if len(args) > 1: return os.getenv(args[0], ' '.join(args[1:])) - elif args[0] in os.environ: + if args[0] in os.environ: return os.environ[args[0]] - else: - _LOGGER.error("Environment variable %s not defined.", node.value) - raise HomeAssistantError(node.value) + _LOGGER.error("Environment variable %s not defined.", node.value) + raise HomeAssistantError(node.value) def _load_secret_yaml(secret_path: str) -> Dict: diff --git a/pylintrc b/pylintrc index d47437cb1218..1e9e490adfed 100644 --- a/pylintrc +++ b/pylintrc @@ -4,7 +4,6 @@ # duplicate-code - unavoidable # cyclic-import - doesn't test if both import on load # abstract-class-little-used - prevents from setting right foundation -# abstract-class-not-used - is flaky, should not show up but does # unused-argument - generic callbacks and setup methods create a lot of warnings # global-statement - used for the on-demand requirement installation # redefined-variable-type - this is Python, we're duck typing! @@ -14,7 +13,6 @@ # inconsistent-return-statements - doesn't handle raise disable= abstract-class-little-used, - abstract-class-not-used, abstract-method, cyclic-import, duplicate-code, diff --git a/tests/common.py b/tests/common.py index 3e8e813164e3..314799b185bf 100644 --- a/tests/common.py +++ b/tests/common.py @@ -496,14 +496,13 @@ class MockToggleDevice(entity.ToggleEntity): """Return the last call.""" if not self.calls: return None - elif method is None: + if method is None: return self.calls[-1] - else: - try: - return next(call for call in reversed(self.calls) - if call[0] == method) - except StopIteration: - return None + try: + return next(call for call in reversed(self.calls) + if call[0] == method) + except StopIteration: + return None class MockConfigEntry(config_entries.ConfigEntry): diff --git a/tests/components/alexa/test_smart_home.py b/tests/components/alexa/test_smart_home.py index ce4ec5aa146c..cf8535653a96 100644 --- a/tests/components/alexa/test_smart_home.py +++ b/tests/components/alexa/test_smart_home.py @@ -124,7 +124,7 @@ def discovery_test(device, hass, expected_endpoints=1): if expected_endpoints == 1: return endpoints[0] - elif expected_endpoints > 1: + if expected_endpoints > 1: return endpoints return None diff --git a/tests/components/camera/test_uvc.py b/tests/components/camera/test_uvc.py index 2de0782fd910..18292d32a024 100644 --- a/tests/components/camera/test_uvc.py +++ b/tests/components/camera/test_uvc.py @@ -45,8 +45,7 @@ class TestUVCSetup(unittest.TestCase): """Create a mock camera.""" if uuid == 'id3': return {'model': 'airCam'} - else: - return {'model': 'UVC'} + return {'model': 'UVC'} mock_remote.return_value.index.return_value = mock_cameras mock_remote.return_value.get_camera.side_effect = mock_get_camera diff --git a/tests/components/device_tracker/test_tomato.py b/tests/components/device_tracker/test_tomato.py index cce39ce43a79..0c20350a8459 100644 --- a/tests/components/device_tracker/test_tomato.py +++ b/tests/components/device_tracker/test_tomato.py @@ -22,9 +22,9 @@ def mock_session_response(*args, **kwargs): # Password: bar if args[0].headers['Authorization'] != 'Basic Zm9vOmJhcg==': return MockSessionResponse(None, 401) - elif "gimmie_bad_data" in args[0].body: + if "gimmie_bad_data" in args[0].body: return MockSessionResponse('This shouldn\'t (wldev = be here.;', 200) - elif "gimmie_good_data" in args[0].body: + if "gimmie_good_data" in args[0].body: return MockSessionResponse( "wldev = [ ['eth1','F4:F5:D8:AA:AA:AA'," "-42,5500,1000,7043,0],['eth1','58:EF:68:00:00:00'," diff --git a/tests/components/device_tracker/test_xiaomi.py b/tests/components/device_tracker/test_xiaomi.py index bdd921f395f7..0705fb2c3997 100644 --- a/tests/components/device_tracker/test_xiaomi.py +++ b/tests/components/device_tracker/test_xiaomi.py @@ -55,21 +55,21 @@ def mocked_requests(*args, **kwargs): "code": "401", "msg": "Invalid token" }, 200) - elif data and data.get('username', None) == TOKEN_TIMEOUT_USERNAME: + if data and data.get('username', None) == TOKEN_TIMEOUT_USERNAME: # deliver an expired token return MockResponse({ "url": "/cgi-bin/luci/;stok=ef5860/web/home", "token": "timedOut", "code": "0" }, 200) - elif str(args[0]).startswith(URL_AUTHORIZE): + if str(args[0]).startswith(URL_AUTHORIZE): # deliver an authorized token return MockResponse({ "url": "/cgi-bin/luci/;stok=ef5860/web/home", "token": "ef5860", "code": "0" }, 200) - elif str(args[0]).endswith("timedOut/" + URL_LIST_END) \ + if str(args[0]).endswith("timedOut/" + URL_LIST_END) \ and FIRST_CALL is True: FIRST_CALL = False # deliver an error when called with expired token @@ -77,7 +77,7 @@ def mocked_requests(*args, **kwargs): "code": "401", "msg": "Invalid token" }, 200) - elif str(args[0]).endswith(URL_LIST_END): + if str(args[0]).endswith(URL_LIST_END): # deliver the device list return MockResponse({ "mac": "1C:98:EC:0E:D5:A4", @@ -149,8 +149,7 @@ def mocked_requests(*args, **kwargs): ], "code": 0 }, 200) - else: - _LOGGER.debug('UNKNOWN ROUTE') + _LOGGER.debug('UNKNOWN ROUTE') class TestXiaomiDeviceScanner(unittest.TestCase): diff --git a/tests/components/light/test_hue.py b/tests/components/light/test_hue.py index a1e3867f9c3c..db8d7e5f1e19 100644 --- a/tests/components/light/test_hue.py +++ b/tests/components/light/test_hue.py @@ -182,7 +182,7 @@ def mock_bridge(hass): if path == 'lights': return bridge.mock_light_responses.popleft() - elif path == 'groups': + if path == 'groups': return bridge.mock_group_responses.popleft() return None diff --git a/tests/components/notify/test_group.py b/tests/components/notify/test_group.py index a847de51142f..8e7ef4348f7d 100644 --- a/tests/components/notify/test_group.py +++ b/tests/components/notify/test_group.py @@ -26,8 +26,7 @@ class TestNotifyGroup(unittest.TestCase): def mock_get_service(hass, config, discovery_info=None): if config['name'] == 'demo1': return self.service1 - else: - return self.service2 + return self.service2 with assert_setup_component(2), \ patch.object(demo, 'get_service', mock_get_service): diff --git a/tests/components/recorder/models_original.py b/tests/components/recorder/models_original.py index 31ec5ee7ed73..990414d77135 100644 --- a/tests/components/recorder/models_original.py +++ b/tests/components/recorder/models_original.py @@ -157,7 +157,6 @@ def _process_timestamp(ts): """Process a timestamp into datetime object.""" if ts is None: return None - elif ts.tzinfo is None: + if ts.tzinfo is None: return dt_util.UTC.localize(ts) - else: - return dt_util.as_utc(ts) + return dt_util.as_utc(ts) diff --git a/tests/components/sensor/test_radarr.py b/tests/components/sensor/test_radarr.py index 94eeafad7b19..0d6aca9d0b70 100644 --- a/tests/components/sensor/test_radarr.py +++ b/tests/components/sensor/test_radarr.py @@ -83,7 +83,7 @@ def mocked_requests_get(*args, **kwargs): "id": 12 } ], 200) - elif 'api/command' in url: + if 'api/command' in url: return MockResponse([ { "name": "RescanMovie", @@ -94,7 +94,7 @@ def mocked_requests_get(*args, **kwargs): "id": 24 } ], 200) - elif 'api/movie' in url: + if 'api/movie' in url: return MockResponse([ { "title": "Assassin's Creed", @@ -149,7 +149,7 @@ def mocked_requests_get(*args, **kwargs): "id": 1 } ], 200) - elif 'api/diskspace' in url: + if 'api/diskspace' in url: return MockResponse([ { "path": "/data", @@ -158,7 +158,7 @@ def mocked_requests_get(*args, **kwargs): "totalSpace": 499738734592 } ], 200) - elif 'api/system/status' in url: + if 'api/system/status' in url: return MockResponse({ "version": "0.2.0.210", "buildTime": "2017-01-22T23:12:49Z", @@ -182,10 +182,9 @@ def mocked_requests_get(*args, **kwargs): "(Stable 4.6.1.3/abb06f1 " "Mon Oct 3 07:57:59 UTC 2016)") }, 200) - else: - return MockResponse({ - "error": "Unauthorized" - }, 401) + return MockResponse({ + "error": "Unauthorized" + }, 401) class TestRadarrSetup(unittest.TestCase): diff --git a/tests/components/sensor/test_sonarr.py b/tests/components/sensor/test_sonarr.py index 9e2050e850c2..275bb4a1e8b1 100644 --- a/tests/components/sensor/test_sonarr.py +++ b/tests/components/sensor/test_sonarr.py @@ -139,7 +139,7 @@ def mocked_requests_get(*args, **kwargs): "id": 14402 } ], 200) - elif 'api/command' in url: + if 'api/command' in url: return MockResponse([ { "name": "RescanSeries", @@ -150,7 +150,7 @@ def mocked_requests_get(*args, **kwargs): "id": 24 } ], 200) - elif 'api/wanted/missing' in url or 'totalRecords' in url: + if 'api/wanted/missing' in url or 'totalRecords' in url: return MockResponse( { "page": 1, @@ -325,7 +325,7 @@ def mocked_requests_get(*args, **kwargs): } ] }, 200) - elif 'api/queue' in url: + if 'api/queue' in url: return MockResponse([ { "series": { @@ -449,7 +449,7 @@ def mocked_requests_get(*args, **kwargs): "id": 1503378561 } ], 200) - elif 'api/series' in url: + if 'api/series' in url: return MockResponse([ { "title": "Marvel's Daredevil", @@ -540,7 +540,7 @@ def mocked_requests_get(*args, **kwargs): "id": 7 } ], 200) - elif 'api/diskspace' in url: + if 'api/diskspace' in url: return MockResponse([ { "path": "/data", @@ -549,7 +549,7 @@ def mocked_requests_get(*args, **kwargs): "totalSpace": 499738734592 } ], 200) - elif 'api/system/status' in url: + if 'api/system/status' in url: return MockResponse({ "version": "2.0.0.1121", "buildTime": "2014-02-08T20:49:36.5560392Z", @@ -568,10 +568,9 @@ def mocked_requests_get(*args, **kwargs): "startOfWeek": 0, "urlBase": "" }, 200) - else: - return MockResponse({ - "error": "Unauthorized" - }, 401) + return MockResponse({ + "error": "Unauthorized" + }, 401) class TestSonarrSetup(unittest.TestCase): diff --git a/tests/components/switch/test_flux.py b/tests/components/switch/test_flux.py index 61e665f265c4..155ed85dac2d 100644 --- a/tests/components/switch/test_flux.py +++ b/tests/components/switch/test_flux.py @@ -92,8 +92,7 @@ class TestSwitchFlux(unittest.TestCase): def event_date(hass, event, now=None): if event == 'sunrise': return sunrise_time - else: - return sunset_time + return sunset_time with patch('homeassistant.util.dt.now', return_value=test_time): with patch('homeassistant.helpers.sun.get_astral_event_date', @@ -134,8 +133,7 @@ class TestSwitchFlux(unittest.TestCase): def event_date(hass, event, now=None): if event == 'sunrise': return sunrise_time - else: - return sunset_time + return sunset_time with patch('homeassistant.util.dt.now', return_value=test_time): with patch('homeassistant.helpers.sun.get_astral_event_date', @@ -181,8 +179,7 @@ class TestSwitchFlux(unittest.TestCase): def event_date(hass, event, now=None): if event == 'sunrise': return sunrise_time - else: - return sunset_time + return sunset_time with patch('homeassistant.util.dt.now', return_value=test_time): with patch('homeassistant.helpers.sun.get_astral_event_date', @@ -228,8 +225,7 @@ class TestSwitchFlux(unittest.TestCase): def event_date(hass, event, now=None): if event == 'sunrise': return sunrise_time - else: - return sunset_time + return sunset_time with patch('homeassistant.util.dt.now', return_value=test_time): with patch('homeassistant.helpers.sun.get_astral_event_date', @@ -276,8 +272,7 @@ class TestSwitchFlux(unittest.TestCase): def event_date(hass, event, now=None): if event == 'sunrise': return sunrise_time - else: - return sunset_time + return sunset_time with patch('homeassistant.util.dt.now', return_value=test_time): with patch('homeassistant.helpers.sun.get_astral_event_date', @@ -323,8 +318,7 @@ class TestSwitchFlux(unittest.TestCase): def event_date(hass, event, now=None): if event == 'sunrise': return sunrise_time - else: - return sunset_time + return sunset_time with patch('homeassistant.util.dt.now', return_value=test_time): with patch('homeassistant.helpers.sun.get_astral_event_date', @@ -374,8 +368,7 @@ class TestSwitchFlux(unittest.TestCase): def event_date(hass, event, now=None): if event == 'sunrise': return sunrise_time - else: - return sunset_time + return sunset_time with patch('homeassistant.util.dt.now', return_value=test_time): with patch('homeassistant.helpers.sun.get_astral_event_date', @@ -426,8 +419,7 @@ class TestSwitchFlux(unittest.TestCase): def event_date(hass, event, now=None): if event == 'sunrise': return sunrise_time - else: - return sunset_time + return sunset_time with patch('homeassistant.util.dt.now', return_value=test_time): with patch('homeassistant.helpers.sun.get_astral_event_date', @@ -477,8 +469,7 @@ class TestSwitchFlux(unittest.TestCase): def event_date(hass, event, now=None): if event == 'sunrise': return sunrise_time - else: - return sunset_time + return sunset_time with patch('homeassistant.util.dt.now', return_value=test_time): with patch('homeassistant.helpers.sun.get_astral_event_date', @@ -528,8 +519,7 @@ class TestSwitchFlux(unittest.TestCase): def event_date(hass, event, now=None): if event == 'sunrise': return sunrise_time - else: - return sunset_time + return sunset_time with patch('homeassistant.util.dt.now', return_value=test_time): with patch('homeassistant.helpers.sun.get_astral_event_date', @@ -579,8 +569,7 @@ class TestSwitchFlux(unittest.TestCase): def event_date(hass, event, now=None): if event == 'sunrise': return sunrise_time - else: - return sunset_time + return sunset_time with patch('homeassistant.util.dt.now', return_value=test_time): with patch('homeassistant.helpers.sun.get_astral_event_date', @@ -627,8 +616,7 @@ class TestSwitchFlux(unittest.TestCase): def event_date(hass, event, now=None): if event == 'sunrise': return sunrise_time - else: - return sunset_time + return sunset_time with patch('homeassistant.util.dt.now', return_value=test_time): with patch('homeassistant.helpers.sun.get_astral_event_date', @@ -677,8 +665,7 @@ class TestSwitchFlux(unittest.TestCase): def event_date(hass, event, now=None): if event == 'sunrise': return sunrise_time - else: - return sunset_time + return sunset_time with patch('homeassistant.util.dt.now', return_value=test_time): with patch('homeassistant.helpers.sun.get_astral_event_date', @@ -739,9 +726,8 @@ class TestSwitchFlux(unittest.TestCase): if event == 'sunrise': print('sunrise {}'.format(sunrise_time)) return sunrise_time - else: - print('sunset {}'.format(sunset_time)) - return sunset_time + print('sunset {}'.format(sunset_time)) + return sunset_time with patch('homeassistant.util.dt.now', return_value=test_time): with patch('homeassistant.helpers.sun.get_astral_event_date', @@ -793,8 +779,7 @@ class TestSwitchFlux(unittest.TestCase): def event_date(hass, event, now=None): if event == 'sunrise': return sunrise_time - else: - return sunset_time + return sunset_time with patch('homeassistant.util.dt.now', return_value=test_time): with patch('homeassistant.helpers.sun.get_astral_event_date', @@ -838,8 +823,7 @@ class TestSwitchFlux(unittest.TestCase): def event_date(hass, event, now=None): if event == 'sunrise': return sunrise_time - else: - return sunset_time + return sunset_time with patch('homeassistant.util.dt.now', return_value=test_time): with patch('homeassistant.helpers.sun.get_astral_event_date', diff --git a/tests/components/test_graphite.py b/tests/components/test_graphite.py index 280704fdc318..892fe5b5f4d3 100644 --- a/tests/components/test_graphite.py +++ b/tests/components/test_graphite.py @@ -224,13 +224,12 @@ class TestGraphite(unittest.TestCase): def fake_get(): if len(runs) >= 2: return self.gf._quit_object - elif runs: + if runs: runs.append(1) return mock.MagicMock(event_type='somethingelse', data={'new_event': None}) - else: - runs.append(1) - return event + runs.append(1) + return event with mock.patch.object(self.gf, '_queue') as mock_queue: with mock.patch.object(self.gf, '_report_attributes') as mock_r: From 140a874917df5af1fc0a21ce7853c83fced40ed6 Mon Sep 17 00:00:00 2001 From: Andrey Date: Mon, 23 Jul 2018 11:24:39 +0300 Subject: [PATCH 054/113] Add typing to homeassistant/*.py and homeassistant/util/ (#15569) * Add typing to homeassistant/*.py and homeassistant/util/ * Fix wrong merge * Restore iterable in OrderedSet * Fix tests --- homeassistant/__main__.py | 6 +- homeassistant/bootstrap.py | 6 +- homeassistant/components/rachio.py | 4 +- homeassistant/config.py | 85 ++++---- homeassistant/config_entries.py | 12 +- homeassistant/core.py | 250 +++++++++++++---------- homeassistant/data_entry_flow.py | 32 +-- homeassistant/loader.py | 78 ++++--- homeassistant/monkey_patch.py | 11 +- homeassistant/remote.py | 53 ++--- homeassistant/requirements.py | 11 +- homeassistant/setup.py | 22 +- homeassistant/util/__init__.py | 75 +++---- homeassistant/util/async_.py | 41 ++-- homeassistant/util/color.py | 38 ++-- homeassistant/util/decorator.py | 6 +- homeassistant/util/dt.py | 10 +- homeassistant/util/json.py | 2 +- homeassistant/util/location.py | 4 +- homeassistant/util/logging.py | 37 ++-- homeassistant/util/package.py | 2 +- homeassistant/util/ssl.py | 4 +- homeassistant/util/yaml.py | 86 +++++--- mypy.ini | 7 + tests/components/automation/test_init.py | 22 +- tests/components/group/test_init.py | 6 +- tests/components/test_script.py | 6 +- 27 files changed, 532 insertions(+), 384 deletions(-) diff --git a/homeassistant/__main__.py b/homeassistant/__main__.py index 7a5345a1a73e..65b1cd2ae1a6 100644 --- a/homeassistant/__main__.py +++ b/homeassistant/__main__.py @@ -20,7 +20,7 @@ from homeassistant.const import ( ) -def attempt_use_uvloop(): +def attempt_use_uvloop() -> None: """Attempt to use uvloop.""" import asyncio @@ -280,8 +280,8 @@ def setup_and_run_hass(config_dir: str, # Imported here to avoid importing asyncio before monkey patch from homeassistant.util.async_ import run_callback_threadsafe - def open_browser(event): - """Open the webinterface in a browser.""" + def open_browser(_: Any) -> None: + """Open the web interface in a browser.""" if hass.config.api is not None: # type: ignore import webbrowser webbrowser.open(hass.config.api.base_url) # type: ignore diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index d832dda47547..43c7168dd2e8 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -221,8 +221,8 @@ async def async_from_config_file(config_path: str, @core.callback def async_enable_logging(hass: core.HomeAssistant, verbose: bool = False, - log_rotate_days=None, - log_file=None, + log_rotate_days: Optional[int] = None, + log_file: Optional[str] = None, log_no_color: bool = False) -> None: """Set up the logging. @@ -291,7 +291,7 @@ def async_enable_logging(hass: core.HomeAssistant, async_handler = AsyncHandler(hass.loop, err_handler) - async def async_stop_async_handler(event): + async def async_stop_async_handler(_: Any) -> None: """Cleanup async handler.""" logging.getLogger('').removeHandler(async_handler) # type: ignore await async_handler.async_close(blocking=True) diff --git a/homeassistant/components/rachio.py b/homeassistant/components/rachio.py index 7162913087d7..0e67e15d5c09 100644 --- a/homeassistant/components/rachio.py +++ b/homeassistant/components/rachio.py @@ -9,7 +9,7 @@ import logging from aiohttp import web import voluptuous as vol - +from typing import Optional from homeassistant.auth.util import generate_secret from homeassistant.components.http import HomeAssistantView from homeassistant.const import CONF_API_KEY, EVENT_HOMEASSISTANT_STOP, URL_API @@ -241,7 +241,7 @@ class RachioIro: # Only enabled zones return [z for z in self._zones if z[KEY_ENABLED]] - def get_zone(self, zone_id) -> dict or None: + def get_zone(self, zone_id) -> Optional[dict]: """Return the zone with the given ID.""" for zone in self.list_zones(include_disabled=True): if zone[KEY_ID] == zone_id: diff --git a/homeassistant/config.py b/homeassistant/config.py index d458ce44d370..6120a20fd634 100644 --- a/homeassistant/config.py +++ b/homeassistant/config.py @@ -7,8 +7,9 @@ import os import re import shutil # pylint: disable=unused-import -from typing import Any, Tuple, Optional # noqa: F401 - +from typing import ( # noqa: F401 + Any, Tuple, Optional, Dict, List, Union, Callable) +from types import ModuleType import voluptuous as vol from voluptuous.humanize import humanize_error @@ -21,7 +22,7 @@ from homeassistant.const import ( CONF_UNIT_SYSTEM_IMPERIAL, CONF_TEMPERATURE_UNIT, TEMP_CELSIUS, __version__, CONF_CUSTOMIZE, CONF_CUSTOMIZE_DOMAIN, CONF_CUSTOMIZE_GLOB, CONF_WHITELIST_EXTERNAL_DIRS, CONF_AUTH_PROVIDERS, CONF_TYPE) -from homeassistant.core import callback, DOMAIN as CONF_CORE +from homeassistant.core import callback, DOMAIN as CONF_CORE, HomeAssistant from homeassistant.exceptions import HomeAssistantError from homeassistant.loader import get_component, get_platform from homeassistant.util.yaml import load_yaml, SECRET_YAML @@ -193,7 +194,7 @@ def ensure_config_exists(config_dir: str, detect_location: bool = True)\ return config_path -def create_default_config(config_dir: str, detect_location=True)\ +def create_default_config(config_dir: str, detect_location: bool = True)\ -> Optional[str]: """Create a default configuration file in given configuration directory. @@ -276,7 +277,7 @@ def create_default_config(config_dir: str, detect_location=True)\ return None -async def async_hass_config_yaml(hass): +async def async_hass_config_yaml(hass: HomeAssistant) -> Dict: """Load YAML from a Home Assistant configuration file. This function allow a component inside the asyncio loop to reload its @@ -284,23 +285,26 @@ async def async_hass_config_yaml(hass): This method is a coroutine. """ - def _load_hass_yaml_config(): + def _load_hass_yaml_config() -> Dict: path = find_config_file(hass.config.config_dir) - conf = load_yaml_config_file(path) - return conf + if path is None: + raise HomeAssistantError( + "Config file not found in: {}".format(hass.config.config_dir)) + return load_yaml_config_file(path) - conf = await hass.async_add_job(_load_hass_yaml_config) - return conf + return await hass.async_add_executor_job(_load_hass_yaml_config) -def find_config_file(config_dir: str) -> Optional[str]: +def find_config_file(config_dir: Optional[str]) -> Optional[str]: """Look in given directory for supported configuration files.""" + if config_dir is None: + return None config_path = os.path.join(config_dir, YAML_CONFIG_FILE) return config_path if os.path.isfile(config_path) else None -def load_yaml_config_file(config_path): +def load_yaml_config_file(config_path: str) -> Dict[Any, Any]: """Parse a YAML configuration file. This method needs to run in an executor. @@ -323,7 +327,7 @@ def load_yaml_config_file(config_path): return conf_dict -def process_ha_config_upgrade(hass): +def process_ha_config_upgrade(hass: HomeAssistant) -> None: """Upgrade configuration if necessary. This method needs to run in an executor. @@ -360,7 +364,8 @@ def process_ha_config_upgrade(hass): @callback -def async_log_exception(ex, domain, config, hass): +def async_log_exception(ex: vol.Invalid, domain: str, config: Dict, + hass: HomeAssistant) -> None: """Log an error for configuration validation. This method must be run in the event loop. @@ -371,7 +376,7 @@ def async_log_exception(ex, domain, config, hass): @callback -def _format_config_error(ex, domain, config): +def _format_config_error(ex: vol.Invalid, domain: str, config: Dict) -> str: """Generate log exception for configuration validation. This method must be run in the event loop. @@ -396,7 +401,8 @@ def _format_config_error(ex, domain, config): return message -async def async_process_ha_core_config(hass, config): +async def async_process_ha_core_config( + hass: HomeAssistant, config: Dict) -> None: """Process the [homeassistant] section from the configuration. This method is a coroutine. @@ -405,12 +411,12 @@ async def async_process_ha_core_config(hass, config): # Only load auth during startup. if not hasattr(hass, 'auth'): - hass.auth = await auth.auth_manager_from_config( - hass, config.get(CONF_AUTH_PROVIDERS, [])) + setattr(hass, 'auth', await auth.auth_manager_from_config( + hass, config.get(CONF_AUTH_PROVIDERS, []))) hac = hass.config - def set_time_zone(time_zone_str): + def set_time_zone(time_zone_str: Optional[str]) -> None: """Help to set the time zone.""" if time_zone_str is None: return @@ -430,11 +436,10 @@ async def async_process_ha_core_config(hass, config): if key in config: setattr(hac, attr, config[key]) - if CONF_TIME_ZONE in config: - set_time_zone(config.get(CONF_TIME_ZONE)) + set_time_zone(config.get(CONF_TIME_ZONE)) # Init whitelist external dir - hac.whitelist_external_dirs = set((hass.config.path('www'),)) + hac.whitelist_external_dirs = {hass.config.path('www')} if CONF_WHITELIST_EXTERNAL_DIRS in config: hac.whitelist_external_dirs.update( set(config[CONF_WHITELIST_EXTERNAL_DIRS])) @@ -484,12 +489,12 @@ async def async_process_ha_core_config(hass, config): hac.time_zone, hac.elevation): return - discovered = [] + discovered = [] # type: List[Tuple[str, Any]] # If we miss some of the needed values, auto detect them if None in (hac.latitude, hac.longitude, hac.units, hac.time_zone): - info = await hass.async_add_job( + info = await hass.async_add_executor_job( loc_util.detect_location_info) if info is None: @@ -515,7 +520,7 @@ async def async_process_ha_core_config(hass, config): if hac.elevation is None and hac.latitude is not None and \ hac.longitude is not None: - elevation = await hass.async_add_job( + elevation = await hass.async_add_executor_job( loc_util.elevation, hac.latitude, hac.longitude) hac.elevation = elevation discovered.append(('elevation', elevation)) @@ -526,7 +531,8 @@ async def async_process_ha_core_config(hass, config): ", ".join('{}: {}'.format(key, val) for key, val in discovered)) -def _log_pkg_error(package, component, config, message): +def _log_pkg_error( + package: str, component: str, config: Dict, message: str) -> None: """Log an error while merging packages.""" message = "Package {} setup failed. Component {} {}".format( package, component, message) @@ -539,12 +545,13 @@ def _log_pkg_error(package, component, config, message): _LOGGER.error(message) -def _identify_config_schema(module): +def _identify_config_schema(module: ModuleType) -> \ + Tuple[Optional[str], Optional[Dict]]: """Extract the schema and identify list or dict based.""" try: - schema = module.CONFIG_SCHEMA.schema[module.DOMAIN] + schema = module.CONFIG_SCHEMA.schema[module.DOMAIN] # type: ignore except (AttributeError, KeyError): - return (None, None) + return None, None t_schema = str(schema) if t_schema.startswith('{'): return ('dict', schema) @@ -553,9 +560,10 @@ def _identify_config_schema(module): return '', schema -def _recursive_merge(conf, package): +def _recursive_merge( + conf: Dict[str, Any], package: Dict[str, Any]) -> Union[bool, str]: """Merge package into conf, recursively.""" - error = False + error = False # type: Union[bool, str] for key, pack_conf in package.items(): if isinstance(pack_conf, dict): if not pack_conf: @@ -576,8 +584,8 @@ def _recursive_merge(conf, package): return error -def merge_packages_config(hass, config, packages, - _log_pkg_error=_log_pkg_error): +def merge_packages_config(hass: HomeAssistant, config: Dict, packages: Dict, + _log_pkg_error: Callable = _log_pkg_error) -> Dict: """Merge packages into the top-level configuration. Mutate config.""" # pylint: disable=too-many-nested-blocks PACKAGES_CONFIG_SCHEMA(packages) @@ -641,7 +649,8 @@ def merge_packages_config(hass, config, packages, @callback -def async_process_component_config(hass, config, domain): +def async_process_component_config( + hass: HomeAssistant, config: Dict, domain: str) -> Optional[Dict]: """Check component configuration and return processed configuration. Returns None on error. @@ -703,14 +712,14 @@ def async_process_component_config(hass, config, domain): return config -async def async_check_ha_config_file(hass): +async def async_check_ha_config_file(hass: HomeAssistant) -> Optional[str]: """Check if Home Assistant configuration file is valid. This method is a coroutine. """ from homeassistant.scripts.check_config import check_ha_config_file - res = await hass.async_add_job( + res = await hass.async_add_executor_job( check_ha_config_file, hass) if not res.errors: @@ -719,7 +728,9 @@ async def async_check_ha_config_file(hass): @callback -def async_notify_setup_error(hass, component, display_link=False): +def async_notify_setup_error( + hass: HomeAssistant, component: str, + display_link: bool = False) -> None: """Print a persistent notification. This method must be run in the event loop. diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index 0fc66174c668..8e2bb3fa5df9 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -113,10 +113,10 @@ the flow from the config panel. import logging import uuid -from typing import Set # noqa pylint: disable=unused-import +from typing import Set, Optional # noqa pylint: disable=unused-import from homeassistant import data_entry_flow -from homeassistant.core import callback +from homeassistant.core import callback, HomeAssistant from homeassistant.exceptions import HomeAssistantError from homeassistant.setup import async_setup_component, async_process_deps_reqs from homeassistant.util.decorator import Registry @@ -164,8 +164,9 @@ class ConfigEntry: __slots__ = ('entry_id', 'version', 'domain', 'title', 'data', 'source', 'state') - def __init__(self, version, domain, title, data, source, entry_id=None, - state=ENTRY_STATE_NOT_LOADED): + def __init__(self, version: str, domain: str, title: str, data: dict, + source: str, entry_id: Optional[str] = None, + state: str = ENTRY_STATE_NOT_LOADED) -> None: """Initialize a config entry.""" # Unique id of the config entry self.entry_id = entry_id or uuid.uuid4().hex @@ -188,7 +189,8 @@ class ConfigEntry: # State of the entry (LOADED, NOT_LOADED) self.state = state - async def async_setup(self, hass, *, component=None): + async def async_setup( + self, hass: HomeAssistant, *, component=None) -> None: """Set up an entry.""" if component is None: component = getattr(hass.components, self.domain) diff --git a/homeassistant/core.py b/homeassistant/core.py index f868c52cfb00..a7684d130ae4 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -4,9 +4,9 @@ Core components of Home Assistant. Home Assistant is a Home Automation framework for observing the state of entities and react to changes. """ -# pylint: disable=unused-import import asyncio from concurrent.futures import ThreadPoolExecutor +import datetime import enum import logging import os @@ -17,9 +17,10 @@ import threading from time import monotonic from types import MappingProxyType +# pylint: disable=unused-import from typing import ( # NOQA Optional, Any, Callable, List, TypeVar, Dict, Coroutine, Set, - TYPE_CHECKING) + TYPE_CHECKING, Awaitable, Iterator) from async_timeout import timeout import voluptuous as vol @@ -44,11 +45,13 @@ from homeassistant.util import location from homeassistant.util.unit_system import UnitSystem, METRIC_SYSTEM # NOQA # Typing imports that create a circular dependency -# pylint: disable=using-constant-test,unused-import +# pylint: disable=using-constant-test if TYPE_CHECKING: - from homeassistant.config_entries import ConfigEntries # noqa + from homeassistant.config_entries import ConfigEntries # noqa T = TypeVar('T') +CALLABLE_T = TypeVar('CALLABLE_T', bound=Callable) +CALLBACK_TYPE = Callable[[], None] DOMAIN = 'homeassistant' @@ -79,7 +82,7 @@ def valid_state(state: str) -> bool: return len(state) < 256 -def callback(func: Callable[..., T]) -> Callable[..., T]: +def callback(func: CALLABLE_T) -> CALLABLE_T: """Annotation to mark method as safe to call from within the event loop.""" setattr(func, '_hass_callback', True) return func @@ -91,7 +94,7 @@ def is_callback(func: Callable[..., Any]) -> bool: @callback -def async_loop_exception_handler(loop, context): +def async_loop_exception_handler(_: Any, context: Dict) -> None: """Handle all exception inside the core loop.""" kwargs = {} exception = context.get('exception') @@ -119,7 +122,9 @@ class CoreState(enum.Enum): class HomeAssistant: """Root object of the Home Assistant home automation.""" - def __init__(self, loop=None): + def __init__( + self, + loop: Optional[asyncio.events.AbstractEventLoop] = None) -> None: """Initialize new Home Assistant object.""" if sys.platform == 'win32': self.loop = loop or asyncio.ProactorEventLoop() @@ -170,7 +175,7 @@ class HomeAssistant: self.loop.close() return self.exit_code - async def async_start(self): + async def async_start(self) -> None: """Finalize startup from inside the event loop. This method is a coroutine. @@ -178,8 +183,7 @@ class HomeAssistant: _LOGGER.info("Starting Home Assistant") self.state = CoreState.starting - # pylint: disable=protected-access - self.loop._thread_ident = threading.get_ident() + setattr(self.loop, '_thread_ident', threading.get_ident()) self.bus.async_fire(EVENT_HOMEASSISTANT_START) try: @@ -230,7 +234,8 @@ class HomeAssistant: elif asyncio.iscoroutinefunction(target): task = self.loop.create_task(target(*args)) else: - task = self.loop.run_in_executor(None, target, *args) + task = self.loop.run_in_executor( # type: ignore + None, target, *args) # If a task is scheduled if self._track_task and task is not None: @@ -256,11 +261,11 @@ class HomeAssistant: @callback def async_add_executor_job( self, - target: Callable[..., Any], - *args: Any) -> asyncio.Future: + target: Callable[..., T], + *args: Any) -> Awaitable[T]: """Add an executor job from within the event loop.""" - task = self.loop.run_in_executor( # type: ignore - None, target, *args) # type: asyncio.Future + task = self.loop.run_in_executor( + None, target, *args) # If a task is scheduled if self._track_task: @@ -269,12 +274,12 @@ class HomeAssistant: return task @callback - def async_track_tasks(self): + def async_track_tasks(self) -> None: """Track tasks so you can wait for all tasks to be done.""" self._track_task = True @callback - def async_stop_track_tasks(self): + def async_stop_track_tasks(self) -> None: """Stop track tasks so you can't wait for all tasks to be done.""" self._track_task = False @@ -297,7 +302,7 @@ class HomeAssistant: run_coroutine_threadsafe( self.async_block_till_done(), loop=self.loop).result() - async def async_block_till_done(self): + async def async_block_till_done(self) -> None: """Block till all pending work is done.""" # To flush out any call_soon_threadsafe await asyncio.sleep(0, loop=self.loop) @@ -342,9 +347,9 @@ class EventOrigin(enum.Enum): local = 'LOCAL' remote = 'REMOTE' - def __str__(self): + def __str__(self) -> str: """Return the event.""" - return self.value + return self.value # type: ignore class Event: @@ -352,15 +357,16 @@ class Event: __slots__ = ['event_type', 'data', 'origin', 'time_fired'] - def __init__(self, event_type, data=None, origin=EventOrigin.local, - time_fired=None): + def __init__(self, event_type: str, data: Optional[Dict] = None, + origin: EventOrigin = EventOrigin.local, + time_fired: Optional[int] = None) -> None: """Initialize a new event.""" self.event_type = event_type self.data = data or {} self.origin = origin self.time_fired = time_fired or dt_util.utcnow() - def as_dict(self): + def as_dict(self) -> Dict: """Create a dict representation of this Event. Async friendly. @@ -372,7 +378,7 @@ class Event: 'time_fired': self.time_fired, } - def __repr__(self): + def __repr__(self) -> str: """Return the representation.""" # pylint: disable=maybe-no-member if self.data: @@ -383,9 +389,9 @@ class Event: return "".format(self.event_type, str(self.origin)[0]) - def __eq__(self, other): + def __eq__(self, other: Any) -> bool: """Return the comparison.""" - return (self.__class__ == other.__class__ and + return (self.__class__ == other.__class__ and # type: ignore self.event_type == other.event_type and self.data == other.data and self.origin == other.origin and @@ -401,7 +407,7 @@ class EventBus: self._hass = hass @callback - def async_listeners(self): + def async_listeners(self) -> Dict[str, int]: """Return dictionary with events and the number of listeners. This method must be run in the event loop. @@ -410,20 +416,21 @@ class EventBus: for key in self._listeners} @property - def listeners(self): + def listeners(self) -> Dict[str, int]: """Return dictionary with events and the number of listeners.""" - return run_callback_threadsafe( + return run_callback_threadsafe( # type: ignore self._hass.loop, self.async_listeners ).result() - def fire(self, event_type: str, event_data=None, origin=EventOrigin.local): + def fire(self, event_type: str, event_data: Optional[Dict] = None, + origin: EventOrigin = EventOrigin.local) -> None: """Fire an event.""" self._hass.loop.call_soon_threadsafe( self.async_fire, event_type, event_data, origin) @callback - def async_fire(self, event_type: str, event_data=None, - origin=EventOrigin.local): + def async_fire(self, event_type: str, event_data: Optional[Dict] = None, + origin: EventOrigin = EventOrigin.local) -> None: """Fire an event. This method must be run in the event loop. @@ -447,7 +454,8 @@ class EventBus: for func in listeners: self._hass.async_add_job(func, event) - def listen(self, event_type, listener): + def listen( + self, event_type: str, listener: Callable) -> CALLBACK_TYPE: """Listen for all events or events of a specific type. To listen to all events specify the constant ``MATCH_ALL`` @@ -456,7 +464,7 @@ class EventBus: async_remove_listener = run_callback_threadsafe( self._hass.loop, self.async_listen, event_type, listener).result() - def remove_listener(): + def remove_listener() -> None: """Remove the listener.""" run_callback_threadsafe( self._hass.loop, async_remove_listener).result() @@ -464,7 +472,8 @@ class EventBus: return remove_listener @callback - def async_listen(self, event_type, listener): + def async_listen( + self, event_type: str, listener: Callable) -> CALLBACK_TYPE: """Listen for all events or events of a specific type. To listen to all events specify the constant ``MATCH_ALL`` @@ -477,13 +486,14 @@ class EventBus: else: self._listeners[event_type] = [listener] - def remove_listener(): + def remove_listener() -> None: """Remove the listener.""" self._async_remove_listener(event_type, listener) return remove_listener - def listen_once(self, event_type, listener): + def listen_once( + self, event_type: str, listener: Callable) -> CALLBACK_TYPE: """Listen once for event of a specific type. To listen to all events specify the constant ``MATCH_ALL`` @@ -495,7 +505,7 @@ class EventBus: self._hass.loop, self.async_listen_once, event_type, listener, ).result() - def remove_listener(): + def remove_listener() -> None: """Remove the listener.""" run_callback_threadsafe( self._hass.loop, async_remove_listener).result() @@ -503,7 +513,8 @@ class EventBus: return remove_listener @callback - def async_listen_once(self, event_type, listener): + def async_listen_once( + self, event_type: str, listener: Callable) -> CALLBACK_TYPE: """Listen once for event of a specific type. To listen to all events specify the constant ``MATCH_ALL`` @@ -514,8 +525,8 @@ class EventBus: This method must be run in the event loop. """ @callback - def onetime_listener(event): - """Remove listener from eventbus and then fire listener.""" + def onetime_listener(event: Event) -> None: + """Remove listener from event bus and then fire listener.""" if hasattr(onetime_listener, 'run'): return # Set variable so that we will never run twice. @@ -530,7 +541,8 @@ class EventBus: return self.async_listen(event_type, onetime_listener) @callback - def _async_remove_listener(self, event_type, listener): + def _async_remove_listener( + self, event_type: str, listener: Callable) -> None: """Remove a listener of a specific event_type. This method must be run in the event loop. @@ -560,8 +572,10 @@ class State: __slots__ = ['entity_id', 'state', 'attributes', 'last_changed', 'last_updated'] - def __init__(self, entity_id, state, attributes=None, last_changed=None, - last_updated=None): + def __init__(self, entity_id: str, state: Any, + attributes: Optional[Dict] = None, + last_changed: Optional[datetime.datetime] = None, + last_updated: Optional[datetime.datetime] = None) -> None: """Initialize a new state.""" state = str(state) @@ -582,23 +596,23 @@ class State: self.last_changed = last_changed or self.last_updated @property - def domain(self): + def domain(self) -> str: """Domain of this state.""" return split_entity_id(self.entity_id)[0] @property - def object_id(self): + def object_id(self) -> str: """Object id of this state.""" return split_entity_id(self.entity_id)[1] @property - def name(self): + def name(self) -> str: """Name of this state.""" return ( self.attributes.get(ATTR_FRIENDLY_NAME) or self.object_id.replace('_', ' ')) - def as_dict(self): + def as_dict(self) -> Dict: """Return a dict representation of the State. Async friendly. @@ -613,7 +627,7 @@ class State: 'last_updated': self.last_updated} @classmethod - def from_dict(cls, json_dict): + def from_dict(cls, json_dict: Dict) -> Any: """Initialize a state from a dict. Async friendly. @@ -637,14 +651,14 @@ class State: return cls(json_dict['entity_id'], json_dict['state'], json_dict.get('attributes'), last_changed, last_updated) - def __eq__(self, other): + def __eq__(self, other: Any) -> bool: """Return the comparison of the state.""" - return (self.__class__ == other.__class__ and + return (self.__class__ == other.__class__ and # type: ignore self.entity_id == other.entity_id and self.state == other.state and self.attributes == other.attributes) - def __repr__(self): + def __repr__(self) -> str: """Return the representation of the states.""" attr = "; {}".format(util.repr_helper(self.attributes)) \ if self.attributes else "" @@ -657,21 +671,23 @@ class State: class StateMachine: """Helper class that tracks the state of different entities.""" - def __init__(self, bus, loop): + def __init__(self, bus: EventBus, + loop: asyncio.events.AbstractEventLoop) -> None: """Initialize state machine.""" self._states = {} # type: Dict[str, State] self._bus = bus self._loop = loop - def entity_ids(self, domain_filter=None): + def entity_ids(self, domain_filter: Optional[str] = None)-> List[str]: """List of entity ids that are being tracked.""" future = run_callback_threadsafe( self._loop, self.async_entity_ids, domain_filter ) - return future.result() + return future.result() # type: ignore @callback - def async_entity_ids(self, domain_filter=None): + def async_entity_ids( + self, domain_filter: Optional[str] = None) -> List[str]: """List of entity ids that are being tracked. This method must be run in the event loop. @@ -684,26 +700,27 @@ class StateMachine: return [state.entity_id for state in self._states.values() if state.domain == domain_filter] - def all(self): + def all(self)-> List[State]: """Create a list of all states.""" - return run_callback_threadsafe(self._loop, self.async_all).result() + return run_callback_threadsafe( # type: ignore + self._loop, self.async_all).result() @callback - def async_all(self): + def async_all(self)-> List[State]: """Create a list of all states. This method must be run in the event loop. """ return list(self._states.values()) - def get(self, entity_id): + def get(self, entity_id: str) -> Optional[State]: """Retrieve state of entity_id or None if not found. Async friendly. """ return self._states.get(entity_id.lower()) - def is_state(self, entity_id, state): + def is_state(self, entity_id: str, state: State) -> bool: """Test if entity exists and is specified state. Async friendly. @@ -711,16 +728,16 @@ class StateMachine: state_obj = self.get(entity_id) return state_obj is not None and state_obj.state == state - def remove(self, entity_id): + def remove(self, entity_id: str) -> bool: """Remove the state of an entity. Returns boolean to indicate if an entity was removed. """ - return run_callback_threadsafe( + return run_callback_threadsafe( # type: ignore self._loop, self.async_remove, entity_id).result() @callback - def async_remove(self, entity_id): + def async_remove(self, entity_id: str) -> bool: """Remove the state of an entity. Returns boolean to indicate if an entity was removed. @@ -740,7 +757,9 @@ class StateMachine: }) return True - def set(self, entity_id, new_state, attributes=None, force_update=False): + def set(self, entity_id: str, new_state: Any, + attributes: Optional[Dict] = None, + force_update: bool = False) -> None: """Set the state of an entity, add entity if it does not exist. Attributes is an optional dict to specify attributes of this state. @@ -754,8 +773,9 @@ class StateMachine: ).result() @callback - def async_set(self, entity_id, new_state, attributes=None, - force_update=False): + def async_set(self, entity_id: str, new_state: Any, + attributes: Optional[Dict] = None, + force_update: bool = False) -> None: """Set the state of an entity, add entity if it does not exist. Attributes is an optional dict to specify attributes of this state. @@ -769,15 +789,19 @@ class StateMachine: new_state = str(new_state) attributes = attributes or {} old_state = self._states.get(entity_id) - is_existing = old_state is not None - same_state = (is_existing and old_state.state == new_state and - not force_update) - same_attr = is_existing and old_state.attributes == attributes + if old_state is None: + same_state = False + same_attr = False + last_changed = None + else: + same_state = (old_state.state == new_state and + not force_update) + same_attr = old_state.attributes == attributes + last_changed = old_state.last_changed if same_state else None if same_state and same_attr: return - last_changed = old_state.last_changed if same_state else None state = State(entity_id, new_state, attributes, last_changed) self._states[entity_id] = state self._bus.async_fire(EVENT_STATE_CHANGED, { @@ -792,7 +816,7 @@ class Service: __slots__ = ['func', 'schema', 'is_callback', 'is_coroutinefunction'] - def __init__(self, func, schema): + def __init__(self, func: Callable, schema: Optional[vol.Schema]) -> None: """Initialize a service.""" self.func = func self.schema = schema @@ -805,14 +829,15 @@ class ServiceCall: __slots__ = ['domain', 'service', 'data', 'call_id'] - def __init__(self, domain, service, data=None, call_id=None): + def __init__(self, domain: str, service: str, data: Optional[Dict] = None, + call_id: Optional[str] = None) -> None: """Initialize a service call.""" self.domain = domain.lower() self.service = service.lower() self.data = MappingProxyType(data or {}) self.call_id = call_id - def __repr__(self): + def __repr__(self) -> str: """Return the representation of the service.""" if self.data: return "".format( @@ -824,13 +849,13 @@ class ServiceCall: class ServiceRegistry: """Offer the services over the eventbus.""" - def __init__(self, hass): + def __init__(self, hass: HomeAssistant) -> None: """Initialize a service registry.""" self._services = {} # type: Dict[str, Dict[str, Service]] self._hass = hass - self._async_unsub_call_event = None + self._async_unsub_call_event = None # type: Optional[CALLBACK_TYPE] - def _gen_unique_id(): + def _gen_unique_id() -> Iterator[str]: cur_id = 1 while True: yield '{}-{}'.format(id(self), cur_id) @@ -840,14 +865,14 @@ class ServiceRegistry: self._generate_unique_id = lambda: next(gen) @property - def services(self): + def services(self) -> Dict[str, Dict[str, Service]]: """Return dictionary with per domain a list of available services.""" - return run_callback_threadsafe( + return run_callback_threadsafe( # type: ignore self._hass.loop, self.async_services, ).result() @callback - def async_services(self): + def async_services(self) -> Dict[str, Dict[str, Service]]: """Return dictionary with per domain a list of available services. This method must be run in the event loop. @@ -855,14 +880,15 @@ class ServiceRegistry: return {domain: self._services[domain].copy() for domain in self._services} - def has_service(self, domain, service): + def has_service(self, domain: str, service: str) -> bool: """Test if specified service exists. Async friendly. """ return service.lower() in self._services.get(domain.lower(), []) - def register(self, domain, service, service_func, schema=None): + def register(self, domain: str, service: str, service_func: Callable, + schema: Optional[vol.Schema] = None) -> None: """ Register a service. @@ -874,7 +900,8 @@ class ServiceRegistry: ).result() @callback - def async_register(self, domain, service, service_func, schema=None): + def async_register(self, domain: str, service: str, service_func: Callable, + schema: Optional[vol.Schema] = None) -> None: """ Register a service. @@ -900,13 +927,13 @@ class ServiceRegistry: {ATTR_DOMAIN: domain, ATTR_SERVICE: service} ) - def remove(self, domain, service): + def remove(self, domain: str, service: str) -> None: """Remove a registered service from service handler.""" run_callback_threadsafe( self._hass.loop, self.async_remove, domain, service).result() @callback - def async_remove(self, domain, service): + def async_remove(self, domain: str, service: str) -> None: """Remove a registered service from service handler. This method must be run in the event loop. @@ -926,7 +953,9 @@ class ServiceRegistry: {ATTR_DOMAIN: domain, ATTR_SERVICE: service} ) - def call(self, domain, service, service_data=None, blocking=False): + def call(self, domain: str, service: str, + service_data: Optional[Dict] = None, + blocking: bool = False) -> Optional[bool]: """ Call a service. @@ -943,13 +972,14 @@ class ServiceRegistry: Because the service is sent as an event you are not allowed to use the keys ATTR_DOMAIN and ATTR_SERVICE in your service_data. """ - return run_coroutine_threadsafe( + return run_coroutine_threadsafe( # type: ignore self.async_call(domain, service, service_data, blocking), self._hass.loop ).result() - async def async_call(self, domain, service, service_data=None, - blocking=False): + async def async_call(self, domain: str, service: str, + service_data: Optional[Dict] = None, + blocking: bool = False) -> Optional[bool]: """ Call a service. @@ -981,7 +1011,7 @@ class ServiceRegistry: fut = asyncio.Future(loop=self._hass.loop) # type: asyncio.Future @callback - def service_executed(event): + def service_executed(event: Event) -> None: """Handle an executed service.""" if event.data[ATTR_SERVICE_CALL_ID] == call_id: fut.set_result(True) @@ -989,20 +1019,22 @@ class ServiceRegistry: unsub = self._hass.bus.async_listen( EVENT_SERVICE_EXECUTED, service_executed) - self._hass.bus.async_fire(EVENT_CALL_SERVICE, event_data) + self._hass.bus.async_fire(EVENT_CALL_SERVICE, event_data) - if blocking: done, _ = await asyncio.wait( [fut], loop=self._hass.loop, timeout=SERVICE_CALL_LIMIT) success = bool(done) unsub() return success - async def _event_to_service_call(self, event): + self._hass.bus.async_fire(EVENT_CALL_SERVICE, event_data) + return None + + async def _event_to_service_call(self, event: Event) -> None: """Handle the SERVICE_CALLED events from the EventBus.""" service_data = event.data.get(ATTR_SERVICE_DATA) or {} - domain = event.data.get(ATTR_DOMAIN).lower() - service = event.data.get(ATTR_SERVICE).lower() + domain = event.data.get(ATTR_DOMAIN).lower() # type: ignore + service = event.data.get(ATTR_SERVICE).lower() # type: ignore call_id = event.data.get(ATTR_SERVICE_CALL_ID) if not self.has_service(domain, service): @@ -1013,7 +1045,7 @@ class ServiceRegistry: service_handler = self._services[domain][service] - def fire_service_executed(): + def fire_service_executed() -> None: """Fire service executed event.""" if not call_id: return @@ -1045,12 +1077,12 @@ class ServiceRegistry: await service_handler.func(service_call) fire_service_executed() else: - def execute_service(): + def execute_service() -> None: """Execute a service and fires a SERVICE_EXECUTED event.""" service_handler.func(service_call) fire_service_executed() - await self._hass.async_add_job(execute_service) + await self._hass.async_add_executor_job(execute_service) except Exception: # pylint: disable=broad-except _LOGGER.exception('Error executing service %s', service_call) @@ -1058,13 +1090,13 @@ class ServiceRegistry: class Config: """Configuration settings for Home Assistant.""" - def __init__(self): + def __init__(self) -> None: """Initialize a new config object.""" self.latitude = None # type: Optional[float] self.longitude = None # type: Optional[float] self.elevation = None # type: Optional[int] self.location_name = None # type: Optional[str] - self.time_zone = None # type: Optional[str] + self.time_zone = None # type: Optional[datetime.tzinfo] self.units = METRIC_SYSTEM # type: UnitSystem # If True, pip install is skipped for requirements on startup @@ -1090,7 +1122,7 @@ class Config: return self.units.length( location.distance(self.latitude, self.longitude, lat, lon), 'm') - def path(self, *path): + def path(self, *path: str) -> str: """Generate path to the file within the configuration directory. Async friendly. @@ -1122,12 +1154,14 @@ class Config: return False - def as_dict(self): + def as_dict(self) -> Dict: """Create a dictionary representation of this dict. Async friendly. """ - time_zone = self.time_zone or dt_util.UTC + time_zone = dt_util.UTC.zone + if self.time_zone and getattr(self.time_zone, 'zone'): + time_zone = getattr(self.time_zone, 'zone') return { 'latitude': self.latitude, @@ -1135,7 +1169,7 @@ class Config: 'elevation': self.elevation, 'unit_system': self.units.as_dict(), 'location_name': self.location_name, - 'time_zone': time_zone.zone, + 'time_zone': time_zone, 'components': self.components, 'config_dir': self.config_dir, 'whitelist_external_dirs': self.whitelist_external_dirs, @@ -1143,12 +1177,12 @@ class Config: } -def _async_create_timer(hass): +def _async_create_timer(hass: HomeAssistant) -> None: """Create a timer that will start on HOMEASSISTANT_START.""" handle = None @callback - def fire_time_event(nxt): + def fire_time_event(nxt: float) -> None: """Fire next time event.""" nonlocal handle @@ -1165,7 +1199,7 @@ def _async_create_timer(hass): handle = hass.loop.call_later(slp_seconds, fire_time_event, nxt) @callback - def stop_timer(event): + def stop_timer(_: Event) -> None: """Stop the timer.""" if handle is not None: handle.cancel() diff --git a/homeassistant/data_entry_flow.py b/homeassistant/data_entry_flow.py index 24dcb46bb681..f010ada02f3c 100644 --- a/homeassistant/data_entry_flow.py +++ b/homeassistant/data_entry_flow.py @@ -1,8 +1,9 @@ """Classes to help gather user submissions.""" import logging import uuid -from typing import Dict, Any # noqa pylint: disable=unused-import -from .core import callback +import voluptuous as vol +from typing import Dict, Any, Callable, List, Optional # noqa pylint: disable=unused-import +from .core import callback, HomeAssistant from .exceptions import HomeAssistantError _LOGGER = logging.getLogger(__name__) @@ -35,7 +36,8 @@ class UnknownStep(FlowError): class FlowManager: """Manage all the flows that are in progress.""" - def __init__(self, hass, async_create_flow, async_finish_flow): + def __init__(self, hass: HomeAssistant, async_create_flow: Callable, + async_finish_flow: Callable) -> None: """Initialize the flow manager.""" self.hass = hass self._progress = {} # type: Dict[str, Any] @@ -43,7 +45,7 @@ class FlowManager: self._async_finish_flow = async_finish_flow @callback - def async_progress(self): + def async_progress(self) -> List[Dict]: """Return the flows in progress.""" return [{ 'flow_id': flow.flow_id, @@ -51,7 +53,8 @@ class FlowManager: 'source': flow.source, } for flow in self._progress.values()] - async def async_init(self, handler, *, source=SOURCE_USER, data=None): + async def async_init(self, handler: Callable, *, source: str = SOURCE_USER, + data: str = None) -> Any: """Start a configuration flow.""" flow = await self._async_create_flow(handler, source=source, data=data) flow.hass = self.hass @@ -67,7 +70,8 @@ class FlowManager: return await self._async_handle_step(flow, step, data) - async def async_configure(self, flow_id, user_input=None): + async def async_configure( + self, flow_id: str, user_input: str = None) -> Any: """Continue a configuration flow.""" flow = self._progress.get(flow_id) @@ -83,12 +87,13 @@ class FlowManager: flow, step_id, user_input) @callback - def async_abort(self, flow_id): + def async_abort(self, flow_id: str) -> None: """Abort a flow.""" if self._progress.pop(flow_id, None) is None: raise UnknownFlow - async def _async_handle_step(self, flow, step_id, user_input): + async def _async_handle_step(self, flow: Any, step_id: str, + user_input: Optional[str]) -> Dict: """Handle a step of a flow.""" method = "async_step_{}".format(step_id) @@ -97,7 +102,7 @@ class FlowManager: raise UnknownStep("Handler {} doesn't support step {}".format( flow.__class__.__name__, step_id)) - result = await getattr(flow, method)(user_input) + result = await getattr(flow, method)(user_input) # type: Dict if result['type'] not in (RESULT_TYPE_FORM, RESULT_TYPE_CREATE_ENTRY, RESULT_TYPE_ABORT): @@ -133,8 +138,9 @@ class FlowHandler: VERSION = 1 @callback - def async_show_form(self, *, step_id, data_schema=None, errors=None, - description_placeholders=None): + def async_show_form(self, *, step_id: str, data_schema: vol.Schema = None, + errors: Dict = None, + description_placeholders: Dict = None) -> Dict: """Return the definition of a form to gather user input.""" return { 'type': RESULT_TYPE_FORM, @@ -147,7 +153,7 @@ class FlowHandler: } @callback - def async_create_entry(self, *, title, data): + def async_create_entry(self, *, title: str, data: Dict) -> Dict: """Finish config flow and create a config entry.""" return { 'version': self.VERSION, @@ -160,7 +166,7 @@ class FlowHandler: } @callback - def async_abort(self, *, reason): + def async_abort(self, *, reason: str) -> Dict: """Abort the config flow.""" return { 'type': RESULT_TYPE_ABORT, diff --git a/homeassistant/loader.py b/homeassistant/loader.py index 2b0f9ed18e45..c5cf99de2349 100644 --- a/homeassistant/loader.py +++ b/homeassistant/loader.py @@ -17,7 +17,7 @@ import sys from types import ModuleType # pylint: disable=unused-import -from typing import Optional, Set, TYPE_CHECKING # NOQA +from typing import Optional, Set, TYPE_CHECKING, Callable, Any, TypeVar # NOQA from homeassistant.const import PLATFORM_FORMAT from homeassistant.util import OrderedSet @@ -27,6 +27,8 @@ from homeassistant.util import OrderedSet if TYPE_CHECKING: from homeassistant.core import HomeAssistant # NOQA +CALLABLE_T = TypeVar('CALLABLE_T', bound=Callable) + PREPARED = False DEPENDENCY_BLACKLIST = {'config'} @@ -51,7 +53,8 @@ def set_component(hass, # type: HomeAssistant cache[comp_name] = component -def get_platform(hass, domain: str, platform: str) -> Optional[ModuleType]: +def get_platform(hass, # type: HomeAssistant + domain: str, platform: str) -> Optional[ModuleType]: """Try to load specified platform. Async friendly. @@ -59,7 +62,8 @@ def get_platform(hass, domain: str, platform: str) -> Optional[ModuleType]: return get_component(hass, PLATFORM_FORMAT.format(domain, platform)) -def get_component(hass, comp_or_platform) -> Optional[ModuleType]: +def get_component(hass, # type: HomeAssistant + comp_or_platform: str) -> Optional[ModuleType]: """Try to load specified component. Looks in config dir first, then built-in components. @@ -73,6 +77,9 @@ def get_component(hass, comp_or_platform) -> Optional[ModuleType]: cache = hass.data.get(DATA_KEY) if cache is None: + if hass.config.config_dir is None: + _LOGGER.error("Can't load components - config dir is not set") + return None # Only insert if it's not there (happens during tests) if sys.path[0] != hass.config.config_dir: sys.path.insert(0, hass.config.config_dir) @@ -134,14 +141,38 @@ def get_component(hass, comp_or_platform) -> Optional[ModuleType]: return None +class ModuleWrapper: + """Class to wrap a Python module and auto fill in hass argument.""" + + def __init__(self, + hass, # type: HomeAssistant + module: ModuleType) -> None: + """Initialize the module wrapper.""" + self._hass = hass + self._module = module + + def __getattr__(self, attr: str) -> Any: + """Fetch an attribute.""" + value = getattr(self._module, attr) + + if hasattr(value, '__bind_hass'): + value = ft.partial(value, self._hass) + + setattr(self, attr, value) + return value + + class Components: """Helper to load components.""" - def __init__(self, hass): + def __init__( + self, + hass # type: HomeAssistant + ) -> None: """Initialize the Components class.""" self._hass = hass - def __getattr__(self, comp_name): + def __getattr__(self, comp_name: str) -> ModuleWrapper: """Fetch a component.""" component = get_component(self._hass, comp_name) if component is None: @@ -154,11 +185,14 @@ class Components: class Helpers: """Helper to load helpers.""" - def __init__(self, hass): + def __init__( + self, + hass # type: HomeAssistant + ) -> None: """Initialize the Helpers class.""" self._hass = hass - def __getattr__(self, helper_name): + def __getattr__(self, helper_name: str) -> ModuleWrapper: """Fetch a helper.""" helper = importlib.import_module( 'homeassistant.helpers.{}'.format(helper_name)) @@ -167,33 +201,14 @@ class Helpers: return wrapped -class ModuleWrapper: - """Class to wrap a Python module and auto fill in hass argument.""" - - def __init__(self, hass, module): - """Initialize the module wrapper.""" - self._hass = hass - self._module = module - - def __getattr__(self, attr): - """Fetch an attribute.""" - value = getattr(self._module, attr) - - if hasattr(value, '__bind_hass'): - value = ft.partial(value, self._hass) - - setattr(self, attr, value) - return value - - -def bind_hass(func): +def bind_hass(func: CALLABLE_T) -> CALLABLE_T: """Decorate function to indicate that first argument is hass.""" - # pylint: disable=protected-access - func.__bind_hass = True + setattr(func, '__bind_hass', True) return func -def load_order_component(hass, comp_name: str) -> OrderedSet: +def load_order_component(hass, # type: HomeAssistant + comp_name: str) -> OrderedSet: """Return an OrderedSet of components in the correct order of loading. Raises HomeAssistantError if a circular dependency is detected. @@ -204,7 +219,8 @@ def load_order_component(hass, comp_name: str) -> OrderedSet: return _load_order_component(hass, comp_name, OrderedSet(), set()) -def _load_order_component(hass, comp_name: str, load_order: OrderedSet, +def _load_order_component(hass, # type: HomeAssistant + comp_name: str, load_order: OrderedSet, loading: Set) -> OrderedSet: """Recursive function to get load order of components. diff --git a/homeassistant/monkey_patch.py b/homeassistant/monkey_patch.py index 17329fbddff4..aa330ffec167 100644 --- a/homeassistant/monkey_patch.py +++ b/homeassistant/monkey_patch.py @@ -20,9 +20,10 @@ Related Python bugs: - https://bugs.python.org/issue26617 """ import sys +from typing import Any -def patch_weakref_tasks(): +def patch_weakref_tasks() -> None: """Replace weakref.WeakSet to address Python 3 bug.""" # pylint: disable=no-self-use, protected-access, bare-except import asyncio.tasks @@ -30,7 +31,7 @@ def patch_weakref_tasks(): class IgnoreCalls: """Ignore add calls.""" - def add(self, other): + def add(self, other: Any) -> None: """No-op add.""" return @@ -41,7 +42,7 @@ def patch_weakref_tasks(): pass -def disable_c_asyncio(): +def disable_c_asyncio() -> None: """Disable using C implementation of asyncio. Required to be able to apply the weakref monkey patch. @@ -53,12 +54,12 @@ def disable_c_asyncio(): PATH_TRIGGER = '_asyncio' - def __init__(self, path_entry): + def __init__(self, path_entry: str) -> None: if path_entry != self.PATH_TRIGGER: raise ImportError() return - def find_module(self, fullname, path=None): + def find_module(self, fullname: str, path: Any = None) -> None: """Find a module.""" if fullname == self.PATH_TRIGGER: # We lint in Py35, exception is introduced in Py36 diff --git a/homeassistant/remote.py b/homeassistant/remote.py index 7147fab10806..313f98a890c7 100644 --- a/homeassistant/remote.py +++ b/homeassistant/remote.py @@ -13,7 +13,7 @@ import json import logging import urllib.parse -from typing import Optional +from typing import Optional, Dict, Any, List from aiohttp.hdrs import METH_GET, METH_POST, METH_DELETE, CONTENT_TYPE import requests @@ -62,7 +62,7 @@ class API: if port is not None: self.base_url += ':{}'.format(port) - self.status = None + self.status = None # type: Optional[APIStatus] self._headers = {CONTENT_TYPE: CONTENT_TYPE_JSON} if api_password is not None: @@ -75,20 +75,24 @@ class API: return self.status == APIStatus.OK - def __call__(self, method, path, data=None, timeout=5): + def __call__(self, method: str, path: str, data: Dict = None, + timeout: int = 5) -> requests.Response: """Make a call to the Home Assistant API.""" - if data is not None: - data = json.dumps(data, cls=JSONEncoder) + if data is None: + data_str = None + else: + data_str = json.dumps(data, cls=JSONEncoder) url = urllib.parse.urljoin(self.base_url, path) try: if method == METH_GET: return requests.get( - url, params=data, timeout=timeout, headers=self._headers) + url, params=data_str, timeout=timeout, + headers=self._headers) return requests.request( - method, url, data=data, timeout=timeout, + method, url, data=data_str, timeout=timeout, headers=self._headers) except requests.exceptions.ConnectionError: @@ -110,7 +114,7 @@ class JSONEncoder(json.JSONEncoder): """JSONEncoder that supports Home Assistant objects.""" # pylint: disable=method-hidden - def default(self, o): + def default(self, o: Any) -> Any: """Convert Home Assistant objects. Hand other objects to the original method. @@ -125,7 +129,7 @@ class JSONEncoder(json.JSONEncoder): return json.JSONEncoder.default(self, o) -def validate_api(api): +def validate_api(api: API) -> APIStatus: """Make a call to validate API.""" try: req = api(METH_GET, URL_API) @@ -142,12 +146,12 @@ def validate_api(api): return APIStatus.CANNOT_CONNECT -def get_event_listeners(api): +def get_event_listeners(api: API) -> Dict: """List of events that is being listened for.""" try: req = api(METH_GET, URL_API_EVENTS) - return req.json() if req.status_code == 200 else {} + return req.json() if req.status_code == 200 else {} # type: ignore except (HomeAssistantError, ValueError): # ValueError if req.json() can't parse the json @@ -156,7 +160,7 @@ def get_event_listeners(api): return {} -def fire_event(api, event_type, data=None): +def fire_event(api: API, event_type: str, data: Dict = None) -> None: """Fire an event at remote API.""" try: req = api(METH_POST, URL_API_EVENTS_EVENT.format(event_type), data) @@ -169,7 +173,7 @@ def fire_event(api, event_type, data=None): _LOGGER.exception("Error firing event") -def get_state(api, entity_id): +def get_state(api: API, entity_id: str) -> Optional[ha.State]: """Query given API for state of entity_id.""" try: req = api(METH_GET, URL_API_STATES_ENTITY.format(entity_id)) @@ -186,7 +190,7 @@ def get_state(api, entity_id): return None -def get_states(api): +def get_states(api: API) -> List[ha.State]: """Query given API for all states.""" try: req = api(METH_GET, @@ -202,7 +206,7 @@ def get_states(api): return [] -def remove_state(api, entity_id): +def remove_state(api: API, entity_id: str) -> bool: """Call API to remove state for entity_id. Return True if entity is gone (removed/never existed). @@ -222,7 +226,8 @@ def remove_state(api, entity_id): return False -def set_state(api, entity_id, new_state, attributes=None, force_update=False): +def set_state(api: API, entity_id: str, new_state: str, + attributes: Dict = None, force_update: bool = False) -> bool: """Tell API to update state for entity_id. Return True if success. @@ -249,14 +254,14 @@ def set_state(api, entity_id, new_state, attributes=None, force_update=False): return False -def is_state(api, entity_id, state): +def is_state(api: API, entity_id: str, state: str) -> bool: """Query API to see if entity_id is specified state.""" cur_state = get_state(api, entity_id) - return cur_state and cur_state.state == state + return bool(cur_state and cur_state.state == state) -def get_services(api): +def get_services(api: API) -> Dict: """Return a list of dicts. Each dict has a string "domain" and a list of strings "services". @@ -264,7 +269,7 @@ def get_services(api): try: req = api(METH_GET, URL_API_SERVICES) - return req.json() if req.status_code == 200 else {} + return req.json() if req.status_code == 200 else {} # type: ignore except (HomeAssistantError, ValueError): # ValueError if req.json() can't parse the json @@ -273,7 +278,9 @@ def get_services(api): return {} -def call_service(api, domain, service, service_data=None, timeout=5): +def call_service(api: API, domain: str, service: str, + service_data: Dict = None, + timeout: int = 5) -> None: """Call a service at the remote API.""" try: req = api(METH_POST, @@ -288,7 +295,7 @@ def call_service(api, domain, service, service_data=None, timeout=5): _LOGGER.exception("Error calling service") -def get_config(api): +def get_config(api: API) -> Dict: """Return configuration.""" try: req = api(METH_GET, URL_API_CONFIG) @@ -299,7 +306,7 @@ def get_config(api): result = req.json() if 'components' in result: result['components'] = set(result['components']) - return result + return result # type: ignore except (HomeAssistantError, ValueError): # ValueError if req.json() can't parse the JSON diff --git a/homeassistant/requirements.py b/homeassistant/requirements.py index 753947a2c12c..b73ec4e184e8 100644 --- a/homeassistant/requirements.py +++ b/homeassistant/requirements.py @@ -3,15 +3,18 @@ import asyncio from functools import partial import logging import os +from typing import List, Dict, Optional import homeassistant.util.package as pkg_util +from homeassistant.core import HomeAssistant DATA_PIP_LOCK = 'pip_lock' CONSTRAINT_FILE = 'package_constraints.txt' _LOGGER = logging.getLogger(__name__) -async def async_process_requirements(hass, name, requirements): +async def async_process_requirements(hass: HomeAssistant, name: str, + requirements: List[str]) -> bool: """Install the requirements for a component or platform. This method is a coroutine. @@ -25,7 +28,7 @@ async def async_process_requirements(hass, name, requirements): async with pip_lock: for req in requirements: - ret = await hass.async_add_job(pip_install, req) + ret = await hass.async_add_executor_job(pip_install, req) if not ret: _LOGGER.error("Not initializing %s because could not install " "requirement %s", name, req) @@ -34,11 +37,11 @@ async def async_process_requirements(hass, name, requirements): return True -def pip_kwargs(config_dir): +def pip_kwargs(config_dir: Optional[str]) -> Dict[str, str]: """Return keyword arguments for PIP install.""" kwargs = { 'constraints': os.path.join(os.path.dirname(__file__), CONSTRAINT_FILE) } - if not pkg_util.is_virtual_env(): + if not (config_dir is None or pkg_util.is_virtual_env()): kwargs['target'] = os.path.join(config_dir, 'deps') return kwargs diff --git a/homeassistant/setup.py b/homeassistant/setup.py index 0641a461130b..31404b978eb4 100644 --- a/homeassistant/setup.py +++ b/homeassistant/setup.py @@ -4,7 +4,7 @@ import logging.handlers from timeit import default_timer as timer from types import ModuleType -from typing import Optional, Dict +from typing import Optional, Dict, List from homeassistant import requirements, core, loader, config as conf_util from homeassistant.config import async_notify_setup_error @@ -56,7 +56,9 @@ async def async_setup_component(hass: core.HomeAssistant, domain: str, return await task # type: ignore -async def _async_process_dependencies(hass, config, name, dependencies): +async def _async_process_dependencies( + hass: core.HomeAssistant, config: Dict, name: str, + dependencies: List[str]) -> bool: """Ensure all dependencies are set up.""" blacklisted = [dep for dep in dependencies if dep in loader.DEPENDENCY_BLACKLIST] @@ -88,12 +90,12 @@ async def _async_process_dependencies(hass, config, name, dependencies): async def _async_setup_component(hass: core.HomeAssistant, - domain: str, config) -> bool: + domain: str, config: Dict) -> bool: """Set up a component for Home Assistant. This method is a coroutine. """ - def log_error(msg, link=True): + def log_error(msg: str, link: bool = True) -> None: """Log helper.""" _LOGGER.error("Setup failed for %s: %s", domain, msg) async_notify_setup_error(hass, domain, link) @@ -181,7 +183,7 @@ async def _async_setup_component(hass: core.HomeAssistant, return True -async def async_prepare_setup_platform(hass: core.HomeAssistant, config, +async def async_prepare_setup_platform(hass: core.HomeAssistant, config: Dict, domain: str, platform_name: str) \ -> Optional[ModuleType]: """Load a platform and makes sure dependencies are setup. @@ -190,7 +192,7 @@ async def async_prepare_setup_platform(hass: core.HomeAssistant, config, """ platform_path = PLATFORM_FORMAT.format(domain, platform_name) - def log_error(msg): + def log_error(msg: str) -> None: """Log helper.""" _LOGGER.error("Unable to prepare setup for platform %s: %s", platform_path, msg) @@ -217,7 +219,9 @@ async def async_prepare_setup_platform(hass: core.HomeAssistant, config, return platform -async def async_process_deps_reqs(hass, config, name, module): +async def async_process_deps_reqs( + hass: core.HomeAssistant, config: Dict, name: str, + module: ModuleType) -> None: """Process all dependencies and requirements for a module. Module is a Python module of either a component or platform. @@ -231,14 +235,14 @@ async def async_process_deps_reqs(hass, config, name, module): if hasattr(module, 'DEPENDENCIES'): dep_success = await _async_process_dependencies( - hass, config, name, module.DEPENDENCIES) + hass, config, name, module.DEPENDENCIES) # type: ignore if not dep_success: raise HomeAssistantError("Could not setup all dependencies.") if not hass.config.skip_pip and hasattr(module, 'REQUIREMENTS'): req_success = await requirements.async_process_requirements( - hass, name, module.REQUIREMENTS) + hass, name, module.REQUIREMENTS) # type: ignore if not req_success: raise HomeAssistantError("Could not install all requirements.") diff --git a/homeassistant/util/__init__.py b/homeassistant/util/__init__.py index 6b539e991865..37f669944d97 100644 --- a/homeassistant/util/__init__.py +++ b/homeassistant/util/__init__.py @@ -1,9 +1,8 @@ """Helper methods for various modules.""" import asyncio -from collections.abc import MutableSet +from datetime import datetime, timedelta from itertools import chain import threading -from datetime import datetime import re import enum import socket @@ -14,12 +13,13 @@ from types import MappingProxyType from unicodedata import normalize from typing import (Any, Optional, TypeVar, Callable, KeysView, Union, # noqa - Iterable, List, Mapping) + Iterable, List, Dict, Iterator, Coroutine, MutableSet) from .dt import as_local, utcnow T = TypeVar('T') U = TypeVar('U') +ENUM_T = TypeVar('ENUM_T', bound=enum.Enum) RE_SANITIZE_FILENAME = re.compile(r'(~|\.\.|/|\\)') RE_SANITIZE_PATH = re.compile(r'(~|\.(\.)+)') @@ -91,7 +91,7 @@ def ensure_unique_string(preferred_string: str, current_strings: # Taken from: http://stackoverflow.com/a/11735897 -def get_local_ip(): +def get_local_ip() -> str: """Try to determine the local IP address of the machine.""" try: sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) @@ -99,7 +99,7 @@ def get_local_ip(): # Use Google Public DNS server to determine own IP sock.connect(('8.8.8.8', 80)) - return sock.getsockname()[0] + return sock.getsockname()[0] # type: ignore except socket.error: try: return socket.gethostbyname(socket.gethostname()) @@ -110,7 +110,7 @@ def get_local_ip(): # Taken from http://stackoverflow.com/a/23728630 -def get_random_string(length=10): +def get_random_string(length: int = 10) -> str: """Return a random string with letters and digits.""" generator = random.SystemRandom() source_chars = string.ascii_letters + string.digits @@ -121,59 +121,59 @@ def get_random_string(length=10): class OrderedEnum(enum.Enum): """Taken from Python 3.4.0 docs.""" - def __ge__(self, other): + def __ge__(self: ENUM_T, other: ENUM_T) -> bool: """Return the greater than element.""" if self.__class__ is other.__class__: - return self.value >= other.value + return bool(self.value >= other.value) return NotImplemented - def __gt__(self, other): + def __gt__(self: ENUM_T, other: ENUM_T) -> bool: """Return the greater element.""" if self.__class__ is other.__class__: - return self.value > other.value + return bool(self.value > other.value) return NotImplemented - def __le__(self, other): + def __le__(self: ENUM_T, other: ENUM_T) -> bool: """Return the lower than element.""" if self.__class__ is other.__class__: - return self.value <= other.value + return bool(self.value <= other.value) return NotImplemented - def __lt__(self, other): + def __lt__(self: ENUM_T, other: ENUM_T) -> bool: """Return the lower element.""" if self.__class__ is other.__class__: - return self.value < other.value + return bool(self.value < other.value) return NotImplemented -class OrderedSet(MutableSet): +class OrderedSet(MutableSet[T]): """Ordered set taken from http://code.activestate.com/recipes/576694/.""" - def __init__(self, iterable=None): + def __init__(self, iterable: Iterable[T] = None) -> None: """Initialize the set.""" self.end = end = [] # type: List[Any] end += [None, end, end] # sentinel node for doubly linked list - self.map = {} # type: Mapping[List, Any] # key --> [key, prev, next] + self.map = {} # type: Dict[T, List] # key --> [key, prev, next] if iterable is not None: - self |= iterable + self |= iterable # type: ignore - def __len__(self): + def __len__(self) -> int: """Return the length of the set.""" return len(self.map) - def __contains__(self, key): + def __contains__(self, key: T) -> bool: # type: ignore """Check if key is in set.""" return key in self.map # pylint: disable=arguments-differ - def add(self, key): + def add(self, key: T) -> None: """Add an element to the end of the set.""" if key not in self.map: end = self.end curr = end[1] curr[2] = end[1] = self.map[key] = [key, curr, end] - def promote(self, key): + def promote(self, key: T) -> None: """Promote element to beginning of the set, add if not there.""" if key in self.map: self.discard(key) @@ -183,14 +183,14 @@ class OrderedSet(MutableSet): curr[2] = begin[1] = self.map[key] = [key, curr, begin] # pylint: disable=arguments-differ - def discard(self, key): + def discard(self, key: T) -> None: """Discard an element from the set.""" if key in self.map: key, prev_item, next_item = self.map.pop(key) prev_item[2] = next_item next_item[1] = prev_item - def __iter__(self): + def __iter__(self) -> Iterator[T]: """Iterate of the set.""" end = self.end curr = end[2] @@ -198,7 +198,7 @@ class OrderedSet(MutableSet): yield curr[0] curr = curr[2] - def __reversed__(self): + def __reversed__(self) -> Iterator[T]: """Reverse the ordering.""" end = self.end curr = end[1] @@ -207,7 +207,7 @@ class OrderedSet(MutableSet): curr = curr[1] # pylint: disable=arguments-differ - def pop(self, last=True): + def pop(self, last: bool = True) -> T: """Pop element of the end of the set. Set last=False to pop from the beginning. @@ -216,20 +216,20 @@ class OrderedSet(MutableSet): raise KeyError('set is empty') key = self.end[1][0] if last else self.end[2][0] self.discard(key) - return key + return key # type: ignore - def update(self, *args): + def update(self, *args: Any) -> None: """Add elements from args to the set.""" for item in chain(*args): self.add(item) - def __repr__(self): + def __repr__(self) -> str: """Return the representation.""" if not self: return '%s()' % (self.__class__.__name__,) return '%s(%r)' % (self.__class__.__name__, list(self)) - def __eq__(self, other): + def __eq__(self, other: Any) -> bool: """Return the comparison.""" if isinstance(other, OrderedSet): return len(self) == len(other) and list(self) == list(other) @@ -254,20 +254,21 @@ class Throttle: Adds a datetime attribute `last_call` to the method. """ - def __init__(self, min_time, limit_no_throttle=None): + def __init__(self, min_time: timedelta, + limit_no_throttle: timedelta = None) -> None: """Initialize the throttle.""" self.min_time = min_time self.limit_no_throttle = limit_no_throttle - def __call__(self, method): + def __call__(self, method: Callable) -> Callable: """Caller for the throttle.""" # Make sure we return a coroutine if the method is async. if asyncio.iscoroutinefunction(method): - async def throttled_value(): + async def throttled_value() -> None: """Stand-in function for when real func is being throttled.""" return None else: - def throttled_value(): + def throttled_value() -> None: # type: ignore """Stand-in function for when real func is being throttled.""" return None @@ -288,14 +289,14 @@ class Throttle: '.' not in method.__qualname__.split('..')[-1]) @wraps(method) - def wrapper(*args, **kwargs): + def wrapper(*args: Any, **kwargs: Any) -> Union[Callable, Coroutine]: """Wrap that allows wrapped to be called only once per min_time. If we cannot acquire the lock, it is running so return None. """ # pylint: disable=protected-access if hasattr(method, '__self__'): - host = method.__self__ + host = getattr(method, '__self__') elif is_func: host = wrapper else: @@ -318,7 +319,7 @@ class Throttle: if force or utcnow() - throttle[1] > self.min_time: result = method(*args, **kwargs) throttle[1] = utcnow() - return result + return result # type: ignore return throttled_value() finally: diff --git a/homeassistant/util/async_.py b/homeassistant/util/async_.py index 334b4f454834..1e2eb25245a9 100644 --- a/homeassistant/util/async_.py +++ b/homeassistant/util/async_.py @@ -3,22 +3,25 @@ import concurrent.futures import threading import logging from asyncio import coroutines +from asyncio.events import AbstractEventLoop from asyncio.futures import Future from asyncio import ensure_future - +from typing import Any, Union, Coroutine, Callable, Generator _LOGGER = logging.getLogger(__name__) -def _set_result_unless_cancelled(fut, result): +def _set_result_unless_cancelled(fut: Future, result: Any) -> None: """Set the result only if the Future was not cancelled.""" if fut.cancelled(): return fut.set_result(result) -def _set_concurrent_future_state(concurr, source): +def _set_concurrent_future_state( + concurr: concurrent.futures.Future, + source: Union[concurrent.futures.Future, Future]) -> None: """Copy state from a future to a concurrent.futures.Future.""" assert source.done() if source.cancelled(): @@ -33,7 +36,8 @@ def _set_concurrent_future_state(concurr, source): concurr.set_result(result) -def _copy_future_state(source, dest): +def _copy_future_state(source: Union[concurrent.futures.Future, Future], + dest: Union[concurrent.futures.Future, Future]) -> None: """Copy state from another Future. The other Future may be a concurrent.futures.Future. @@ -53,7 +57,9 @@ def _copy_future_state(source, dest): dest.set_result(result) -def _chain_future(source, destination): +def _chain_future( + source: Union[concurrent.futures.Future, Future], + destination: Union[concurrent.futures.Future, Future]) -> None: """Chain two futures so that when one completes, so does the other. The result (or exception) of source will be copied to destination. @@ -74,20 +80,23 @@ def _chain_future(source, destination): else: dest_loop = None - def _set_state(future, other): + def _set_state(future: Union[concurrent.futures.Future, Future], + other: Union[concurrent.futures.Future, Future]) -> None: if isinstance(future, Future): _copy_future_state(other, future) else: _set_concurrent_future_state(future, other) - def _call_check_cancel(destination): + def _call_check_cancel( + destination: Union[concurrent.futures.Future, Future]) -> None: if destination.cancelled(): if source_loop is None or source_loop is dest_loop: source.cancel() else: source_loop.call_soon_threadsafe(source.cancel) - def _call_set_state(source): + def _call_set_state( + source: Union[concurrent.futures.Future, Future]) -> None: if dest_loop is None or dest_loop is source_loop: _set_state(destination, source) else: @@ -97,7 +106,9 @@ def _chain_future(source, destination): source.add_done_callback(_call_set_state) -def run_coroutine_threadsafe(coro, loop): +def run_coroutine_threadsafe( + coro: Union[Coroutine, Generator], + loop: AbstractEventLoop) -> concurrent.futures.Future: """Submit a coroutine object to a given event loop. Return a concurrent.futures.Future to access the result. @@ -110,7 +121,7 @@ def run_coroutine_threadsafe(coro, loop): raise TypeError('A coroutine object is required') future = concurrent.futures.Future() # type: concurrent.futures.Future - def callback(): + def callback() -> None: """Handle the call to the coroutine.""" try: _chain_future(ensure_future(coro, loop=loop), future) @@ -125,7 +136,8 @@ def run_coroutine_threadsafe(coro, loop): return future -def fire_coroutine_threadsafe(coro, loop): +def fire_coroutine_threadsafe(coro: Coroutine, + loop: AbstractEventLoop) -> None: """Submit a coroutine object to a given event loop. This method does not provide a way to retrieve the result and @@ -139,7 +151,7 @@ def fire_coroutine_threadsafe(coro, loop): if not coroutines.iscoroutine(coro): raise TypeError('A coroutine object is required: %s' % coro) - def callback(): + def callback() -> None: """Handle the firing of a coroutine.""" ensure_future(coro, loop=loop) @@ -147,7 +159,8 @@ def fire_coroutine_threadsafe(coro, loop): return -def run_callback_threadsafe(loop, callback, *args): +def run_callback_threadsafe(loop: AbstractEventLoop, callback: Callable, + *args: Any) -> concurrent.futures.Future: """Submit a callback object to a given event loop. Return a concurrent.futures.Future to access the result. @@ -158,7 +171,7 @@ def run_callback_threadsafe(loop, callback, *args): future = concurrent.futures.Future() # type: concurrent.futures.Future - def run_callback(): + def run_callback() -> None: """Run callback and store result.""" try: future.set_result(callback(*args)) diff --git a/homeassistant/util/color.py b/homeassistant/util/color.py index a26f7014444c..0538bfbf369f 100644 --- a/homeassistant/util/color.py +++ b/homeassistant/util/color.py @@ -2,7 +2,7 @@ import math import colorsys -from typing import Tuple +from typing import Tuple, List # Official CSS3 colors from w3.org: # https://www.w3.org/TR/2010/PR-css3-color-20101028/#html4 @@ -162,7 +162,7 @@ COLORS = { } -def color_name_to_rgb(color_name): +def color_name_to_rgb(color_name: str) -> Tuple[int, int, int]: """Convert color name to RGB hex value.""" # COLORS map has no spaces in it, so make the color_name have no # spaces in it as well for matching purposes @@ -305,7 +305,8 @@ def color_hsb_to_RGB(fH: float, fS: float, fB: float) -> Tuple[int, int, int]: return (r, g, b) -def color_RGB_to_hsv(iR: int, iG: int, iB: int) -> Tuple[float, float, float]: +def color_RGB_to_hsv( + iR: float, iG: float, iB: float) -> Tuple[float, float, float]: """Convert an rgb color to its hsv representation. Hue is scaled 0-360 @@ -316,7 +317,7 @@ def color_RGB_to_hsv(iR: int, iG: int, iB: int) -> Tuple[float, float, float]: return round(fHSV[0]*360, 3), round(fHSV[1]*100, 3), round(fHSV[2]*100, 3) -def color_RGB_to_hs(iR: int, iG: int, iB: int) -> Tuple[float, float]: +def color_RGB_to_hs(iR: float, iG: float, iB: float) -> Tuple[float, float]: """Convert an rgb color to its hs representation.""" return color_RGB_to_hsv(iR, iG, iB)[:2] @@ -340,7 +341,7 @@ def color_hs_to_RGB(iH: float, iS: float) -> Tuple[int, int, int]: def color_xy_to_hs(vX: float, vY: float) -> Tuple[float, float]: """Convert an xy color to its hs representation.""" h, s, _ = color_RGB_to_hsv(*color_xy_to_RGB(vX, vY)) - return (h, s) + return h, s def color_hs_to_xy(iH: float, iS: float) -> Tuple[float, float]: @@ -348,8 +349,7 @@ def color_hs_to_xy(iH: float, iS: float) -> Tuple[float, float]: return color_RGB_to_xy(*color_hs_to_RGB(iH, iS)) -def _match_max_scale(input_colors: Tuple[int, ...], - output_colors: Tuple[int, ...]) -> Tuple[int, ...]: +def _match_max_scale(input_colors: Tuple, output_colors: Tuple) -> Tuple: """Match the maximum value of the output to the input.""" max_in = max(input_colors) max_out = max(output_colors) @@ -360,7 +360,7 @@ def _match_max_scale(input_colors: Tuple[int, ...], return tuple(int(round(i * factor)) for i in output_colors) -def color_rgb_to_rgbw(r, g, b): +def color_rgb_to_rgbw(r: int, g: int, b: int) -> Tuple[int, int, int, int]: """Convert an rgb color to an rgbw representation.""" # Calculate the white channel as the minimum of input rgb channels. # Subtract the white portion from the remaining rgb channels. @@ -369,25 +369,25 @@ def color_rgb_to_rgbw(r, g, b): # Match the output maximum value to the input. This ensures the full # channel range is used. - return _match_max_scale((r, g, b), rgbw) + return _match_max_scale((r, g, b), rgbw) # type: ignore -def color_rgbw_to_rgb(r, g, b, w): +def color_rgbw_to_rgb(r: int, g: int, b: int, w: int) -> Tuple[int, int, int]: """Convert an rgbw color to an rgb representation.""" # Add the white channel back into the rgb channels. rgb = (r + w, g + w, b + w) # Match the output maximum value to the input. This ensures the # output doesn't overflow. - return _match_max_scale((r, g, b, w), rgb) + return _match_max_scale((r, g, b, w), rgb) # type: ignore -def color_rgb_to_hex(r, g, b): +def color_rgb_to_hex(r: int, g: int, b: int) -> str: """Return a RGB color from a hex color string.""" return '{0:02x}{1:02x}{2:02x}'.format(round(r), round(g), round(b)) -def rgb_hex_to_rgb_list(hex_string): +def rgb_hex_to_rgb_list(hex_string: str) -> List[int]: """Return an RGB color value list from a hex color string.""" return [int(hex_string[i:i + len(hex_string) // 3], 16) for i in range(0, @@ -395,12 +395,14 @@ def rgb_hex_to_rgb_list(hex_string): len(hex_string) // 3)] -def color_temperature_to_hs(color_temperature_kelvin): +def color_temperature_to_hs( + color_temperature_kelvin: float) -> Tuple[float, float]: """Return an hs color from a color temperature in Kelvin.""" return color_RGB_to_hs(*color_temperature_to_rgb(color_temperature_kelvin)) -def color_temperature_to_rgb(color_temperature_kelvin): +def color_temperature_to_rgb( + color_temperature_kelvin: float) -> Tuple[float, float, float]: """ Return an RGB color from a color temperature in Kelvin. @@ -421,7 +423,7 @@ def color_temperature_to_rgb(color_temperature_kelvin): blue = _get_blue(tmp_internal) - return (red, green, blue) + return red, green, blue def _bound(color_component: float, minimum: float = 0, @@ -464,11 +466,11 @@ def _get_blue(temperature: float) -> float: return _bound(blue) -def color_temperature_mired_to_kelvin(mired_temperature): +def color_temperature_mired_to_kelvin(mired_temperature: float) -> float: """Convert absolute mired shift to degrees kelvin.""" return math.floor(1000000 / mired_temperature) -def color_temperature_kelvin_to_mired(kelvin_temperature): +def color_temperature_kelvin_to_mired(kelvin_temperature: float) -> float: """Convert degrees kelvin to mired shift.""" return math.floor(1000000 / kelvin_temperature) diff --git a/homeassistant/util/decorator.py b/homeassistant/util/decorator.py index c26606d52cff..9d2a4600a64c 100644 --- a/homeassistant/util/decorator.py +++ b/homeassistant/util/decorator.py @@ -1,12 +1,14 @@ """Decorator utility functions.""" +from typing import Callable, TypeVar +CALLABLE_T = TypeVar('CALLABLE_T', bound=Callable) class Registry(dict): """Registry of items.""" - def register(self, name): + def register(self, name: str) -> Callable[[CALLABLE_T], CALLABLE_T]: """Return decorator to register item with a specific name.""" - def decorator(func): + def decorator(func: CALLABLE_T) -> CALLABLE_T: """Register decorated function.""" self[name] = func return func diff --git a/homeassistant/util/dt.py b/homeassistant/util/dt.py index bae38f27ee25..06159a944a26 100644 --- a/homeassistant/util/dt.py +++ b/homeassistant/util/dt.py @@ -71,14 +71,14 @@ def as_utc(dattim: dt.datetime) -> dt.datetime: return dattim.astimezone(UTC) -def as_timestamp(dt_value): +def as_timestamp(dt_value: dt.datetime) -> float: """Convert a date/time into a unix time (seconds since 1970).""" if hasattr(dt_value, "timestamp"): - parsed_dt = dt_value + parsed_dt = dt_value # type: Optional[dt.datetime] else: parsed_dt = parse_datetime(str(dt_value)) - if not parsed_dt: - raise ValueError("not a valid date/time.") + if parsed_dt is None: + raise ValueError("not a valid date/time.") return parsed_dt.timestamp() @@ -150,7 +150,7 @@ def parse_date(dt_str: str) -> Optional[dt.date]: return None -def parse_time(time_str): +def parse_time(time_str: str) -> Optional[dt.time]: """Parse a time string (00:20:00) into Time object. Return None if invalid. diff --git a/homeassistant/util/json.py b/homeassistant/util/json.py index 1029e58c1186..8ecfebd5b331 100644 --- a/homeassistant/util/json.py +++ b/homeassistant/util/json.py @@ -38,7 +38,7 @@ def load_json(filename: str, default: Union[List, Dict, None] = None) \ return {} if default is None else default -def save_json(filename: str, data: Union[List, Dict]): +def save_json(filename: str, data: Union[List, Dict]) -> None: """Save JSON data to a file. Returns True on success. diff --git a/homeassistant/util/location.py b/homeassistant/util/location.py index 9fc87b24a9b7..16aec2ec6172 100644 --- a/homeassistant/util/location.py +++ b/homeassistant/util/location.py @@ -33,7 +33,7 @@ LocationInfo = collections.namedtuple( 'use_metric']) -def detect_location_info(): +def detect_location_info() -> Optional[LocationInfo]: """Detect location information.""" data = _get_freegeoip() @@ -63,7 +63,7 @@ def distance(lat1: Optional[float], lon1: Optional[float], return result * 1000 -def elevation(latitude, longitude): +def elevation(latitude: float, longitude: float) -> int: """Return elevation for given latitude and longitude.""" try: req = requests.get( diff --git a/homeassistant/util/logging.py b/homeassistant/util/logging.py index 7ce98fc2f2a2..f2bf15d8a03b 100644 --- a/homeassistant/util/logging.py +++ b/homeassistant/util/logging.py @@ -1,7 +1,9 @@ """Logging utilities.""" import asyncio +from asyncio.events import AbstractEventLoop import logging import threading +from typing import Optional from .async_ import run_coroutine_threadsafe @@ -9,12 +11,12 @@ from .async_ import run_coroutine_threadsafe class HideSensitiveDataFilter(logging.Filter): """Filter API password calls.""" - def __init__(self, text): + def __init__(self, text: str) -> None: """Initialize sensitive data filter.""" super().__init__() self.text = text - def filter(self, record): + def filter(self, record: logging.LogRecord) -> bool: """Hide sensitive data in messages.""" record.msg = record.msg.replace(self.text, '*******') @@ -25,7 +27,8 @@ class HideSensitiveDataFilter(logging.Filter): class AsyncHandler: """Logging handler wrapper to add an async layer.""" - def __init__(self, loop, handler): + def __init__( + self, loop: AbstractEventLoop, handler: logging.Handler) -> None: """Initialize async logging handler wrapper.""" self.handler = handler self.loop = loop @@ -45,11 +48,11 @@ class AsyncHandler: self._thread.start() - def close(self): + def close(self) -> None: """Wrap close to handler.""" self.emit(None) - async def async_close(self, blocking=False): + async def async_close(self, blocking: bool = False) -> None: """Close the handler. When blocking=True, will wait till closed. @@ -60,7 +63,7 @@ class AsyncHandler: while self._thread.is_alive(): await asyncio.sleep(0, loop=self.loop) - def emit(self, record): + def emit(self, record: Optional[logging.LogRecord]) -> None: """Process a record.""" ident = self.loop.__dict__.get("_thread_ident") @@ -71,11 +74,11 @@ class AsyncHandler: else: self.loop.call_soon_threadsafe(self._queue.put_nowait, record) - def __repr__(self): + def __repr__(self) -> str: """Return the string names.""" return str(self.handler) - def _process(self): + def _process(self) -> None: """Process log in a thread.""" while True: record = run_coroutine_threadsafe( @@ -87,34 +90,34 @@ class AsyncHandler: self.handler.emit(record) - def createLock(self): + def createLock(self) -> None: """Ignore lock stuff.""" pass - def acquire(self): + def acquire(self) -> None: """Ignore lock stuff.""" pass - def release(self): + def release(self) -> None: """Ignore lock stuff.""" pass @property - def level(self): + def level(self) -> int: """Wrap property level to handler.""" return self.handler.level @property - def formatter(self): + def formatter(self) -> Optional[logging.Formatter]: """Wrap property formatter to handler.""" return self.handler.formatter @property - def name(self): + def name(self) -> str: """Wrap property set_name to handler.""" - return self.handler.get_name() + return self.handler.get_name() # type: ignore @name.setter - def name(self, name): + def name(self, name: str) -> None: """Wrap property get_name to handler.""" - self.handler.name = name + self.handler.set_name(name) # type: ignore diff --git a/homeassistant/util/package.py b/homeassistant/util/package.py index d1d398020dee..9433046e6881 100644 --- a/homeassistant/util/package.py +++ b/homeassistant/util/package.py @@ -16,7 +16,7 @@ _LOGGER = logging.getLogger(__name__) INSTALL_LOCK = threading.Lock() -def is_virtual_env(): +def is_virtual_env() -> bool: """Return if we run in a virtual environtment.""" # Check supports venv && virtualenv return (getattr(sys, 'base_prefix', sys.prefix) != sys.prefix or diff --git a/homeassistant/util/ssl.py b/homeassistant/util/ssl.py index 4f528cfcb516..392c5986c891 100644 --- a/homeassistant/util/ssl.py +++ b/homeassistant/util/ssl.py @@ -4,7 +4,7 @@ import ssl import certifi -def client_context(): +def client_context() -> ssl.SSLContext: """Return an SSL context for making requests.""" context = ssl.create_default_context( purpose=ssl.Purpose.SERVER_AUTH, @@ -13,7 +13,7 @@ def client_context(): return context -def server_context(): +def server_context() -> ssl.SSLContext: """Return an SSL context following the Mozilla recommendations. TLS configuration follows the best-practice guidelines specified here: diff --git a/homeassistant/util/yaml.py b/homeassistant/util/yaml.py index 7ce16600e1b6..40ddfdf7b966 100644 --- a/homeassistant/util/yaml.py +++ b/homeassistant/util/yaml.py @@ -4,7 +4,7 @@ import os import sys import fnmatch from collections import OrderedDict -from typing import Union, List, Dict +from typing import Union, List, Dict, Iterator, overload, TypeVar import yaml try: @@ -22,7 +22,10 @@ from homeassistant.exceptions import HomeAssistantError _LOGGER = logging.getLogger(__name__) _SECRET_NAMESPACE = 'homeassistant' SECRET_YAML = 'secrets.yaml' -__SECRET_CACHE = {} # type: Dict +__SECRET_CACHE = {} # type: Dict[str, JSON_TYPE] + +JSON_TYPE = Union[List, Dict, str] +DICT_T = TypeVar('DICT_T', bound=Dict) class NodeListClass(list): @@ -37,7 +40,42 @@ class NodeStrClass(str): pass -def _add_reference(obj, loader, node): +# pylint: disable=too-many-ancestors +class SafeLineLoader(yaml.SafeLoader): + """Loader class that keeps track of line numbers.""" + + def compose_node(self, parent: yaml.nodes.Node, + index: int) -> yaml.nodes.Node: + """Annotate a node with the first line it was seen.""" + last_line = self.line # type: int + node = super(SafeLineLoader, + self).compose_node(parent, index) # type: yaml.nodes.Node + node.__line__ = last_line + 1 # type: ignore + return node + + +# pylint: disable=pointless-statement +@overload +def _add_reference(obj: Union[list, NodeListClass], + loader: yaml.SafeLoader, + node: yaml.nodes.Node) -> NodeListClass: ... + + +@overload # noqa: F811 +def _add_reference(obj: Union[str, NodeStrClass], + loader: yaml.SafeLoader, + node: yaml.nodes.Node) -> NodeStrClass: ... + + +@overload # noqa: F811 +def _add_reference(obj: DICT_T, + loader: yaml.SafeLoader, + node: yaml.nodes.Node) -> DICT_T: ... +# pylint: enable=pointless-statement + + +def _add_reference(obj, loader: SafeLineLoader, # type: ignore # noqa: F811 + node: yaml.nodes.Node): """Add file reference information to an object.""" if isinstance(obj, list): obj = NodeListClass(obj) @@ -48,20 +86,7 @@ def _add_reference(obj, loader, node): return obj -# pylint: disable=too-many-ancestors -class SafeLineLoader(yaml.SafeLoader): - """Loader class that keeps track of line numbers.""" - - def compose_node(self, parent: yaml.nodes.Node, index) -> yaml.nodes.Node: - """Annotate a node with the first line it was seen.""" - last_line = self.line # type: int - node = super(SafeLineLoader, - self).compose_node(parent, index) # type: yaml.nodes.Node - node.__line__ = last_line + 1 # type: ignore - return node - - -def load_yaml(fname: str) -> Union[List, Dict]: +def load_yaml(fname: str) -> JSON_TYPE: """Load a YAML file.""" try: with open(fname, encoding='utf-8') as conf_file: @@ -83,12 +108,12 @@ def dump(_dict: dict) -> str: .replace(': null\n', ':\n') -def save_yaml(path, data): +def save_yaml(path: str, data: dict) -> None: """Save YAML to a file.""" # Dump before writing to not truncate the file if dumping fails - data = dump(data) + str_data = dump(data) with open(path, 'w', encoding='utf-8') as outfile: - outfile.write(data) + outfile.write(str_data) def clear_secret_cache() -> None: @@ -100,7 +125,7 @@ def clear_secret_cache() -> None: def _include_yaml(loader: SafeLineLoader, - node: yaml.nodes.Node) -> Union[List, Dict]: + node: yaml.nodes.Node) -> JSON_TYPE: """Load another YAML file and embeds it using the !include tag. Example: @@ -115,7 +140,7 @@ def _is_file_valid(name: str) -> bool: return not name.startswith('.') -def _find_files(directory: str, pattern: str): +def _find_files(directory: str, pattern: str) -> Iterator[str]: """Recursively load files in a directory.""" for root, dirs, files in os.walk(directory, topdown=True): dirs[:] = [d for d in dirs if _is_file_valid(d)] @@ -151,7 +176,7 @@ def _include_dir_merge_named_yaml(loader: SafeLineLoader, def _include_dir_list_yaml(loader: SafeLineLoader, - node: yaml.nodes.Node): + node: yaml.nodes.Node) -> List[JSON_TYPE]: """Load multiple files from directory as a list.""" loc = os.path.join(os.path.dirname(loader.name), node.value) return [load_yaml(f) for f in _find_files(loc, '*.yaml') @@ -159,11 +184,11 @@ def _include_dir_list_yaml(loader: SafeLineLoader, def _include_dir_merge_list_yaml(loader: SafeLineLoader, - node: yaml.nodes.Node): + node: yaml.nodes.Node) -> JSON_TYPE: """Load multiple files from directory as a merged list.""" loc = os.path.join(os.path.dirname(loader.name), node.value) # type: str - merged_list = [] # type: List + merged_list = [] # type: List[JSON_TYPE] for fname in _find_files(loc, '*.yaml'): if os.path.basename(fname) == SECRET_YAML: continue @@ -202,14 +227,14 @@ def _ordered_dict(loader: SafeLineLoader, return _add_reference(OrderedDict(nodes), loader, node) -def _construct_seq(loader: SafeLineLoader, node: yaml.nodes.Node): +def _construct_seq(loader: SafeLineLoader, node: yaml.nodes.Node) -> JSON_TYPE: """Add line number and file name to Load YAML sequence.""" obj, = loader.construct_yaml_seq(node) return _add_reference(obj, loader, node) def _env_var_yaml(loader: SafeLineLoader, - node: yaml.nodes.Node): + node: yaml.nodes.Node) -> str: """Load environment variables and embed it into the configuration YAML.""" args = node.value.split() @@ -222,7 +247,7 @@ def _env_var_yaml(loader: SafeLineLoader, raise HomeAssistantError(node.value) -def _load_secret_yaml(secret_path: str) -> Dict: +def _load_secret_yaml(secret_path: str) -> JSON_TYPE: """Load the secrets yaml from path.""" secret_path = os.path.join(secret_path, SECRET_YAML) if secret_path in __SECRET_CACHE: @@ -248,7 +273,7 @@ def _load_secret_yaml(secret_path: str) -> Dict: def _secret_yaml(loader: SafeLineLoader, - node: yaml.nodes.Node): + node: yaml.nodes.Node) -> JSON_TYPE: """Load secrets and embed it into the configuration YAML.""" secret_path = os.path.dirname(loader.name) while True: @@ -308,7 +333,8 @@ yaml.SafeLoader.add_constructor('!include_dir_merge_named', # From: https://gist.github.com/miracle2k/3184458 # pylint: disable=redefined-outer-name -def represent_odict(dump, tag, mapping, flow_style=None): +def represent_odict(dump, tag, mapping, # type: ignore + flow_style=None) -> yaml.MappingNode: """Like BaseRepresenter.represent_mapping but does not issue the sort().""" value = [] # type: list node = yaml.MappingNode(tag, value, flow_style=flow_style) diff --git a/mypy.ini b/mypy.ini index 5a597994d6bd..c92786e643fd 100644 --- a/mypy.ini +++ b/mypy.ini @@ -2,11 +2,18 @@ check_untyped_defs = true follow_imports = silent ignore_missing_imports = true +warn_incomplete_stub = true warn_redundant_casts = true warn_return_any = true warn_unused_configs = true warn_unused_ignores = true +[mypy-homeassistant.*] +disallow_untyped_defs = true + +[mypy-homeassistant.config_entries] +disallow_untyped_defs = false + [mypy-homeassistant.util.yaml] warn_return_any = false diff --git a/tests/components/automation/test_init.py b/tests/components/automation/test_init.py index 33f1a7aa704c..b1990fb80aac 100644 --- a/tests/components/automation/test_init.py +++ b/tests/components/automation/test_init.py @@ -437,10 +437,12 @@ class TestAutomation(unittest.TestCase): } } }}): - automation.reload(self.hass) - self.hass.block_till_done() - # De-flake ?! - self.hass.block_till_done() + with patch('homeassistant.config.find_config_file', + return_value=''): + automation.reload(self.hass) + self.hass.block_till_done() + # De-flake ?! + self.hass.block_till_done() assert self.hass.states.get('automation.hello') is None assert self.hass.states.get('automation.bye') is not None @@ -485,8 +487,10 @@ class TestAutomation(unittest.TestCase): with patch('homeassistant.config.load_yaml_config_file', autospec=True, return_value={automation.DOMAIN: 'not valid'}): - automation.reload(self.hass) - self.hass.block_till_done() + with patch('homeassistant.config.find_config_file', + return_value=''): + automation.reload(self.hass) + self.hass.block_till_done() assert self.hass.states.get('automation.hello') is None @@ -521,8 +525,10 @@ class TestAutomation(unittest.TestCase): with patch('homeassistant.config.load_yaml_config_file', side_effect=HomeAssistantError('bla')): - automation.reload(self.hass) - self.hass.block_till_done() + with patch('homeassistant.config.find_config_file', + return_value=''): + automation.reload(self.hass) + self.hass.block_till_done() assert self.hass.states.get('automation.hello') is not None diff --git a/tests/components/group/test_init.py b/tests/components/group/test_init.py index 31ad70e8abac..a5e9bbc0b820 100644 --- a/tests/components/group/test_init.py +++ b/tests/components/group/test_init.py @@ -365,8 +365,10 @@ class TestComponentsGroup(unittest.TestCase): 'icon': 'mdi:work', 'view': True, }}}): - group.reload(self.hass) - self.hass.block_till_done() + with patch('homeassistant.config.find_config_file', + return_value=''): + group.reload(self.hass) + self.hass.block_till_done() assert sorted(self.hass.states.entity_ids()) == \ ['group.all_tests', 'group.hello'] diff --git a/tests/components/test_script.py b/tests/components/test_script.py index fcb0047c135b..c4282cdfbaf2 100644 --- a/tests/components/test_script.py +++ b/tests/components/test_script.py @@ -199,8 +199,10 @@ class TestScriptComponent(unittest.TestCase): } }] }}}): - script.reload(self.hass) - self.hass.block_till_done() + with patch('homeassistant.config.find_config_file', + return_value=''): + script.reload(self.hass) + self.hass.block_till_done() assert self.hass.states.get(ENTITY_ID) is None assert not self.hass.services.has_service(script.DOMAIN, 'test') From 1325682d828c148cc00c7a032fb1c6a1b1c12fea Mon Sep 17 00:00:00 2001 From: Anders Melchiorsen Date: Mon, 23 Jul 2018 12:29:37 +0200 Subject: [PATCH 055/113] Use case insensitive comparison for Sonos model check (#15604) --- homeassistant/components/media_player/sonos.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/media_player/sonos.py b/homeassistant/components/media_player/sonos.py index da0ad24b1355..8a92d89ce675 100644 --- a/homeassistant/components/media_player/sonos.py +++ b/homeassistant/components/media_player/sonos.py @@ -865,9 +865,10 @@ class SonosDevice(MediaPlayerDevice): """List of available input sources.""" sources = [fav.title for fav in self._favorites] - if 'PLAY:5' in self._model or 'CONNECT' in self._model: + model = self._model.upper() + if 'PLAY:5' in model or 'CONNECT' in model: sources += [SOURCE_LINEIN] - elif 'PLAYBAR' in self._model: + elif 'PLAYBAR' in model: sources += [SOURCE_LINEIN, SOURCE_TV] return sources From fddfb9e41251210a4edabf37a13a0272da52ac56 Mon Sep 17 00:00:00 2001 From: Anders Melchiorsen Date: Mon, 23 Jul 2018 12:31:03 +0200 Subject: [PATCH 056/113] Refresh Sonos source list on changes (#15605) --- homeassistant/components/media_player/sonos.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/media_player/sonos.py b/homeassistant/components/media_player/sonos.py index 8a92d89ce675..5375001f75c5 100644 --- a/homeassistant/components/media_player/sonos.py +++ b/homeassistant/components/media_player/sonos.py @@ -447,11 +447,15 @@ class SonosDevice(MediaPlayerDevice): self.update_volume() - self._favorites = [] + self._set_favorites() + + def _set_favorites(self): + """Set available favorites.""" # SoCo 0.14 raises a generic Exception on invalid xml in favorites. # Filter those out now so our list is safe to use. # pylint: disable=broad-except try: + self._favorites = [] for fav in self.soco.music_library.get_sonos_favorites(): try: if fav.reference.get_uri(): @@ -493,6 +497,9 @@ class SonosDevice(MediaPlayerDevice): queue = _ProcessSonosEventQueue(self.update_groups) player.zoneGroupTopology.subscribe(auto_renew=True, event_queue=queue) + queue = _ProcessSonosEventQueue(self.update_content) + player.contentDirectory.subscribe(auto_renew=True, event_queue=queue) + def update(self): """Retrieve latest state.""" available = self._check_available() @@ -735,6 +742,11 @@ class SonosDevice(MediaPlayerDevice): slave._sonos_group = sonos_group slave.schedule_update_ha_state() + def update_content(self, event=None): + """Update information about available content.""" + self._set_favorites() + self.schedule_update_ha_state() + @property def volume_level(self): """Volume level of the media player (0..1).""" From 3acbd5a769070b4234043081935d4b126e5bdb89 Mon Sep 17 00:00:00 2001 From: Muhammad Sheraz Lodhi Date: Mon, 23 Jul 2018 14:31:54 +0400 Subject: [PATCH 057/113] The tense is wrong (#15614) Instead of spent, we should be using spend :) --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9ad922d70456..86e212bb11d6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,6 @@ # Contributing to Home Assistant -Everybody is invited and welcome to contribute to Home Assistant. There is a lot to do...if you are not a developer perhaps you would like to help with the documentation on [home-assistant.io](https://home-assistant.io/)? If you are a developer and have devices in your home which aren't working with Home Assistant yet, why not spent a couple of hours and help to integrate them? +Everybody is invited and welcome to contribute to Home Assistant. There is a lot to do...if you are not a developer perhaps you would like to help with the documentation on [home-assistant.io](https://home-assistant.io/)? If you are a developer and have devices in your home which aren't working with Home Assistant yet, why not spend a couple of hours and help to integrate them? The process is straight-forward. From 50b6c5948d5912046864946fd02b2fb3afc2ea6b Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Mon, 23 Jul 2018 12:37:23 +0200 Subject: [PATCH 058/113] Suppress error between 00:00 and 01:00 (#15555) * Suppress error between 00:00 and 01:00 Suppress an error that often occers between 00:00 and 01:00 CE(S)T during that time, probably because buienradar.nl is then updating its forcast for the next day. The API does not always work between these times (in the middle of the night). * white space & import * unnecessary brackets --- homeassistant/components/sensor/buienradar.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/sensor/buienradar.py b/homeassistant/components/sensor/buienradar.py index 33c7e432fca3..992c27bbe2ea 100644 --- a/homeassistant/components/sensor/buienradar.py +++ b/homeassistant/components/sensor/buienradar.py @@ -5,7 +5,7 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.buienradar/ """ import asyncio -from datetime import timedelta +from datetime import datetime, timedelta import logging import async_timeout @@ -481,9 +481,10 @@ class BrData: _LOGGER.debug("Buienradar parsed data: %s", result) if result.get(SUCCESS) is not True: - _LOGGER.warning("Unable to parse data from Buienradar." - "(Msg: %s)", - result.get(MESSAGE),) + if int(datetime.now().strftime('%H')) > 0: + _LOGGER.warning("Unable to parse data from Buienradar." + "(Msg: %s)", + result.get(MESSAGE),) yield from self.schedule_update(SCHEDULE_NOK) return From ea2ff6aae32821b8faaa7e5eb00efce779ebac73 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 23 Jul 2018 14:05:38 +0200 Subject: [PATCH 059/113] Use async_create_task (#15633) * Use async_create_task * Fix test --- homeassistant/components/__init__.py | 4 ++-- .../components/alarm_control_panel/__init__.py | 13 +++++++------ homeassistant/components/alert.py | 10 +++++----- homeassistant/components/android_ip_webcam.py | 8 ++++---- homeassistant/components/apple_tv.py | 4 ++-- homeassistant/components/cast/__init__.py | 4 ++-- homeassistant/components/deconz/__init__.py | 2 +- homeassistant/components/eight_sleep.py | 4 ++-- homeassistant/components/envisalink.py | 6 +++--- homeassistant/components/homematicip_cloud/hap.py | 2 +- homeassistant/components/hue/bridge.py | 2 +- homeassistant/components/insteon_plm/__init__.py | 2 +- homeassistant/components/knx.py | 2 +- homeassistant/components/lutron_caseta.py | 4 ++-- homeassistant/components/mysensors/gateway.py | 2 +- homeassistant/components/nest/__init__.py | 2 +- homeassistant/components/rainmachine/__init__.py | 2 +- homeassistant/components/sabnzbd.py | 2 +- homeassistant/components/satel_integra.py | 4 ++-- homeassistant/components/sonos/__init__.py | 4 ++-- homeassistant/components/spc.py | 4 ++-- homeassistant/components/tradfri.py | 4 ++-- homeassistant/components/upnp.py | 2 +- homeassistant/components/velux.py | 2 +- homeassistant/helpers/discovery.py | 2 +- tests/components/deconz/test_init.py | 2 +- 26 files changed, 50 insertions(+), 49 deletions(-) diff --git a/homeassistant/components/__init__.py b/homeassistant/components/__init__.py index f0c4f7bb3e23..06e28c42b13c 100644 --- a/homeassistant/components/__init__.py +++ b/homeassistant/components/__init__.py @@ -167,7 +167,7 @@ def async_setup(hass, config): def async_handle_core_service(call): """Service handler for handling core services.""" if call.service == SERVICE_HOMEASSISTANT_STOP: - hass.async_add_job(hass.async_stop()) + hass.async_create_task(hass.async_stop()) return try: @@ -183,7 +183,7 @@ def async_setup(hass, config): return if call.service == SERVICE_HOMEASSISTANT_RESTART: - hass.async_add_job(hass.async_stop(RESTART_EXIT_CODE)) + hass.async_create_task(hass.async_stop(RESTART_EXIT_CODE)) hass.services.async_register( ha.DOMAIN, SERVICE_HOMEASSISTANT_STOP, async_handle_core_service) diff --git a/homeassistant/components/alarm_control_panel/__init__.py b/homeassistant/components/alarm_control_panel/__init__.py index 84a72945a7e2..0a4dd6bde784 100644 --- a/homeassistant/components/alarm_control_panel/__init__.py +++ b/homeassistant/components/alarm_control_panel/__init__.py @@ -187,7 +187,7 @@ class AlarmControlPanel(Entity): This method must be run in the event loop and returns a coroutine. """ - return self.hass.async_add_job(self.alarm_disarm, code) + return self.hass.async_add_executor_job(self.alarm_disarm, code) def alarm_arm_home(self, code=None): """Send arm home command.""" @@ -198,7 +198,7 @@ class AlarmControlPanel(Entity): This method must be run in the event loop and returns a coroutine. """ - return self.hass.async_add_job(self.alarm_arm_home, code) + return self.hass.async_add_executor_job(self.alarm_arm_home, code) def alarm_arm_away(self, code=None): """Send arm away command.""" @@ -209,7 +209,7 @@ class AlarmControlPanel(Entity): This method must be run in the event loop and returns a coroutine. """ - return self.hass.async_add_job(self.alarm_arm_away, code) + return self.hass.async_add_executor_job(self.alarm_arm_away, code) def alarm_arm_night(self, code=None): """Send arm night command.""" @@ -220,7 +220,7 @@ class AlarmControlPanel(Entity): This method must be run in the event loop and returns a coroutine. """ - return self.hass.async_add_job(self.alarm_arm_night, code) + return self.hass.async_add_executor_job(self.alarm_arm_night, code) def alarm_trigger(self, code=None): """Send alarm trigger command.""" @@ -231,7 +231,7 @@ class AlarmControlPanel(Entity): This method must be run in the event loop and returns a coroutine. """ - return self.hass.async_add_job(self.alarm_trigger, code) + return self.hass.async_add_executor_job(self.alarm_trigger, code) def alarm_arm_custom_bypass(self, code=None): """Send arm custom bypass command.""" @@ -242,7 +242,8 @@ class AlarmControlPanel(Entity): This method must be run in the event loop and returns a coroutine. """ - return self.hass.async_add_job(self.alarm_arm_custom_bypass, code) + return self.hass.async_add_executor_job( + self.alarm_arm_custom_bypass, code) @property def state_attributes(self): diff --git a/homeassistant/components/alert.py b/homeassistant/components/alert.py index 9d47e4bd322f..80a02b3275d6 100644 --- a/homeassistant/components/alert.py +++ b/homeassistant/components/alert.py @@ -68,7 +68,7 @@ def turn_on(hass, entity_id): def async_turn_on(hass, entity_id): """Async reset the alert.""" data = {ATTR_ENTITY_ID: entity_id} - hass.async_add_job( + hass.async_create_task( hass.services.async_call(DOMAIN, SERVICE_TURN_ON, data)) @@ -81,7 +81,7 @@ def turn_off(hass, entity_id): def async_turn_off(hass, entity_id): """Async acknowledge the alert.""" data = {ATTR_ENTITY_ID: entity_id} - hass.async_add_job( + hass.async_create_task( hass.services.async_call(DOMAIN, SERVICE_TURN_OFF, data)) @@ -94,7 +94,7 @@ def toggle(hass, entity_id): def async_toggle(hass, entity_id): """Async toggle acknowledgement of alert.""" data = {ATTR_ENTITY_ID: entity_id} - hass.async_add_job( + hass.async_create_task( hass.services.async_call(DOMAIN, SERVICE_TOGGLE, data)) @@ -217,7 +217,7 @@ class Alert(ToggleEntity): else: yield from self._schedule_notify() - self.hass.async_add_job(self.async_update_ha_state) + self.async_schedule_update_ha_state() @asyncio.coroutine def end_alerting(self): @@ -228,7 +228,7 @@ class Alert(ToggleEntity): self._firing = False if self._done_message and self._send_done_message: yield from self._notify_done_message() - self.hass.async_add_job(self.async_update_ha_state) + self.async_schedule_update_ha_state() @asyncio.coroutine def _schedule_notify(self): diff --git a/homeassistant/components/android_ip_webcam.py b/homeassistant/components/android_ip_webcam.py index 13fa64438d37..5da117e74c38 100644 --- a/homeassistant/components/android_ip_webcam.py +++ b/homeassistant/components/android_ip_webcam.py @@ -214,11 +214,11 @@ def async_setup(hass, config): CONF_PASSWORD: password }) - hass.async_add_job(discovery.async_load_platform( + hass.async_create_task(discovery.async_load_platform( hass, 'camera', 'mjpeg', mjpeg_camera, config)) if sensors: - hass.async_add_job(discovery.async_load_platform( + hass.async_create_task(discovery.async_load_platform( hass, 'sensor', DOMAIN, { CONF_NAME: name, CONF_HOST: host, @@ -226,7 +226,7 @@ def async_setup(hass, config): }, config)) if switches: - hass.async_add_job(discovery.async_load_platform( + hass.async_create_task(discovery.async_load_platform( hass, 'switch', DOMAIN, { CONF_NAME: name, CONF_HOST: host, @@ -234,7 +234,7 @@ def async_setup(hass, config): }, config)) if motion: - hass.async_add_job(discovery.async_load_platform( + hass.async_create_task(discovery.async_load_platform( hass, 'binary_sensor', DOMAIN, { CONF_HOST: host, CONF_NAME: name, diff --git a/homeassistant/components/apple_tv.py b/homeassistant/components/apple_tv.py index 68445092db7e..958e4a197775 100644 --- a/homeassistant/components/apple_tv.py +++ b/homeassistant/components/apple_tv.py @@ -218,10 +218,10 @@ def _setup_atv(hass, atv_config): ATTR_POWER: power } - hass.async_add_job(discovery.async_load_platform( + hass.async_create_task(discovery.async_load_platform( hass, 'media_player', DOMAIN, atv_config)) - hass.async_add_job(discovery.async_load_platform( + hass.async_create_task(discovery.async_load_platform( hass, 'remote', DOMAIN, atv_config)) diff --git a/homeassistant/components/cast/__init__.py b/homeassistant/components/cast/__init__.py index a4ee25f0915c..966107bcb36a 100644 --- a/homeassistant/components/cast/__init__.py +++ b/homeassistant/components/cast/__init__.py @@ -14,7 +14,7 @@ async def async_setup(hass, config): async def async_setup_entry(hass, entry): """Set up Cast from a config entry.""" - hass.async_add_job(hass.config_entries.async_forward_entry_setup( + hass.async_create_task(hass.config_entries.async_forward_entry_setup( entry, 'media_player')) return True @@ -23,7 +23,7 @@ async def _async_has_devices(hass): """Return if there are devices that can be discovered.""" from pychromecast.discovery import discover_chromecasts - return await hass.async_add_job(discover_chromecasts) + return await hass.async_add_executor_job(discover_chromecasts) config_entry_flow.register_discovery_flow( diff --git a/homeassistant/components/deconz/__init__.py b/homeassistant/components/deconz/__init__.py index 414d4b0766c8..e0982c65f334 100644 --- a/homeassistant/components/deconz/__init__.py +++ b/homeassistant/components/deconz/__init__.py @@ -97,7 +97,7 @@ async def async_setup_entry(hass, config_entry): hass.data[DATA_DECONZ_UNSUB] = [] for component in ['binary_sensor', 'light', 'scene', 'sensor']: - hass.async_add_job(hass.config_entries.async_forward_entry_setup( + hass.async_create_task(hass.config_entries.async_forward_entry_setup( config_entry, component)) @callback diff --git a/homeassistant/components/eight_sleep.py b/homeassistant/components/eight_sleep.py index 704eab1846bb..209fa7ba8793 100644 --- a/homeassistant/components/eight_sleep.py +++ b/homeassistant/components/eight_sleep.py @@ -143,12 +143,12 @@ async def async_setup(hass, config): # No users, cannot continue return False - hass.async_add_job(discovery.async_load_platform( + hass.async_create_task(discovery.async_load_platform( hass, 'sensor', DOMAIN, { CONF_SENSORS: sensors, }, config)) - hass.async_add_job(discovery.async_load_platform( + hass.async_create_task(discovery.async_load_platform( hass, 'binary_sensor', DOMAIN, { CONF_BINARY_SENSORS: binary_sensors, }, config)) diff --git a/homeassistant/components/envisalink.py b/homeassistant/components/envisalink.py index 5ffd97ef0e31..7dd4e7dc32ae 100644 --- a/homeassistant/components/envisalink.py +++ b/homeassistant/components/envisalink.py @@ -167,21 +167,21 @@ def async_setup(hass, config): # Load sub-components for Envisalink if partitions: - hass.async_add_job(async_load_platform( + hass.async_create_task(async_load_platform( hass, 'alarm_control_panel', 'envisalink', { CONF_PARTITIONS: partitions, CONF_CODE: code, CONF_PANIC: panic_type }, config )) - hass.async_add_job(async_load_platform( + hass.async_create_task(async_load_platform( hass, 'sensor', 'envisalink', { CONF_PARTITIONS: partitions, CONF_CODE: code }, config )) if zones: - hass.async_add_job(async_load_platform( + hass.async_create_task(async_load_platform( hass, 'binary_sensor', 'envisalink', { CONF_ZONES: zones }, config diff --git a/homeassistant/components/homematicip_cloud/hap.py b/homeassistant/components/homematicip_cloud/hap.py index 57bb9488ee83..9715a5fc0240 100644 --- a/homeassistant/components/homematicip_cloud/hap.py +++ b/homeassistant/components/homematicip_cloud/hap.py @@ -117,7 +117,7 @@ class HomematicipHAP: self.config_entry.data.get(HMIPC_HAPID)) for component in COMPONENTS: - self.hass.async_add_job( + self.hass.async_create_task( self.hass.config_entries.async_forward_entry_setup( self.config_entry, component) ) diff --git a/homeassistant/components/hue/bridge.py b/homeassistant/components/hue/bridge.py index f528de462efe..b7cf0e1de07d 100644 --- a/homeassistant/components/hue/bridge.py +++ b/homeassistant/components/hue/bridge.py @@ -78,7 +78,7 @@ class HueBridge: host) return False - hass.async_add_job(hass.config_entries.async_forward_entry_setup( + hass.async_create_task(hass.config_entries.async_forward_entry_setup( self.config_entry, 'light')) hass.services.async_register( diff --git a/homeassistant/components/insteon_plm/__init__.py b/homeassistant/components/insteon_plm/__init__.py index 7845b47e2189..055015b74f5d 100644 --- a/homeassistant/components/insteon_plm/__init__.py +++ b/homeassistant/components/insteon_plm/__init__.py @@ -148,7 +148,7 @@ def async_setup(hass, config): device.states[state_key].name, platform) - hass.async_add_job( + hass.async_create_task( discovery.async_load_platform( hass, platform, DOMAIN, discovered={'address': device.address.id, diff --git a/homeassistant/components/knx.py b/homeassistant/components/knx.py index c6101230031c..5b3af3029b4f 100644 --- a/homeassistant/components/knx.py +++ b/homeassistant/components/knx.py @@ -107,7 +107,7 @@ async def async_setup(hass, config): ('scene', 'Scene'), ('notify', 'Notification')): found_devices = _get_devices(hass, discovery_type) - hass.async_add_job( + hass.async_create_task( discovery.async_load_platform(hass, component, DOMAIN, { ATTR_DISCOVER_DEVICES: found_devices }, config)) diff --git a/homeassistant/components/lutron_caseta.py b/homeassistant/components/lutron_caseta.py index 7b1b7417cfd9..2535fb761209 100644 --- a/homeassistant/components/lutron_caseta.py +++ b/homeassistant/components/lutron_caseta.py @@ -63,8 +63,8 @@ def async_setup(hass, base_config): _LOGGER.info("Connected to Lutron smartbridge at %s", config[CONF_HOST]) for component in LUTRON_CASETA_COMPONENTS: - hass.async_add_job(discovery.async_load_platform(hass, component, - DOMAIN, {}, config)) + hass.async_create_task(discovery.async_load_platform( + hass, component, DOMAIN, {}, config)) return True diff --git a/homeassistant/components/mysensors/gateway.py b/homeassistant/components/mysensors/gateway.py index a7de62cd3292..8c80604d1881 100644 --- a/homeassistant/components/mysensors/gateway.py +++ b/homeassistant/components/mysensors/gateway.py @@ -178,7 +178,7 @@ async def _discover_persistent_devices(hass, gateway): @callback def _discover_mysensors_platform(hass, platform, new_devices): """Discover a MySensors platform.""" - task = hass.async_add_job(discovery.async_load_platform( + task = hass.async_create_task(discovery.async_load_platform( hass, platform, DOMAIN, {ATTR_DEVICES: new_devices, CONF_NAME: DOMAIN})) return task diff --git a/homeassistant/components/nest/__init__.py b/homeassistant/components/nest/__init__.py index 23a331b21b2d..4406062c8214 100644 --- a/homeassistant/components/nest/__init__.py +++ b/homeassistant/components/nest/__init__.py @@ -127,7 +127,7 @@ async def async_setup_entry(hass, entry): return False for component in 'climate', 'camera', 'sensor', 'binary_sensor': - hass.async_add_job(hass.config_entries.async_forward_entry_setup( + hass.async_create_task(hass.config_entries.async_forward_entry_setup( entry, component)) def set_mode(service): diff --git a/homeassistant/components/rainmachine/__init__.py b/homeassistant/components/rainmachine/__init__.py index b6546f2e67b7..9f15c8b373fa 100644 --- a/homeassistant/components/rainmachine/__init__.py +++ b/homeassistant/components/rainmachine/__init__.py @@ -151,7 +151,7 @@ async def async_setup(hass, config): ('sensor', conf[CONF_SENSORS]), ('switch', conf[CONF_SWITCHES]), ]: - hass.async_add_job( + hass.async_create_task( discovery.async_load_platform(hass, component, DOMAIN, schema, config)) diff --git a/homeassistant/components/sabnzbd.py b/homeassistant/components/sabnzbd.py index a7b33b4c6970..b9c75c87c1d1 100644 --- a/homeassistant/components/sabnzbd.py +++ b/homeassistant/components/sabnzbd.py @@ -134,7 +134,7 @@ def async_setup_sabnzbd(hass, sab_api, config, name): if config.get(CONF_SENSORS): hass.data[DATA_SABNZBD] = sab_api_data - hass.async_add_job( + hass.async_create_task( discovery.async_load_platform(hass, 'sensor', DOMAIN, {}, config)) async def async_service_handler(service): diff --git a/homeassistant/components/satel_integra.py b/homeassistant/components/satel_integra.py index 4247855da39d..128377d19f76 100644 --- a/homeassistant/components/satel_integra.py +++ b/homeassistant/components/satel_integra.py @@ -98,10 +98,10 @@ def async_setup(hass, config): conf, conf.get(CONF_ARM_HOME_MODE)) - task_control_panel = hass.async_add_job( + task_control_panel = hass.async_create_task( async_load_platform(hass, 'alarm_control_panel', DOMAIN, conf, config)) - task_zones = hass.async_add_job( + task_zones = hass.async_create_task( async_load_platform(hass, 'binary_sensor', DOMAIN, {CONF_ZONES: zones}, config)) diff --git a/homeassistant/components/sonos/__init__.py b/homeassistant/components/sonos/__init__.py index 7c3de2107681..c67a3dffaaf2 100644 --- a/homeassistant/components/sonos/__init__.py +++ b/homeassistant/components/sonos/__init__.py @@ -14,7 +14,7 @@ async def async_setup(hass, config): async def async_setup_entry(hass, entry): """Set up Sonos from a config entry.""" - hass.async_add_job(hass.config_entries.async_forward_entry_setup( + hass.async_create_task(hass.config_entries.async_forward_entry_setup( entry, 'media_player')) return True @@ -23,7 +23,7 @@ async def _async_has_devices(hass): """Return if there are devices that can be discovered.""" import soco - return await hass.async_add_job(soco.discover) + return await hass.async_add_executor_job(soco.discover) config_entry_flow.register_discovery_flow(DOMAIN, 'Sonos', _async_has_devices) diff --git a/homeassistant/components/spc.py b/homeassistant/components/spc.py index 52d4165f3faf..bf7db87f06b3 100644 --- a/homeassistant/components/spc.py +++ b/homeassistant/components/spc.py @@ -56,14 +56,14 @@ def async_setup(hass, config): # add sensor devices for each zone (typically motion/fire/door sensors) zones = yield from api.get_zones() if zones: - hass.async_add_job(discovery.async_load_platform( + hass.async_create_task(discovery.async_load_platform( hass, 'binary_sensor', DOMAIN, {ATTR_DISCOVER_DEVICES: zones}, config)) # create a separate alarm panel for each area areas = yield from api.get_areas() if areas: - hass.async_add_job(discovery.async_load_platform( + hass.async_create_task(discovery.async_load_platform( hass, 'alarm_control_panel', DOMAIN, {ATTR_DISCOVER_AREAS: areas}, config)) diff --git a/homeassistant/components/tradfri.py b/homeassistant/components/tradfri.py index 9ed613abde04..b2e41902552b 100644 --- a/homeassistant/components/tradfri.py +++ b/homeassistant/components/tradfri.py @@ -166,8 +166,8 @@ async def _setup_gateway(hass, hass_config, host, identity, key, return True gateways[gateway_id] = gateway - hass.async_add_job(discovery.async_load_platform( + hass.async_create_task(discovery.async_load_platform( hass, 'light', DOMAIN, {'gateway': gateway_id}, hass_config)) - hass.async_add_job(discovery.async_load_platform( + hass.async_create_task(discovery.async_load_platform( hass, 'sensor', DOMAIN, {'gateway': gateway_id}, hass_config)) return True diff --git a/homeassistant/components/upnp.py b/homeassistant/components/upnp.py index 8aeb93fed25f..b4fe9d3fce9c 100644 --- a/homeassistant/components/upnp.py +++ b/homeassistant/components/upnp.py @@ -88,7 +88,7 @@ async def async_setup(hass, config): service = device.find_first_service(IP_SERVICE) if _service['serviceType'] == CIC_SERVICE: unit = config.get(CONF_UNITS) - hass.async_add_job(discovery.async_load_platform( + hass.async_create_task(discovery.async_load_platform( hass, 'sensor', DOMAIN, {'unit': unit}, config)) except UpnpSoapError as error: _LOGGER.error(error) diff --git a/homeassistant/components/velux.py b/homeassistant/components/velux.py index 47daf17f2a92..c3c6c1e21140 100644 --- a/homeassistant/components/velux.py +++ b/homeassistant/components/velux.py @@ -39,7 +39,7 @@ async def async_setup(hass, config): return False for component in SUPPORTED_DOMAINS: - hass.async_add_job( + hass.async_create_task( discovery.async_load_platform(hass, component, DOMAIN, {}, config)) return True diff --git a/homeassistant/helpers/discovery.py b/homeassistant/helpers/discovery.py index cb587c432c18..7d0730a969c6 100644 --- a/homeassistant/helpers/discovery.py +++ b/homeassistant/helpers/discovery.py @@ -145,7 +145,7 @@ async def async_load_platform(hass, component, platform, discovered=None, Use `listen_platform` to register a callback for these events. Warning: Do not await this inside a setup method to avoid a dead lock. - Use `hass.async_add_job(async_load_platform(..))` instead. + Use `hass.async_create_task(async_load_platform(..))` instead. This method is a coroutine. """ diff --git a/tests/components/deconz/test_init.py b/tests/components/deconz/test_init.py index 1cee08feb0a2..8f5342de1e3a 100644 --- a/tests/components/deconz/test_init.py +++ b/tests/components/deconz/test_init.py @@ -91,7 +91,7 @@ async def test_setup_entry_successful(hass): """Test setup entry is successful.""" entry = Mock() entry.data = {'host': '1.2.3.4', 'port': 80, 'api_key': '1234567890ABCDEF'} - with patch.object(hass, 'async_add_job') as mock_add_job, \ + with patch.object(hass, 'async_create_task') as mock_add_job, \ patch.object(hass, 'config_entries') as mock_config_entries, \ patch('pydeconz.DeconzSession.async_load_parameters', return_value=mock_coro(True)): From 4e7dbf9ce5e5e2eb471fb289048a17ee0d7ccc02 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 23 Jul 2018 14:06:09 +0200 Subject: [PATCH 060/113] Allow system users to refresh tokens (#15574) --- homeassistant/components/auth/__init__.py | 45 ++++++++++------ tests/components/auth/test_init.py | 65 +++++++++++++++++++++++ 2 files changed, 93 insertions(+), 17 deletions(-) diff --git a/homeassistant/components/auth/__init__.py b/homeassistant/components/auth/__init__.py index 3eac6a370d2e..ee4013302093 100644 --- a/homeassistant/components/auth/__init__.py +++ b/homeassistant/components/auth/__init__.py @@ -252,6 +252,20 @@ class GrantTokenView(HomeAssistantView): hass = request.app['hass'] data = await request.post() + grant_type = data.get('grant_type') + + if grant_type == 'authorization_code': + return await self._async_handle_auth_code(hass, data) + + if grant_type == 'refresh_token': + return await self._async_handle_refresh_token(hass, data) + + return self.json({ + 'error': 'unsupported_grant_type', + }, status_code=400) + + async def _async_handle_auth_code(self, hass, data): + """Handle authorization code request.""" client_id = data.get('client_id') if client_id is None or not indieauth.verify_client_id(client_id): return self.json({ @@ -259,21 +273,6 @@ class GrantTokenView(HomeAssistantView): 'error_description': 'Invalid client id', }, status_code=400) - grant_type = data.get('grant_type') - - if grant_type == 'authorization_code': - return await self._async_handle_auth_code(hass, client_id, data) - - if grant_type == 'refresh_token': - return await self._async_handle_refresh_token( - hass, client_id, data) - - return self.json({ - 'error': 'unsupported_grant_type', - }, status_code=400) - - async def _async_handle_auth_code(self, hass, client_id, data): - """Handle authorization code request.""" code = data.get('code') if code is None: @@ -309,8 +308,15 @@ class GrantTokenView(HomeAssistantView): int(refresh_token.access_token_expiration.total_seconds()), }) - async def _async_handle_refresh_token(self, hass, client_id, data): + async def _async_handle_refresh_token(self, hass, data): """Handle authorization code request.""" + client_id = data.get('client_id') + if client_id is not None and not indieauth.verify_client_id(client_id): + return self.json({ + 'error': 'invalid_request', + 'error_description': 'Invalid client id', + }, status_code=400) + token = data.get('refresh_token') if token is None: @@ -320,11 +326,16 @@ class GrantTokenView(HomeAssistantView): refresh_token = await hass.auth.async_get_refresh_token(token) - if refresh_token is None or refresh_token.client_id != client_id: + if refresh_token is None: return self.json({ 'error': 'invalid_grant', }, status_code=400) + if refresh_token.client_id != client_id: + return self.json({ + 'error': 'invalid_request', + }, status_code=400) + access_token = hass.auth.async_create_access_token(refresh_token) return self.json({ diff --git a/tests/components/auth/test_init.py b/tests/components/auth/test_init.py index 807bf15854b9..467d6dc3c6c0 100644 --- a/tests/components/auth/test_init.py +++ b/tests/components/auth/test_init.py @@ -130,3 +130,68 @@ async def test_cors_on_token(hass, aiohttp_client): 'origin': 'http://example.com' }) assert resp.headers['Access-Control-Allow-Origin'] == 'http://example.com' + + +async def test_refresh_token_system_generated(hass, aiohttp_client): + """Test that we can get access tokens for system generated user.""" + client = await async_setup_auth(hass, aiohttp_client) + user = await hass.auth.async_create_system_user('Test System') + refresh_token = await hass.auth.async_create_refresh_token(user, None) + + resp = await client.post('/auth/token', data={ + 'client_id': 'https://this-is-not-allowed-for-system-users.com/', + 'grant_type': 'refresh_token', + 'refresh_token': refresh_token.token, + }) + + assert resp.status == 400 + result = await resp.json() + assert result['error'] == 'invalid_request' + + resp = await client.post('/auth/token', data={ + 'grant_type': 'refresh_token', + 'refresh_token': refresh_token.token, + }) + + assert resp.status == 200 + tokens = await resp.json() + assert hass.auth.async_get_access_token(tokens['access_token']) is not None + + +async def test_refresh_token_different_client_id(hass, aiohttp_client): + """Test that we verify client ID.""" + client = await async_setup_auth(hass, aiohttp_client) + user = await hass.auth.async_create_user('Test User') + refresh_token = await hass.auth.async_create_refresh_token(user, CLIENT_ID) + + # No client ID + resp = await client.post('/auth/token', data={ + 'grant_type': 'refresh_token', + 'refresh_token': refresh_token.token, + }) + + assert resp.status == 400 + result = await resp.json() + assert result['error'] == 'invalid_request' + + # Different client ID + resp = await client.post('/auth/token', data={ + 'client_id': 'http://example-different.com', + 'grant_type': 'refresh_token', + 'refresh_token': refresh_token.token, + }) + + assert resp.status == 400 + result = await resp.json() + assert result['error'] == 'invalid_request' + + # Correct + resp = await client.post('/auth/token', data={ + 'client_id': CLIENT_ID, + 'grant_type': 'refresh_token', + 'refresh_token': refresh_token.token, + }) + + assert resp.status == 200 + tokens = await resp.json() + assert hass.auth.async_get_access_token(tokens['access_token']) is not None From 8213b1476f0e537fde5202b1b3d1ec0768004b03 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 23 Jul 2018 14:14:57 +0200 Subject: [PATCH 061/113] WIP: Hass.io sent token to supervisor (#15536) Hass.io sent token to supervisor --- homeassistant/components/hassio/__init__.py | 26 +++- homeassistant/components/hassio/handler.py | 21 ++- tests/components/hassio/test_init.py | 140 +++++++++++--------- 3 files changed, 114 insertions(+), 73 deletions(-) diff --git a/homeassistant/components/hassio/__init__.py b/homeassistant/components/hassio/__init__.py index 13bc6e9c5585..3a3e19fb4843 100644 --- a/homeassistant/components/hassio/__init__.py +++ b/homeassistant/components/hassio/__init__.py @@ -27,6 +27,8 @@ _LOGGER = logging.getLogger(__name__) DOMAIN = 'hassio' DEPENDENCIES = ['http'] +STORAGE_KEY = DOMAIN +STORAGE_VERSION = 1 CONF_FRONTEND_REPO = 'development_repo' @@ -167,6 +169,21 @@ def async_setup(hass, config): _LOGGER.error("Not connected with Hass.io") return False + store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY) + data = yield from store.async_load() + + if data is None: + data = {} + + if 'hassio_user' in data: + user = yield from hass.auth.async_get_user(data['hassio_user']) + refresh_token = list(user.refresh_tokens.values())[0] + else: + user = yield from hass.auth.async_create_system_user('Hass.io') + refresh_token = yield from hass.auth.async_create_refresh_token(user) + data['hassio_user'] = user.id + yield from store.async_save(data) + # This overrides the normal API call that would be forwarded development_repo = config.get(DOMAIN, {}).get(CONF_FRONTEND_REPO) if development_repo is not None: @@ -186,8 +203,13 @@ def async_setup(hass, config): embed_iframe=True, ) - if 'http' in config: - yield from hassio.update_hass_api(config['http']) + # Temporary. No refresh token tells supervisor to use API password. + if hass.auth.active: + token = refresh_token.token + else: + token = None + + yield from hassio.update_hass_api(config.get('http', {}), token) if 'homeassistant' in config: yield from hassio.update_hass_timezone(config['homeassistant']) diff --git a/homeassistant/components/hassio/handler.py b/homeassistant/components/hassio/handler.py index 5410cb9ec1a4..d75529a99b03 100644 --- a/homeassistant/components/hassio/handler.py +++ b/homeassistant/components/hassio/handler.py @@ -23,10 +23,9 @@ X_HASSIO = 'X-HASSIO-KEY' def _api_bool(funct): """Return a boolean.""" - @asyncio.coroutine - def _wrapper(*argv, **kwargs): + async def _wrapper(*argv, **kwargs): """Wrap function.""" - data = yield from funct(*argv, **kwargs) + data = await funct(*argv, **kwargs) return data and data['result'] == "ok" return _wrapper @@ -34,10 +33,9 @@ def _api_bool(funct): def _api_data(funct): """Return data of an api.""" - @asyncio.coroutine - def _wrapper(*argv, **kwargs): + async def _wrapper(*argv, **kwargs): """Wrap function.""" - data = yield from funct(*argv, **kwargs) + data = await funct(*argv, **kwargs) if data and data['result'] == "ok": return data['data'] return None @@ -94,24 +92,23 @@ class HassIO: return self.send_command("/homeassistant/check", timeout=300) @_api_bool - def update_hass_api(self, http_config): - """Update Home Assistant API data on Hass.io. - - This method return a coroutine. - """ + async def update_hass_api(self, http_config, refresh_token): + """Update Home Assistant API data on Hass.io.""" port = http_config.get(CONF_SERVER_PORT) or SERVER_PORT options = { 'ssl': CONF_SSL_CERTIFICATE in http_config, 'port': port, 'password': http_config.get(CONF_API_PASSWORD), 'watchdog': True, + 'refresh_token': refresh_token, } if CONF_SERVER_HOST in http_config: options['watchdog'] = False _LOGGER.warning("Don't use 'server_host' options with Hass.io") - return self.send_command("/homeassistant/options", payload=options) + return await self.send_command("/homeassistant/options", + payload=options) @_api_bool def update_hass_timezone(self, core_config): diff --git a/tests/components/hassio/test_init.py b/tests/components/hassio/test_init.py index f67a6cbccec4..b19756697311 100644 --- a/tests/components/hassio/test_init.py +++ b/tests/components/hassio/test_init.py @@ -3,8 +3,11 @@ import asyncio import os from unittest.mock import patch, Mock +import pytest + from homeassistant.setup import async_setup_component -from homeassistant.components.hassio import async_check_config +from homeassistant.components.hassio import ( + STORAGE_KEY, async_check_config) from tests.common import mock_coro @@ -15,20 +18,28 @@ MOCK_ENVIRON = { } -@asyncio.coroutine -def test_setup_api_ping(hass, aioclient_mock): - """Test setup with API ping.""" +@pytest.fixture(autouse=True) +def mock_all(aioclient_mock): + """Mock all setup requests.""" + aioclient_mock.post( + "http://127.0.0.1/homeassistant/options", json={'result': 'ok'}) aioclient_mock.get( "http://127.0.0.1/supervisor/ping", json={'result': 'ok'}) + aioclient_mock.post( + "http://127.0.0.1/supervisor/options", json={'result': 'ok'}) aioclient_mock.get( "http://127.0.0.1/homeassistant/info", json={ 'result': 'ok', 'data': {'last_version': '10.0'}}) + +@asyncio.coroutine +def test_setup_api_ping(hass, aioclient_mock): + """Test setup with API ping.""" with patch.dict(os.environ, MOCK_ENVIRON): result = yield from async_setup_component(hass, 'hassio', {}) assert result - assert aioclient_mock.call_count == 2 + assert aioclient_mock.call_count == 3 assert hass.components.hassio.get_homeassistant_version() == "10.0" assert hass.components.hassio.is_hassio() @@ -36,14 +47,6 @@ def test_setup_api_ping(hass, aioclient_mock): @asyncio.coroutine def test_setup_api_push_api_data(hass, aioclient_mock): """Test setup with API push.""" - aioclient_mock.get( - "http://127.0.0.1/supervisor/ping", json={'result': 'ok'}) - aioclient_mock.get( - "http://127.0.0.1/homeassistant/info", json={ - 'result': 'ok', 'data': {'last_version': '10.0'}}) - aioclient_mock.post( - "http://127.0.0.1/homeassistant/options", json={'result': 'ok'}) - with patch.dict(os.environ, MOCK_ENVIRON): result = yield from async_setup_component(hass, 'hassio', { 'http': { @@ -64,14 +67,6 @@ def test_setup_api_push_api_data(hass, aioclient_mock): @asyncio.coroutine def test_setup_api_push_api_data_server_host(hass, aioclient_mock): """Test setup with API push with active server host.""" - aioclient_mock.get( - "http://127.0.0.1/supervisor/ping", json={'result': 'ok'}) - aioclient_mock.get( - "http://127.0.0.1/homeassistant/info", json={ - 'result': 'ok', 'data': {'last_version': '10.0'}}) - aioclient_mock.post( - "http://127.0.0.1/homeassistant/options", json={'result': 'ok'}) - with patch.dict(os.environ, MOCK_ENVIRON): result = yield from async_setup_component(hass, 'hassio', { 'http': { @@ -90,19 +85,12 @@ def test_setup_api_push_api_data_server_host(hass, aioclient_mock): assert not aioclient_mock.mock_calls[1][2]['watchdog'] -@asyncio.coroutine -def test_setup_api_push_api_data_default(hass, aioclient_mock): +async def test_setup_api_push_api_data_default(hass, aioclient_mock, + hass_storage): """Test setup with API push default data.""" - aioclient_mock.get( - "http://127.0.0.1/supervisor/ping", json={'result': 'ok'}) - aioclient_mock.get( - "http://127.0.0.1/homeassistant/info", json={ - 'result': 'ok', 'data': {'last_version': '10.0'}}) - aioclient_mock.post( - "http://127.0.0.1/homeassistant/options", json={'result': 'ok'}) - - with patch.dict(os.environ, MOCK_ENVIRON): - result = yield from async_setup_component(hass, 'hassio', { + with patch.dict(os.environ, MOCK_ENVIRON), \ + patch('homeassistant.auth.AuthManager.active', return_value=True): + result = await async_setup_component(hass, 'hassio', { 'http': {}, 'hassio': {} }) @@ -112,19 +100,61 @@ def test_setup_api_push_api_data_default(hass, aioclient_mock): assert not aioclient_mock.mock_calls[1][2]['ssl'] assert aioclient_mock.mock_calls[1][2]['password'] is None assert aioclient_mock.mock_calls[1][2]['port'] == 8123 + refresh_token = aioclient_mock.mock_calls[1][2]['refresh_token'] + hassio_user = await hass.auth.async_get_user( + hass_storage[STORAGE_KEY]['data']['hassio_user'] + ) + assert hassio_user is not None + assert hassio_user.system_generated + assert refresh_token in hassio_user.refresh_tokens + + +async def test_setup_api_push_api_data_no_auth(hass, aioclient_mock, + hass_storage): + """Test setup with API push default data.""" + with patch.dict(os.environ, MOCK_ENVIRON): + result = await async_setup_component(hass, 'hassio', { + 'http': {}, + 'hassio': {} + }) + assert result + + assert aioclient_mock.call_count == 3 + assert not aioclient_mock.mock_calls[1][2]['ssl'] + assert aioclient_mock.mock_calls[1][2]['password'] is None + assert aioclient_mock.mock_calls[1][2]['port'] == 8123 + assert aioclient_mock.mock_calls[1][2]['refresh_token'] is None + + +async def test_setup_api_existing_hassio_user(hass, aioclient_mock, + hass_storage): + """Test setup with API push default data.""" + user = await hass.auth.async_create_system_user('Hass.io test') + token = await hass.auth.async_create_refresh_token(user) + hass_storage[STORAGE_KEY] = { + 'version': 1, + 'data': { + 'hassio_user': user.id + } + } + with patch.dict(os.environ, MOCK_ENVIRON), \ + patch('homeassistant.auth.AuthManager.active', return_value=True): + result = await async_setup_component(hass, 'hassio', { + 'http': {}, + 'hassio': {} + }) + assert result + + assert aioclient_mock.call_count == 3 + assert not aioclient_mock.mock_calls[1][2]['ssl'] + assert aioclient_mock.mock_calls[1][2]['password'] is None + assert aioclient_mock.mock_calls[1][2]['port'] == 8123 + assert aioclient_mock.mock_calls[1][2]['refresh_token'] == token.token @asyncio.coroutine def test_setup_core_push_timezone(hass, aioclient_mock): """Test setup with API push default data.""" - aioclient_mock.get( - "http://127.0.0.1/supervisor/ping", json={'result': 'ok'}) - aioclient_mock.get( - "http://127.0.0.1/homeassistant/info", json={ - 'result': 'ok', 'data': {'last_version': '10.0'}}) - aioclient_mock.post( - "http://127.0.0.1/supervisor/options", json={'result': 'ok'}) - with patch.dict(os.environ, MOCK_ENVIRON): result = yield from async_setup_component(hass, 'hassio', { 'hassio': {}, @@ -134,21 +164,13 @@ def test_setup_core_push_timezone(hass, aioclient_mock): }) assert result - assert aioclient_mock.call_count == 3 - assert aioclient_mock.mock_calls[1][2]['timezone'] == "testzone" + assert aioclient_mock.call_count == 4 + assert aioclient_mock.mock_calls[2][2]['timezone'] == "testzone" @asyncio.coroutine def test_setup_hassio_no_additional_data(hass, aioclient_mock): """Test setup with API push default data.""" - aioclient_mock.get( - "http://127.0.0.1/supervisor/ping", json={'result': 'ok'}) - aioclient_mock.get( - "http://127.0.0.1/homeassistant/info", json={ - 'result': 'ok', 'data': {'last_version': '10.0'}}) - aioclient_mock.get( - "http://127.0.0.1/homeassistant/info", json={'result': 'ok'}) - with patch.dict(os.environ, MOCK_ENVIRON), \ patch.dict(os.environ, {'HASSIO_TOKEN': "123456"}): result = yield from async_setup_component(hass, 'hassio', { @@ -156,7 +178,7 @@ def test_setup_hassio_no_additional_data(hass, aioclient_mock): }) assert result - assert aioclient_mock.call_count == 2 + assert aioclient_mock.call_count == 3 assert aioclient_mock.mock_calls[-1][3]['X-HASSIO-KEY'] == "123456" @@ -234,14 +256,14 @@ def test_service_calls(hassio_env, hass, aioclient_mock): 'hassio', 'addon_stdin', {'addon': 'test', 'input': 'test'}) yield from hass.async_block_till_done() - assert aioclient_mock.call_count == 4 + assert aioclient_mock.call_count == 5 assert aioclient_mock.mock_calls[-1][2] == 'test' yield from hass.services.async_call('hassio', 'host_shutdown', {}) yield from hass.services.async_call('hassio', 'host_reboot', {}) yield from hass.async_block_till_done() - assert aioclient_mock.call_count == 6 + assert aioclient_mock.call_count == 7 yield from hass.services.async_call('hassio', 'snapshot_full', {}) yield from hass.services.async_call('hassio', 'snapshot_partial', { @@ -251,7 +273,7 @@ def test_service_calls(hassio_env, hass, aioclient_mock): }) yield from hass.async_block_till_done() - assert aioclient_mock.call_count == 8 + assert aioclient_mock.call_count == 9 assert aioclient_mock.mock_calls[-1][2] == { 'addons': ['test'], 'folders': ['ssl'], 'password': "123456"} @@ -267,7 +289,7 @@ def test_service_calls(hassio_env, hass, aioclient_mock): }) yield from hass.async_block_till_done() - assert aioclient_mock.call_count == 10 + assert aioclient_mock.call_count == 11 assert aioclient_mock.mock_calls[-1][2] == { 'addons': ['test'], 'folders': ['ssl'], 'homeassistant': False, 'password': "123456" @@ -289,17 +311,17 @@ def test_service_calls_core(hassio_env, hass, aioclient_mock): yield from hass.services.async_call('homeassistant', 'stop') yield from hass.async_block_till_done() - assert aioclient_mock.call_count == 1 + assert aioclient_mock.call_count == 2 yield from hass.services.async_call('homeassistant', 'check_config') yield from hass.async_block_till_done() - assert aioclient_mock.call_count == 2 + assert aioclient_mock.call_count == 3 yield from hass.services.async_call('homeassistant', 'restart') yield from hass.async_block_till_done() - assert aioclient_mock.call_count == 4 + assert aioclient_mock.call_count == 5 @asyncio.coroutine From f3dfc433c2a61f8430564584936ef23f53a9c1ed Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Mon, 23 Jul 2018 14:36:36 +0200 Subject: [PATCH 062/113] Fix aiohttp connection reset errors (#15577) * Fix aiohttp connection reset errors * Update aiohttp_client.py * Update aiohttp_client.py * Update __init__.py * Update mjpeg.py * Update mjpeg.py * Update ffmpeg.py * Update ffmpeg.py * Update ffmpeg.py * Update proxy.py * Update __init__.py * Update aiohttp_client.py * Update aiohttp_client.py * Update proxy.py * Update proxy.py * Fix await inside coroutine * Fix async syntax * Lint --- homeassistant/components/camera/__init__.py | 50 ++++++++------------- homeassistant/components/camera/ffmpeg.py | 24 +++++----- homeassistant/components/camera/mjpeg.py | 7 ++- homeassistant/components/camera/proxy.py | 15 +++---- homeassistant/helpers/aiohttp_client.py | 19 +++----- 5 files changed, 46 insertions(+), 69 deletions(-) diff --git a/homeassistant/components/camera/__init__.py b/homeassistant/components/camera/__init__.py index e84377ee419b..3c252adb7ea3 100644 --- a/homeassistant/components/camera/__init__.py +++ b/homeassistant/components/camera/__init__.py @@ -301,32 +301,23 @@ class Camera(Entity): last_image = None - try: - while True: - img_bytes = await self.async_camera_image() - if not img_bytes: - break + while True: + img_bytes = await self.async_camera_image() + if not img_bytes: + break - if img_bytes and img_bytes != last_image: + if img_bytes and img_bytes != last_image: + await write_to_mjpeg_stream(img_bytes) + + # Chrome seems to always ignore first picture, + # print it twice. + if last_image is None: await write_to_mjpeg_stream(img_bytes) + last_image = img_bytes - # Chrome seems to always ignore first picture, - # print it twice. - if last_image is None: - await write_to_mjpeg_stream(img_bytes) + await asyncio.sleep(interval) - last_image = img_bytes - - await asyncio.sleep(interval) - - except asyncio.CancelledError: - _LOGGER.debug("Stream closed by frontend.") - response = None - raise - - finally: - if response is not None: - await response.write_eof() + return response async def handle_async_mjpeg_stream(self, request): """Serve an HTTP MJPEG stream from the camera. @@ -409,10 +400,9 @@ class CameraView(HomeAssistantView): request.query.get('token') in camera.access_tokens) if not authenticated: - return web.Response(status=401) + raise web.HTTPUnauthorized() - response = await self.handle(request, camera) - return response + return await self.handle(request, camera) async def handle(self, request, camera): """Handle the camera request.""" @@ -435,7 +425,7 @@ class CameraImageView(CameraView): return web.Response(body=image, content_type=camera.content_type) - return web.Response(status=500) + raise web.HTTPInternalServerError() class CameraMjpegStream(CameraView): @@ -448,8 +438,7 @@ class CameraMjpegStream(CameraView): """Serve camera stream, possibly with interval.""" interval = request.query.get('interval') if interval is None: - await camera.handle_async_mjpeg_stream(request) - return + return await camera.handle_async_mjpeg_stream(request) try: # Compose camera stream from stills @@ -457,10 +446,9 @@ class CameraMjpegStream(CameraView): if interval < MIN_STREAM_INTERVAL: raise ValueError("Stream interval must be be > {}" .format(MIN_STREAM_INTERVAL)) - await camera.handle_async_still_stream(request, interval) - return + return await camera.handle_async_still_stream(request, interval) except ValueError: - return web.Response(status=400) + raise web.HTTPBadRequest() @callback diff --git a/homeassistant/components/camera/ffmpeg.py b/homeassistant/components/camera/ffmpeg.py index 1bbd263e5855..3da0f19fbf03 100644 --- a/homeassistant/components/camera/ffmpeg.py +++ b/homeassistant/components/camera/ffmpeg.py @@ -29,8 +29,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ }) -@asyncio.coroutine -def async_setup_platform(hass, config, async_add_devices, discovery_info=None): +async def async_setup_platform(hass, config, async_add_devices, + discovery_info=None): """Set up a FFmpeg camera.""" if not hass.data[DATA_FFMPEG].async_run_test(config.get(CONF_INPUT)): return @@ -49,30 +49,30 @@ class FFmpegCamera(Camera): self._input = config.get(CONF_INPUT) self._extra_arguments = config.get(CONF_EXTRA_ARGUMENTS) - @asyncio.coroutine - def async_camera_image(self): + async def async_camera_image(self): """Return a still image response from the camera.""" from haffmpeg import ImageFrame, IMAGE_JPEG ffmpeg = ImageFrame(self._manager.binary, loop=self.hass.loop) - image = yield from asyncio.shield(ffmpeg.get_image( + image = await asyncio.shield(ffmpeg.get_image( self._input, output_format=IMAGE_JPEG, extra_cmd=self._extra_arguments), loop=self.hass.loop) return image - @asyncio.coroutine - def handle_async_mjpeg_stream(self, request): + async def handle_async_mjpeg_stream(self, request): """Generate an HTTP MJPEG stream from the camera.""" from haffmpeg import CameraMjpeg stream = CameraMjpeg(self._manager.binary, loop=self.hass.loop) - yield from stream.open_camera( + await stream.open_camera( self._input, extra_cmd=self._extra_arguments) - yield from async_aiohttp_proxy_stream( - self.hass, request, stream, - 'multipart/x-mixed-replace;boundary=ffserver') - yield from stream.close() + try: + return await async_aiohttp_proxy_stream( + self.hass, request, stream, + 'multipart/x-mixed-replace;boundary=ffserver') + finally: + await stream.close() @property def name(self): diff --git a/homeassistant/components/camera/mjpeg.py b/homeassistant/components/camera/mjpeg.py index a5ed0cdc02c4..757a1b5fc094 100644 --- a/homeassistant/components/camera/mjpeg.py +++ b/homeassistant/components/camera/mjpeg.py @@ -123,19 +123,18 @@ class MjpegCamera(Camera): with closing(req) as response: return extract_image_from_mjpeg(response.iter_content(102400)) - @asyncio.coroutine - def handle_async_mjpeg_stream(self, request): + async def handle_async_mjpeg_stream(self, request): """Generate an HTTP MJPEG stream from the camera.""" # aiohttp don't support DigestAuth -> Fallback if self._authentication == HTTP_DIGEST_AUTHENTICATION: - yield from super().handle_async_mjpeg_stream(request) + await super().handle_async_mjpeg_stream(request) return # connect to stream websession = async_get_clientsession(self.hass) stream_coro = websession.get(self._mjpeg_url, auth=self._auth) - yield from async_aiohttp_proxy_web(self.hass, request, stream_coro) + return await async_aiohttp_proxy_web(self.hass, request, stream_coro) @property def name(self): diff --git a/homeassistant/components/camera/proxy.py b/homeassistant/components/camera/proxy.py index 6fe891b6b241..d88d52c4c8a0 100644 --- a/homeassistant/components/camera/proxy.py +++ b/homeassistant/components/camera/proxy.py @@ -191,8 +191,8 @@ class ProxyCamera(Camera): stream_coro = websession.get(url, headers=self._headers) if not self._stream_opts: - await async_aiohttp_proxy_web(self.hass, request, stream_coro) - return + return await async_aiohttp_proxy_web( + self.hass, request, stream_coro) response = aiohttp.web.StreamResponse() response.content_type = ('multipart/x-mixed-replace; ' @@ -229,15 +229,10 @@ class ProxyCamera(Camera): _resize_image, image, self._stream_opts) await write(image) data = data[jpg_end + 2:] - except asyncio.CancelledError: - _LOGGER.debug("Stream closed by frontend.") - req.close() - response = None - raise - finally: - if response is not None: - await response.write_eof() + req.close() + + return response @property def name(self): diff --git a/homeassistant/helpers/aiohttp_client.py b/homeassistant/helpers/aiohttp_client.py index 71f3374f0c03..53b246c700d3 100644 --- a/homeassistant/helpers/aiohttp_client.py +++ b/homeassistant/helpers/aiohttp_client.py @@ -66,14 +66,13 @@ def async_create_clientsession(hass, verify_ssl=True, auto_cleanup=True, return clientsession -@asyncio.coroutine @bind_hass -def async_aiohttp_proxy_web(hass, request, web_coro, buffer_size=102400, - timeout=10): +async def async_aiohttp_proxy_web(hass, request, web_coro, + buffer_size=102400, timeout=10): """Stream websession request to aiohttp web response.""" try: with async_timeout.timeout(timeout, loop=hass.loop): - req = yield from web_coro + req = await web_coro except asyncio.CancelledError: # The user cancelled the request @@ -88,7 +87,7 @@ def async_aiohttp_proxy_web(hass, request, web_coro, buffer_size=102400, raise HTTPBadGateway() from err try: - yield from async_aiohttp_proxy_stream( + return await async_aiohttp_proxy_stream( hass, request, req.content, @@ -112,19 +111,15 @@ async def async_aiohttp_proxy_stream(hass, request, stream, content_type, data = await stream.read(buffer_size) if not data: - await response.write_eof() break - await response.write(data) except (asyncio.TimeoutError, aiohttp.ClientError): - # Something went wrong fetching data, close connection gracefully - await response.write_eof() - - except asyncio.CancelledError: - # The user closed the connection + # Something went wrong fetching data, closed connection pass + return response + @callback def _async_register_clientsession_shutdown(hass, clientsession): From 32045011748c9738f06871da5992c32c85863003 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 23 Jul 2018 15:08:03 +0200 Subject: [PATCH 063/113] Cast/Sonos: create config entry if manually configured (#15630) * Cast/Sonos: create config entry if manually configured * Add test for helper --- homeassistant/components/cast/__init__.py | 10 ++++++- homeassistant/components/sonos/__init__.py | 10 ++++++- homeassistant/helpers/config_entry_flow.py | 12 +++++++++ tests/components/cast/test_init.py | 31 ++++++++++++++++++++++ tests/components/sonos/test_init.py | 27 +++++++++++++++++++ tests/helpers/test_config_entry_flow.py | 21 +++++++++++++++ 6 files changed, 109 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/cast/__init__.py b/homeassistant/components/cast/__init__.py index 966107bcb36a..aadf0103c5a6 100644 --- a/homeassistant/components/cast/__init__.py +++ b/homeassistant/components/cast/__init__.py @@ -1,4 +1,5 @@ """Component to embed Google Cast.""" +from homeassistant import data_entry_flow from homeassistant.helpers import config_entry_flow @@ -8,7 +9,14 @@ REQUIREMENTS = ['pychromecast==2.1.0'] async def async_setup(hass, config): """Set up the Cast component.""" - hass.data[DOMAIN] = config.get(DOMAIN, {}) + conf = config.get(DOMAIN) + + hass.data[DOMAIN] = conf or {} + + if conf is not None: + hass.async_create_task(hass.config_entries.flow.async_init( + DOMAIN, source=data_entry_flow.SOURCE_IMPORT)) + return True diff --git a/homeassistant/components/sonos/__init__.py b/homeassistant/components/sonos/__init__.py index c67a3dffaaf2..4c5592c02c22 100644 --- a/homeassistant/components/sonos/__init__.py +++ b/homeassistant/components/sonos/__init__.py @@ -1,4 +1,5 @@ """Component to embed Sonos.""" +from homeassistant import data_entry_flow from homeassistant.helpers import config_entry_flow @@ -8,7 +9,14 @@ REQUIREMENTS = ['SoCo==0.14'] async def async_setup(hass, config): """Set up the Sonos component.""" - hass.data[DOMAIN] = config.get(DOMAIN, {}) + conf = config.get(DOMAIN) + + hass.data[DOMAIN] = conf or {} + + if conf is not None: + hass.async_create_task(hass.config_entries.flow.async_init( + DOMAIN, source=data_entry_flow.SOURCE_IMPORT)) + return True diff --git a/homeassistant/helpers/config_entry_flow.py b/homeassistant/helpers/config_entry_flow.py index 2a4ec2966df9..6f51d9aca2c4 100644 --- a/homeassistant/helpers/config_entry_flow.py +++ b/homeassistant/helpers/config_entry_flow.py @@ -72,6 +72,18 @@ class DiscoveryFlowHandler(data_entry_flow.FlowHandler): return await self.async_step_confirm() + async def async_step_import(self, _): + """Handle a flow initialized by import.""" + if self._async_in_progress() or self._async_current_entries(): + return self.async_abort( + reason='single_instance_allowed' + ) + + return self.async_create_entry( + title=self._title, + data={}, + ) + @callback def _async_current_entries(self): """Return current entries.""" diff --git a/tests/components/cast/test_init.py b/tests/components/cast/test_init.py index 260856c6742e..3ed9ea7b88e1 100644 --- a/tests/components/cast/test_init.py +++ b/tests/components/cast/test_init.py @@ -2,6 +2,7 @@ from unittest.mock import patch from homeassistant import data_entry_flow +from homeassistant.setup import async_setup_component from homeassistant.components import cast from tests.common import MockDependency, mock_coro @@ -20,3 +21,33 @@ async def test_creating_entry_sets_up_media_player(hass): await hass.async_block_till_done() assert len(mock_setup.mock_calls) == 1 + + +async def test_configuring_cast_creates_entry(hass): + """Test that specifying config will create an entry.""" + with patch('homeassistant.components.cast.async_setup_entry', + return_value=mock_coro(True)) as mock_setup, \ + MockDependency('pychromecast', 'discovery'), \ + patch('pychromecast.discovery.discover_chromecasts', + return_value=True): + await async_setup_component(hass, cast.DOMAIN, { + 'cast': { + 'some_config': 'to_trigger_import' + } + }) + await hass.async_block_till_done() + + assert len(mock_setup.mock_calls) == 1 + + +async def test_not_configuring_cast_not_creates_entry(hass): + """Test that no config will not create an entry.""" + with patch('homeassistant.components.cast.async_setup_entry', + return_value=mock_coro(True)) as mock_setup, \ + MockDependency('pychromecast', 'discovery'), \ + patch('pychromecast.discovery.discover_chromecasts', + return_value=True): + await async_setup_component(hass, cast.DOMAIN, {}) + await hass.async_block_till_done() + + assert len(mock_setup.mock_calls) == 0 diff --git a/tests/components/sonos/test_init.py b/tests/components/sonos/test_init.py index 2cbc2360fd44..9fe22fc7e79e 100644 --- a/tests/components/sonos/test_init.py +++ b/tests/components/sonos/test_init.py @@ -2,6 +2,7 @@ from unittest.mock import patch from homeassistant import data_entry_flow +from homeassistant.setup import async_setup_component from homeassistant.components import sonos from tests.common import mock_coro @@ -18,3 +19,29 @@ async def test_creating_entry_sets_up_media_player(hass): await hass.async_block_till_done() assert len(mock_setup.mock_calls) == 1 + + +async def test_configuring_sonos_creates_entry(hass): + """Test that specifying config will create an entry.""" + with patch('homeassistant.components.sonos.async_setup_entry', + return_value=mock_coro(True)) as mock_setup, \ + patch('soco.discover', return_value=True): + await async_setup_component(hass, sonos.DOMAIN, { + 'sonos': { + 'some_config': 'to_trigger_import' + } + }) + await hass.async_block_till_done() + + assert len(mock_setup.mock_calls) == 1 + + +async def test_not_configuring_sonos_not_creates_entry(hass): + """Test that no config will not create an entry.""" + with patch('homeassistant.components.sonos.async_setup_entry', + return_value=mock_coro(True)) as mock_setup, \ + patch('soco.discover', return_value=True): + await async_setup_component(hass, sonos.DOMAIN, {}) + await hass.async_block_till_done() + + assert len(mock_setup.mock_calls) == 0 diff --git a/tests/helpers/test_config_entry_flow.py b/tests/helpers/test_config_entry_flow.py index d3f13ac43021..19185e165bcc 100644 --- a/tests/helpers/test_config_entry_flow.py +++ b/tests/helpers/test_config_entry_flow.py @@ -114,3 +114,24 @@ async def test_user_init_trumps_discovery(hass, flow_conf): # Discovery flow has been aborted assert len(hass.config_entries.flow.async_progress()) == 0 + + +async def test_import_no_confirmation(hass, flow_conf): + """Test import requires no confirmation to setup.""" + flow = config_entries.HANDLERS['test']() + flow.hass = hass + flow_conf['discovered'] = True + + result = await flow.async_step_import(None) + assert result['type'] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + + +async def test_import_single_instance(hass, flow_conf): + """Test import doesn't create second instance.""" + flow = config_entries.HANDLERS['test']() + flow.hass = hass + flow_conf['discovered'] = True + MockConfigEntry(domain='test').add_to_hass(hass) + + result = await flow.async_step_import(None) + assert result['type'] == data_entry_flow.RESULT_TYPE_ABORT From 1b94fe3613bbc4641998c0be947d05e4fb9f903f Mon Sep 17 00:00:00 2001 From: John Arild Berentsen Date: Mon, 23 Jul 2018 15:31:12 +0200 Subject: [PATCH 064/113] Add ability to set Zwave protection commandclass (#15390) * Add API for protection commandclass * Adjusting * tests * Spelling * Missed flake8 * Period * spelling * Review changes * removing additional .keys() * period * Move i/o out into executor pool * Move i/o out into executor pool * Forgot get method * Do it right... I feel stupid * Long lines * Merging --- homeassistant/components/config/zwave.py | 57 +++++++ tests/components/config/test_zwave.py | 189 +++++++++++++++++++++++ 2 files changed, 246 insertions(+) diff --git a/homeassistant/components/config/zwave.py b/homeassistant/components/config/zwave.py index c839ab7bc6ec..84927712741c 100644 --- a/homeassistant/components/config/zwave.py +++ b/homeassistant/components/config/zwave.py @@ -29,6 +29,7 @@ def async_setup(hass): hass.http.register_view(ZWaveUserCodeView) hass.http.register_view(ZWaveLogView) hass.http.register_view(ZWaveConfigWriteView) + hass.http.register_view(ZWaveProtectionView) return True @@ -196,3 +197,59 @@ class ZWaveUserCodeView(HomeAssistantView): 'label': value.label, 'length': len(value.data)} return self.json(usercodes) + + +class ZWaveProtectionView(HomeAssistantView): + """View for the protection commandclass of a node.""" + + url = r"/api/zwave/protection/{node_id:\d+}" + name = "api:zwave:protection" + + async def get(self, request, node_id): + """Retrieve the protection commandclass options of node.""" + nodeid = int(node_id) + hass = request.app['hass'] + network = hass.data.get(const.DATA_NETWORK) + + def _fetch_protection(): + """Helper to get protection data.""" + node = network.nodes.get(nodeid) + if node is None: + return self.json_message('Node not found', HTTP_NOT_FOUND) + protection_options = {} + if not node.has_command_class(const.COMMAND_CLASS_PROTECTION): + return self.json(protection_options) + protections = node.get_protections() + protection_options = { + 'value_id': '{0:d}'.format(list(protections)[0]), + 'selected': node.get_protection_item(list(protections)[0]), + 'options': node.get_protection_items(list(protections)[0])} + return self.json(protection_options) + + return await hass.async_add_executor_job(_fetch_protection) + + async def post(self, request, node_id): + """Change the selected option in protection commandclass.""" + nodeid = int(node_id) + hass = request.app['hass'] + network = hass.data.get(const.DATA_NETWORK) + protection_data = await request.json() + + def _set_protection(): + """Helper to get protection data.""" + node = network.nodes.get(nodeid) + selection = protection_data["selection"] + value_id = int(protection_data[const.ATTR_VALUE_ID]) + if node is None: + return self.json_message('Node not found', HTTP_NOT_FOUND) + if not node.has_command_class(const.COMMAND_CLASS_PROTECTION): + return self.json_message( + 'No protection commandclass on this node', HTTP_NOT_FOUND) + state = node.set_protection(value_id, selection) + if not state: + return self.json_message( + 'Protection setting did not complete', 202) + return self.json_message( + 'Protection setting succsessfully set', HTTP_OK) + + return await hass.async_add_executor_job(_set_protection) diff --git a/tests/components/config/test_zwave.py b/tests/components/config/test_zwave.py index 672bafeaf282..8aae5c0a28b9 100644 --- a/tests/components/config/test_zwave.py +++ b/tests/components/config/test_zwave.py @@ -367,3 +367,192 @@ def test_save_config(hass, client): result = yield from resp.json() assert network.write_config.called assert result == {'message': 'Z-Wave configuration saved to file.'} + + +async def test_get_protection_values(hass, client): + """Test getting protection values on node.""" + network = hass.data[DATA_NETWORK] = MagicMock() + node = MockNode(node_id=18, + command_classes=[const.COMMAND_CLASS_PROTECTION]) + value = MockValue( + value_id=123456, + index=0, + instance=1, + command_class=const.COMMAND_CLASS_PROTECTION) + value.label = 'Protection Test' + value.data_items = ['Unprotected', 'Protection by Sequence', + 'No Operation Possible'] + value.data = 'Unprotected' + network.nodes = {18: node} + node.value = value + + node.get_protection_item.return_value = "Unprotected" + node.get_protection_items.return_value = value.data_items + node.get_protections.return_value = {value.value_id: 'Object'} + + resp = await client.get('/api/zwave/protection/18') + + assert resp.status == 200 + result = await resp.json() + assert node.get_protections.called + assert node.get_protection_item.called + assert node.get_protection_items.called + assert result == { + 'value_id': '123456', + 'selected': 'Unprotected', + 'options': ['Unprotected', 'Protection by Sequence', + 'No Operation Possible'] + } + + +async def test_get_protection_values_nonexisting_node(hass, client): + """Test getting protection values on node with wrong nodeid.""" + network = hass.data[DATA_NETWORK] = MagicMock() + node = MockNode(node_id=18, + command_classes=[const.COMMAND_CLASS_PROTECTION]) + value = MockValue( + value_id=123456, + index=0, + instance=1, + command_class=const.COMMAND_CLASS_PROTECTION) + value.label = 'Protection Test' + value.data_items = ['Unprotected', 'Protection by Sequence', + 'No Operation Possible'] + value.data = 'Unprotected' + network.nodes = {17: node} + node.value = value + + resp = await client.get('/api/zwave/protection/18') + + assert resp.status == 404 + result = await resp.json() + assert not node.get_protections.called + assert not node.get_protection_item.called + assert not node.get_protection_items.called + assert result == {'message': 'Node not found'} + + +async def test_get_protection_values_without_protectionclass(hass, client): + """Test getting protection values on node without protectionclass.""" + network = hass.data[DATA_NETWORK] = MagicMock() + node = MockNode(node_id=18) + value = MockValue( + value_id=123456, + index=0, + instance=1) + network.nodes = {18: node} + node.value = value + + resp = await client.get('/api/zwave/protection/18') + + assert resp.status == 200 + result = await resp.json() + assert not node.get_protections.called + assert not node.get_protection_item.called + assert not node.get_protection_items.called + assert result == {} + + +async def test_set_protection_value(hass, client): + """Test setting protection value on node.""" + network = hass.data[DATA_NETWORK] = MagicMock() + node = MockNode(node_id=18, + command_classes=[const.COMMAND_CLASS_PROTECTION]) + value = MockValue( + value_id=123456, + index=0, + instance=1, + command_class=const.COMMAND_CLASS_PROTECTION) + value.label = 'Protection Test' + value.data_items = ['Unprotected', 'Protection by Sequence', + 'No Operation Possible'] + value.data = 'Unprotected' + network.nodes = {18: node} + node.value = value + + resp = await client.post( + '/api/zwave/protection/18', data=json.dumps({ + 'value_id': '123456', 'selection': 'Protection by Sequence'})) + + assert resp.status == 200 + result = await resp.json() + assert node.set_protection.called + assert result == {'message': 'Protection setting succsessfully set'} + + +async def test_set_protection_value_failed(hass, client): + """Test setting protection value failed on node.""" + network = hass.data[DATA_NETWORK] = MagicMock() + node = MockNode(node_id=18, + command_classes=[const.COMMAND_CLASS_PROTECTION]) + value = MockValue( + value_id=123456, + index=0, + instance=1, + command_class=const.COMMAND_CLASS_PROTECTION) + value.label = 'Protection Test' + value.data_items = ['Unprotected', 'Protection by Sequence', + 'No Operation Possible'] + value.data = 'Unprotected' + network.nodes = {18: node} + node.value = value + node.set_protection.return_value = False + + resp = await client.post( + '/api/zwave/protection/18', data=json.dumps({ + 'value_id': '123456', 'selection': 'Protecton by Seuence'})) + + assert resp.status == 202 + result = await resp.json() + assert node.set_protection.called + assert result == {'message': 'Protection setting did not complete'} + + +async def test_set_protection_value_nonexisting_node(hass, client): + """Test setting protection value on nonexisting node.""" + network = hass.data[DATA_NETWORK] = MagicMock() + node = MockNode(node_id=17, + command_classes=[const.COMMAND_CLASS_PROTECTION]) + value = MockValue( + value_id=123456, + index=0, + instance=1, + command_class=const.COMMAND_CLASS_PROTECTION) + value.label = 'Protection Test' + value.data_items = ['Unprotected', 'Protection by Sequence', + 'No Operation Possible'] + value.data = 'Unprotected' + network.nodes = {17: node} + node.value = value + node.set_protection.return_value = False + + resp = await client.post( + '/api/zwave/protection/18', data=json.dumps({ + 'value_id': '123456', 'selection': 'Protecton by Seuence'})) + + assert resp.status == 404 + result = await resp.json() + assert not node.set_protection.called + assert result == {'message': 'Node not found'} + + +async def test_set_protection_value_missing_class(hass, client): + """Test setting protection value on node without protectionclass.""" + network = hass.data[DATA_NETWORK] = MagicMock() + node = MockNode(node_id=17) + value = MockValue( + value_id=123456, + index=0, + instance=1) + network.nodes = {17: node} + node.value = value + node.set_protection.return_value = False + + resp = await client.post( + '/api/zwave/protection/17', data=json.dumps({ + 'value_id': '123456', 'selection': 'Protecton by Seuence'})) + + assert resp.status == 404 + result = await resp.json() + assert not node.set_protection.called + assert result == {'message': 'No protection commandclass on this node'} From bc481fa366428838ccddd91e5380d862da94d92f Mon Sep 17 00:00:00 2001 From: Daniel Shokouhi Date: Mon, 23 Jul 2018 15:46:12 -0700 Subject: [PATCH 065/113] Update Neato library to allow for dynamic endpoints (#15639) --- homeassistant/components/neato.py | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/neato.py b/homeassistant/components/neato.py index 6eabfc713bc1..25da38e7f755 100644 --- a/homeassistant/components/neato.py +++ b/homeassistant/components/neato.py @@ -17,7 +17,7 @@ from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) -REQUIREMENTS = ['pybotvac==0.0.8'] +REQUIREMENTS = ['pybotvac==0.0.9'] DOMAIN = 'neato' NEATO_ROBOTS = 'neato_robots' diff --git a/requirements_all.txt b/requirements_all.txt index 0bb763a3faed..a6ad298dc9b6 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -753,7 +753,7 @@ pyblackbird==0.5 # pybluez==0.22 # homeassistant.components.neato -pybotvac==0.0.8 +pybotvac==0.0.9 # homeassistant.components.cloudflare pycfdns==0.0.1 From 45c35ceb2b886df1f65a6d22e67314bece9ad2da Mon Sep 17 00:00:00 2001 From: Cheong Yip Date: Tue, 24 Jul 2018 12:19:01 +1000 Subject: [PATCH 066/113] Fix typo `asayn_init` instead of `async_init` (#15645) --- homeassistant/components/device_tracker/tile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/device_tracker/tile.py b/homeassistant/components/device_tracker/tile.py index 526c1a4b47b6..07f15e7e88ad 100644 --- a/homeassistant/components/device_tracker/tile.py +++ b/homeassistant/components/device_tracker/tile.py @@ -109,7 +109,7 @@ class TileScanner: _LOGGER.debug('Updating Tile data') try: - await self._client.asayn_init() + await self._client.async_init() tiles = await self._client.tiles.all( whitelist=self._types, show_inactive=self._show_inactive) except SessionExpiredError: From d7690c5fdae94be25c940633e2092cf98d8a7b52 Mon Sep 17 00:00:00 2001 From: Jason Hu Date: Tue, 24 Jul 2018 01:09:52 -0700 Subject: [PATCH 067/113] Add ipban for failed login attempt in new login flow (#15551) * Add ipban for failed login attempt in new login flow * Address review comment * Use decorator to clean up code --- homeassistant/components/auth/__init__.py | 10 ++++++++++ homeassistant/components/http/ban.py | 12 +++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/auth/__init__.py b/homeassistant/components/auth/__init__.py index ee4013302093..95a2a2f1fac6 100644 --- a/homeassistant/components/auth/__init__.py +++ b/homeassistant/components/auth/__init__.py @@ -110,6 +110,8 @@ import aiohttp.web import voluptuous as vol from homeassistant import data_entry_flow +from homeassistant.components.http.ban import process_wrong_login, \ + log_invalid_auth from homeassistant.core import callback from homeassistant.helpers.data_entry_flow import ( FlowManagerIndexView, FlowManagerResourceView) @@ -183,6 +185,7 @@ class LoginFlowIndexView(FlowManagerIndexView): vol.Required('handler'): vol.Any(str, list), vol.Required('redirect_uri'): str, })) + @log_invalid_auth async def post(self, request, data): """Create a new login flow.""" if not indieauth.verify_redirect_uri(data['client_id'], @@ -212,6 +215,7 @@ class LoginFlowResourceView(FlowManagerResourceView): @RequestDataValidator(vol.Schema({ 'client_id': str }, extra=vol.ALLOW_EXTRA)) + @log_invalid_auth async def post(self, request, flow_id, data): """Handle progressing a login flow request.""" client_id = data.pop('client_id') @@ -227,6 +231,11 @@ class LoginFlowResourceView(FlowManagerResourceView): return self.json_message('User input malformed', 400) if result['type'] != data_entry_flow.RESULT_TYPE_CREATE_ENTRY: + # @log_invalid_auth does not work here since it returns HTTP 200 + # need manually log failed login attempts + if result['errors'] is not None and \ + result['errors'].get('base') == 'invalid_auth': + await process_wrong_login(request) return self.json(self._prepare_result_json(result)) result.pop('data') @@ -247,6 +256,7 @@ class GrantTokenView(HomeAssistantView): """Initialize the grant token view.""" self._retrieve_credentials = retrieve_credentials + @log_invalid_auth async def post(self, request): """Grant a token.""" hass = request.app['hass'] diff --git a/homeassistant/components/http/ban.py b/homeassistant/components/http/ban.py index e05f951322e9..ab582066a22a 100644 --- a/homeassistant/components/http/ban.py +++ b/homeassistant/components/http/ban.py @@ -1,5 +1,4 @@ """Ban logic for HTTP component.""" - from collections import defaultdict from datetime import datetime from ipaddress import ip_address @@ -71,6 +70,17 @@ async def ban_middleware(request, handler): raise +def log_invalid_auth(func): + """Decorator to handle invalid auth or failed login attempts.""" + async def handle_req(view, request, *args, **kwargs): + """Try to log failed login attempts if response status >= 400.""" + resp = await func(view, request, *args, **kwargs) + if resp.status >= 400: + await process_wrong_login(request) + return resp + return handle_req + + async def process_wrong_login(request): """Process a wrong login attempt. From c1f5ead61df1d4272b8550fe1bfade392e747e44 Mon Sep 17 00:00:00 2001 From: huangyupeng Date: Tue, 24 Jul 2018 16:29:43 +0800 Subject: [PATCH 068/113] Add Tuya cover and scene platform (#15587) * Add Tuya cover platform * Add Tuya cover and scene * fix description * remove scene default method --- homeassistant/components/cover/tuya.py | 60 ++++++++++++++++++++++++++ homeassistant/components/scene/tuya.py | 40 +++++++++++++++++ homeassistant/components/tuya.py | 2 + 3 files changed, 102 insertions(+) create mode 100644 homeassistant/components/cover/tuya.py create mode 100644 homeassistant/components/scene/tuya.py diff --git a/homeassistant/components/cover/tuya.py b/homeassistant/components/cover/tuya.py new file mode 100644 index 000000000000..7b5fefee58aa --- /dev/null +++ b/homeassistant/components/cover/tuya.py @@ -0,0 +1,60 @@ +""" +Support for Tuya cover. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/cover.tuya/ +""" +from homeassistant.components.cover import ( + CoverDevice, ENTITY_ID_FORMAT, SUPPORT_OPEN, SUPPORT_CLOSE, SUPPORT_STOP) +from homeassistant.components.tuya import DATA_TUYA, TuyaDevice + +DEPENDENCIES = ['tuya'] + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Set up Tuya cover devices.""" + if discovery_info is None: + return + tuya = hass.data[DATA_TUYA] + dev_ids = discovery_info.get('dev_ids') + devices = [] + for dev_id in dev_ids: + device = tuya.get_device_by_id(dev_id) + if device is None: + continue + devices.append(TuyaCover(device)) + add_devices(devices) + + +class TuyaCover(TuyaDevice, CoverDevice): + """Tuya cover devices.""" + + def __init__(self, tuya): + """Init tuya cover device.""" + super().__init__(tuya) + self.entity_id = ENTITY_ID_FORMAT.format(tuya.object_id()) + + @property + def supported_features(self): + """Flag supported features.""" + supported_features = SUPPORT_OPEN | SUPPORT_CLOSE + if self.tuya.support_stop(): + supported_features |= SUPPORT_STOP + return supported_features + + @property + def is_closed(self): + """Return if the cover is closed or not.""" + return None + + def open_cover(self, **kwargs): + """Open the cover.""" + self.tuya.open_cover() + + def close_cover(self, **kwargs): + """Close cover.""" + self.tuya.close_cover() + + def stop_cover(self, **kwargs): + """Stop the cover.""" + self.tuya.stop_cover() diff --git a/homeassistant/components/scene/tuya.py b/homeassistant/components/scene/tuya.py new file mode 100644 index 000000000000..3990a7da2069 --- /dev/null +++ b/homeassistant/components/scene/tuya.py @@ -0,0 +1,40 @@ +""" +Support for the Tuya scene. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/scene.tuya/ +""" +from homeassistant.components.scene import Scene, DOMAIN +from homeassistant.components.tuya import DATA_TUYA, TuyaDevice + +DEPENDENCIES = ['tuya'] + +ENTITY_ID_FORMAT = DOMAIN + '.{}' + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Set up Tuya scenes.""" + if discovery_info is None: + return + tuya = hass.data[DATA_TUYA] + dev_ids = discovery_info.get('dev_ids') + devices = [] + for dev_id in dev_ids: + device = tuya.get_device_by_id(dev_id) + if device is None: + continue + devices.append(TuyaScene(device)) + add_devices(devices) + + +class TuyaScene(TuyaDevice, Scene): + """Tuya Scene.""" + + def __init__(self, tuya): + """Init Tuya scene.""" + super().__init__(tuya) + self.entity_id = ENTITY_ID_FORMAT.format(tuya.object_id()) + + def activate(self): + """Activate the scene.""" + self.tuya.activate() diff --git a/homeassistant/components/tuya.py b/homeassistant/components/tuya.py index f55fe7a03b39..490c11baad77 100644 --- a/homeassistant/components/tuya.py +++ b/homeassistant/components/tuya.py @@ -34,8 +34,10 @@ SERVICE_PULL_DEVICES = 'pull_devices' TUYA_TYPE_TO_HA = { 'climate': 'climate', + 'cover': 'cover', 'fan': 'fan', 'light': 'light', + 'scene': 'scene', 'switch': 'switch', } From fbeaa576043847121372146b3e86a22392f139bd Mon Sep 17 00:00:00 2001 From: Jan Collijs Date: Tue, 24 Jul 2018 10:41:24 +0200 Subject: [PATCH 069/113] Update smappy library version (#15636) Adding latest smappy lib version Updated smappy library version --- homeassistant/components/smappee.py | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/smappee.py b/homeassistant/components/smappee.py index 2ee0080a06d6..7904f0a6cce4 100644 --- a/homeassistant/components/smappee.py +++ b/homeassistant/components/smappee.py @@ -16,7 +16,7 @@ from homeassistant.util import Throttle from homeassistant.helpers.discovery import load_platform import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['smappy==0.2.15'] +REQUIREMENTS = ['smappy==0.2.16'] _LOGGER = logging.getLogger(__name__) diff --git a/requirements_all.txt b/requirements_all.txt index a6ad298dc9b6..5a614e964718 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1256,7 +1256,7 @@ sleekxmpp==1.3.2 sleepyq==0.6 # homeassistant.components.smappee -smappy==0.2.15 +smappy==0.2.16 # homeassistant.components.raspihats # homeassistant.components.sensor.bh1750 From d9cf8fcfe82105b53aa6b4ee58a0e9633a700ca3 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 24 Jul 2018 14:12:53 +0200 Subject: [PATCH 070/113] Allow changing entity ID (#15637) * Allow changing entity ID * Add support to websocket command * Address comments * Error handling --- .../components/config/entity_registry.py | 28 +++++-- homeassistant/helpers/entity.py | 26 ++++++- homeassistant/helpers/entity_platform.py | 2 +- homeassistant/helpers/entity_registry.py | 40 ++++++++-- .../components/config/test_entity_registry.py | 44 +++++++++-- tests/helpers/test_entity.py | 12 +++ tests/helpers/test_entity_platform.py | 76 ++++++++++++++++++- 7 files changed, 206 insertions(+), 22 deletions(-) diff --git a/homeassistant/components/config/entity_registry.py b/homeassistant/components/config/entity_registry.py index c594bf1f99e1..7c0867e3852c 100644 --- a/homeassistant/components/config/entity_registry.py +++ b/homeassistant/components/config/entity_registry.py @@ -20,6 +20,7 @@ SCHEMA_WS_UPDATE = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ vol.Required('entity_id'): cv.entity_id, # If passed in, we update value. Passing None will remove old value. vol.Optional('name'): vol.Any(str, None), + vol.Optional('new_entity_id'): str, }) @@ -74,13 +75,28 @@ def websocket_update_entity(hass, connection, msg): msg['id'], websocket_api.ERR_NOT_FOUND, 'Entity not found')) return - entry = registry.async_update_entity( - msg['entity_id'], name=msg['name']) - connection.send_message_outside(websocket_api.result_message( - msg['id'], _entry_dict(entry) - )) + changes = {} - hass.async_add_job(update_entity()) + if 'name' in msg: + changes['name'] = msg['name'] + + if 'new_entity_id' in msg: + changes['new_entity_id'] = msg['new_entity_id'] + + try: + if changes: + entry = registry.async_update_entity( + msg['entity_id'], **changes) + except ValueError as err: + connection.send_message_outside(websocket_api.error_message( + msg['id'], 'invalid_info', str(err) + )) + else: + connection.send_message_outside(websocket_api.result_message( + msg['id'], _entry_dict(entry) + )) + + hass.async_create_task(update_entity()) @callback diff --git a/homeassistant/helpers/entity.py b/homeassistant/helpers/entity.py index c7e88b210b3b..f466664fc61f 100644 --- a/homeassistant/helpers/entity.py +++ b/homeassistant/helpers/entity.py @@ -82,6 +82,9 @@ class Entity: # Name in the entity registry registry_name = None + # Hold list for functions to call on remove. + _on_remove = None + @property def should_poll(self) -> bool: """Return True if entity has to be polled for state. @@ -324,8 +327,19 @@ class Entity: if self.parallel_updates: self.parallel_updates.release() + @callback + def async_on_remove(self, func): + """Add a function to call when entity removed.""" + if self._on_remove is None: + self._on_remove = [] + self._on_remove.append(func) + async def async_remove(self): """Remove entity from Home Assistant.""" + if self._on_remove is not None: + while self._on_remove: + self._on_remove.pop()() + if self.platform is not None: await self.platform.async_remove_entity(self.entity_id) else: @@ -335,7 +349,17 @@ class Entity: def async_registry_updated(self, old, new): """Called when the entity registry has been updated.""" self.registry_name = new.name - self.async_schedule_update_ha_state() + + if new.entity_id == self.entity_id: + self.async_schedule_update_ha_state() + return + + async def readd(): + """Remove and add entity again.""" + await self.async_remove() + await self.platform.async_add_entities([self]) + + self.hass.async_create_task(readd()) def __eq__(self, other): """Return the comparison.""" diff --git a/homeassistant/helpers/entity_platform.py b/homeassistant/helpers/entity_platform.py index 0847c116954b..dc1e376f471a 100644 --- a/homeassistant/helpers/entity_platform.py +++ b/homeassistant/helpers/entity_platform.py @@ -283,7 +283,7 @@ class EntityPlatform: entity.entity_id = entry.entity_id entity.registry_name = entry.name - entry.add_update_listener(entity) + entity.async_on_remove(entry.add_update_listener(entity)) # We won't generate an entity ID if the platform has already set one # We will however make sure that platform cannot pick a registered ID diff --git a/homeassistant/helpers/entity_registry.py b/homeassistant/helpers/entity_registry.py index b222d78b5775..2fa64ff8680f 100644 --- a/homeassistant/helpers/entity_registry.py +++ b/homeassistant/helpers/entity_registry.py @@ -19,10 +19,10 @@ import weakref import attr -from ..core import callback, split_entity_id -from ..loader import bind_hass -from ..util import ensure_unique_string, slugify -from ..util.yaml import load_yaml, save_yaml +from homeassistant.core import callback, split_entity_id, valid_entity_id +from homeassistant.loader import bind_hass +from homeassistant.util import ensure_unique_string, slugify +from homeassistant.util.yaml import load_yaml, save_yaml PATH_REGISTRY = 'entity_registry.yaml' DATA_REGISTRY = 'entity_registry' @@ -63,8 +63,13 @@ class RegistryEntry: """Listen for when entry is updated. Listener: Callback function(old_entry, new_entry) + + Returns function to unlisten. """ - self.update_listeners.append(weakref.ref(listener)) + weak_listener = weakref.ref(listener) + self.update_listeners.append(weak_listener) + + return lambda: self.update_listeners.remove(weak_listener) class EntityRegistry: @@ -133,13 +138,18 @@ class EntityRegistry: return entity @callback - def async_update_entity(self, entity_id, *, name=_UNDEF): + def async_update_entity(self, entity_id, *, name=_UNDEF, + new_entity_id=_UNDEF): """Update properties of an entity.""" - return self._async_update_entity(entity_id, name=name) + return self._async_update_entity( + entity_id, + name=name, + new_entity_id=new_entity_id + ) @callback def _async_update_entity(self, entity_id, *, name=_UNDEF, - config_entry_id=_UNDEF): + config_entry_id=_UNDEF, new_entity_id=_UNDEF): """Private facing update properties method.""" old = self.entities[entity_id] @@ -152,6 +162,20 @@ class EntityRegistry: config_entry_id != old.config_entry_id): changes['config_entry_id'] = config_entry_id + if new_entity_id is not _UNDEF and new_entity_id != old.entity_id: + if self.async_is_registered(new_entity_id): + raise ValueError('Entity is already registered') + + if not valid_entity_id(new_entity_id): + raise ValueError('Invalid entity ID') + + if (split_entity_id(new_entity_id)[0] != + split_entity_id(entity_id)[0]): + raise ValueError('New entity ID should be same domain') + + self.entities.pop(entity_id) + entity_id = changes['entity_id'] = new_entity_id + if not changes: return old diff --git a/tests/components/config/test_entity_registry.py b/tests/components/config/test_entity_registry.py index 1591b8da1d2c..559f29372de4 100644 --- a/tests/components/config/test_entity_registry.py +++ b/tests/components/config/test_entity_registry.py @@ -54,8 +54,8 @@ async def test_get_entity(hass, client): } -async def test_update_entity(hass, client): - """Test get entry.""" +async def test_update_entity_name(hass, client): + """Test updating entity name.""" mock_registry(hass, { 'test_domain.world': RegistryEntry( entity_id='test_domain.world', @@ -92,7 +92,7 @@ async def test_update_entity(hass, client): async def test_update_entity_no_changes(hass, client): - """Test get entry.""" + """Test update entity with no changes.""" mock_registry(hass, { 'test_domain.world': RegistryEntry( entity_id='test_domain.world', @@ -129,7 +129,7 @@ async def test_update_entity_no_changes(hass, client): async def test_get_nonexisting_entity(client): - """Test get entry.""" + """Test get entry with nonexisting entity.""" await client.send_json({ 'id': 6, 'type': 'config/entity_registry/get', @@ -141,7 +141,7 @@ async def test_get_nonexisting_entity(client): async def test_update_nonexisting_entity(client): - """Test get entry.""" + """Test update a nonexisting entity.""" await client.send_json({ 'id': 6, 'type': 'config/entity_registry/update', @@ -151,3 +151,37 @@ async def test_update_nonexisting_entity(client): msg = await client.receive_json() assert not msg['success'] + + +async def test_update_entity_id(hass, client): + """Test update entity id.""" + mock_registry(hass, { + 'test_domain.world': RegistryEntry( + entity_id='test_domain.world', + unique_id='1234', + # Using component.async_add_entities is equal to platform "domain" + platform='test_platform', + ) + }) + platform = MockEntityPlatform(hass) + entity = MockEntity(unique_id='1234') + await platform.async_add_entities([entity]) + + assert hass.states.get('test_domain.world') is not None + + await client.send_json({ + 'id': 6, + 'type': 'config/entity_registry/update', + 'entity_id': 'test_domain.world', + 'new_entity_id': 'test_domain.planet', + }) + + msg = await client.receive_json() + + assert msg['result'] == { + 'entity_id': 'test_domain.planet', + 'name': None + } + + assert hass.states.get('test_domain.world') is None + assert hass.states.get('test_domain.planet') is not None diff --git a/tests/helpers/test_entity.py b/tests/helpers/test_entity.py index 4981ad23cc00..e24bec489f49 100644 --- a/tests/helpers/test_entity.py +++ b/tests/helpers/test_entity.py @@ -400,3 +400,15 @@ def test_async_remove_no_platform(hass): assert len(hass.states.async_entity_ids()) == 1 yield from ent.async_remove() assert len(hass.states.async_entity_ids()) == 0 + + +async def test_async_remove_runs_callbacks(hass): + """Test async_remove method when no platform set.""" + result = [] + + ent = entity.Entity() + ent.hass = hass + ent.entity_id = 'test.test' + ent.async_on_remove(lambda: result.append(1)) + await ent.async_remove() + assert len(result) == 1 diff --git a/tests/helpers/test_entity_platform.py b/tests/helpers/test_entity_platform.py index 2d2f148189f6..b52405aa8bea 100644 --- a/tests/helpers/test_entity_platform.py +++ b/tests/helpers/test_entity_platform.py @@ -5,6 +5,8 @@ import unittest from unittest.mock import patch, Mock, MagicMock from datetime import timedelta +import pytest + from homeassistant.exceptions import PlatformNotReady import homeassistant.loader as loader from homeassistant.helpers.entity import generate_entity_id @@ -487,7 +489,7 @@ def test_registry_respect_entity_disabled(hass): assert hass.states.async_entity_ids() == [] -async def test_entity_registry_updates(hass): +async def test_entity_registry_updates_name(hass): """Test that updates on the entity registry update platform entities.""" registry = mock_registry(hass, { 'test_domain.world': entity_registry.RegistryEntry( @@ -602,3 +604,75 @@ def test_not_fails_with_adding_empty_entities_(hass): yield from component.async_add_entities([]) assert len(hass.states.async_entity_ids()) == 0 + + +async def test_entity_registry_updates_entity_id(hass): + """Test that updates on the entity registry update platform entities.""" + registry = mock_registry(hass, { + 'test_domain.world': entity_registry.RegistryEntry( + entity_id='test_domain.world', + unique_id='1234', + # Using component.async_add_entities is equal to platform "domain" + platform='test_platform', + name='Some name' + ) + }) + platform = MockEntityPlatform(hass) + entity = MockEntity(unique_id='1234') + await platform.async_add_entities([entity]) + + state = hass.states.get('test_domain.world') + assert state is not None + assert state.name == 'Some name' + + registry.async_update_entity('test_domain.world', + new_entity_id='test_domain.planet') + await hass.async_block_till_done() + await hass.async_block_till_done() + + assert hass.states.get('test_domain.world') is None + assert hass.states.get('test_domain.planet') is not None + + +async def test_entity_registry_updates_invalid_entity_id(hass): + """Test that we can't update to an invalid entity id.""" + registry = mock_registry(hass, { + 'test_domain.world': entity_registry.RegistryEntry( + entity_id='test_domain.world', + unique_id='1234', + # Using component.async_add_entities is equal to platform "domain" + platform='test_platform', + name='Some name' + ), + 'test_domain.existing': entity_registry.RegistryEntry( + entity_id='test_domain.existing', + unique_id='5678', + platform='test_platform', + ), + }) + platform = MockEntityPlatform(hass) + entity = MockEntity(unique_id='1234') + await platform.async_add_entities([entity]) + + state = hass.states.get('test_domain.world') + assert state is not None + assert state.name == 'Some name' + + with pytest.raises(ValueError): + registry.async_update_entity('test_domain.world', + new_entity_id='test_domain.existing') + + with pytest.raises(ValueError): + registry.async_update_entity('test_domain.world', + new_entity_id='invalid_entity_id') + + with pytest.raises(ValueError): + registry.async_update_entity('test_domain.world', + new_entity_id='diff_domain.world') + + await hass.async_block_till_done() + await hass.async_block_till_done() + + assert hass.states.get('test_domain.world') is not None + assert hass.states.get('invalid_entity_id') is None + assert hass.states.get('diff_domain.world') is None From 264c618b11e4b8ec4a5a46ffc734c3cedffe498e Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 24 Jul 2018 14:14:08 +0200 Subject: [PATCH 071/113] Bump frontend to 20180724.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 fb59d6254b04..18704183ec53 100644 --- a/homeassistant/components/frontend/__init__.py +++ b/homeassistant/components/frontend/__init__.py @@ -26,7 +26,7 @@ from homeassistant.helpers.translation import async_get_translations from homeassistant.loader import bind_hass from homeassistant.util.yaml import load_yaml -REQUIREMENTS = ['home-assistant-frontend==20180720.0'] +REQUIREMENTS = ['home-assistant-frontend==20180724.0'] DOMAIN = 'frontend' DEPENDENCIES = ['api', 'websocket_api', 'http', 'system_log', diff --git a/requirements_all.txt b/requirements_all.txt index 5a614e964718..8fb02383c275 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -415,7 +415,7 @@ hole==0.3.0 holidays==0.9.5 # homeassistant.components.frontend -home-assistant-frontend==20180720.0 +home-assistant-frontend==20180724.0 # homeassistant.components.homekit_controller # homekit==0.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index aad851a93cb4..be979aa5374d 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -81,7 +81,7 @@ hbmqtt==0.9.2 holidays==0.9.5 # homeassistant.components.frontend -home-assistant-frontend==20180720.0 +home-assistant-frontend==20180724.0 # homeassistant.components.homematicip_cloud homematicip==0.9.8 From 2eb125e90e58fddf097ec01a3cafc50382c046cd Mon Sep 17 00:00:00 2001 From: Giuseppe Date: Tue, 24 Jul 2018 18:35:57 +0200 Subject: [PATCH 072/113] Downgrade netatmo warning log to info (#15652) --- homeassistant/components/sensor/netatmo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/sensor/netatmo.py b/homeassistant/components/sensor/netatmo.py index bc0cbc5eea67..3e3f7ce9486e 100644 --- a/homeassistant/components/sensor/netatmo.py +++ b/homeassistant/components/sensor/netatmo.py @@ -340,7 +340,7 @@ class NetAtmoData: # Never hammer the NetAtmo API more than # twice per update interval newinterval = NETATMO_UPDATE_INTERVAL / 2 - _LOGGER.warning( + _LOGGER.info( "NetAtmo refresh interval reset to %d seconds", newinterval) else: From 45a7ca62aead2cfe17329ad14df6bca47844a46f Mon Sep 17 00:00:00 2001 From: Jason Hu Date: Tue, 24 Jul 2018 10:13:26 -0700 Subject: [PATCH 073/113] Add turn_on/off service to camera (#15051) * Add turn_on/off to camera * Add turn_on/off supported features to camera. Add turn_on/off service implementation to camera, add turn_on/off supported features and services to Demo camera. * Add camera supported_features tests * Resolve code review comment * Fix unit test * Use async_add_executor_job * Address review comment, change DemoCamera to local push * Rewrite tests/components/camera/test_demo * raise HTTPError instead return response --- homeassistant/components/camera/__init__.py | 87 ++++++++++++++++++- homeassistant/components/camera/demo.py | 48 +++++++--- homeassistant/components/camera/services.yaml | 14 +++ tests/components/camera/test_demo.py | 85 ++++++++++++++++-- 4 files changed, 216 insertions(+), 18 deletions(-) diff --git a/homeassistant/components/camera/__init__.py b/homeassistant/components/camera/__init__.py index 3c252adb7ea3..736bcec1e9ca 100644 --- a/homeassistant/components/camera/__init__.py +++ b/homeassistant/components/camera/__init__.py @@ -19,7 +19,8 @@ import async_timeout import voluptuous as vol from homeassistant.core import callback -from homeassistant.const import ATTR_ENTITY_ID +from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TURN_OFF, \ + SERVICE_TURN_ON from homeassistant.exceptions import HomeAssistantError from homeassistant.loader import bind_hass from homeassistant.helpers.entity import Entity @@ -47,6 +48,9 @@ STATE_RECORDING = 'recording' STATE_STREAMING = 'streaming' STATE_IDLE = 'idle' +# Bitfield of features supported by the camera entity +SUPPORT_ON_OFF = 1 + DEFAULT_CONTENT_TYPE = 'image/jpeg' ENTITY_IMAGE_URL = '/api/camera_proxy/{0}?token={1}' @@ -79,6 +83,35 @@ class Image: content = attr.ib(type=bytes) +@bind_hass +def turn_off(hass, entity_id=None): + """Turn off camera.""" + hass.add_job(async_turn_off, hass, entity_id) + + +@bind_hass +async def async_turn_off(hass, entity_id=None): + """Turn off camera.""" + data = {ATTR_ENTITY_ID: entity_id} if entity_id else {} + await hass.services.async_call(DOMAIN, SERVICE_TURN_OFF, data) + + +@bind_hass +def turn_on(hass, entity_id=None): + """Turn on camera.""" + hass.add_job(async_turn_on, hass, entity_id) + + +@bind_hass +async def async_turn_on(hass, entity_id=None): + """Turn on camera, and set operation mode.""" + data = {} + if entity_id is not None: + data[ATTR_ENTITY_ID] = entity_id + + await hass.services.async_call(DOMAIN, SERVICE_TURN_ON, data) + + @bind_hass def enable_motion_detection(hass, entity_id=None): """Enable Motion Detection.""" @@ -119,6 +152,9 @@ async def async_get_image(hass, entity_id, timeout=10): if camera is None: raise HomeAssistantError('Camera not found') + if not camera.is_on: + raise HomeAssistantError('Camera is off') + with suppress(asyncio.CancelledError, asyncio.TimeoutError): with async_timeout.timeout(timeout, loop=hass.loop): image = await camera.async_camera_image() @@ -163,6 +199,12 @@ async def async_setup(hass, config): await camera.async_enable_motion_detection() elif service.service == SERVICE_DISABLE_MOTION: await camera.async_disable_motion_detection() + elif service.service == SERVICE_TURN_OFF and \ + camera.supported_features & SUPPORT_ON_OFF: + await camera.async_turn_off() + elif service.service == SERVICE_TURN_ON and \ + camera.supported_features & SUPPORT_ON_OFF: + await camera.async_turn_on() if not camera.should_poll: continue @@ -200,6 +242,12 @@ async def async_setup(hass, config): except OSError as err: _LOGGER.error("Can't write image to file: %s", err) + hass.services.async_register( + DOMAIN, SERVICE_TURN_OFF, async_handle_camera_service, + schema=CAMERA_SERVICE_SCHEMA) + hass.services.async_register( + DOMAIN, SERVICE_TURN_ON, async_handle_camera_service, + schema=CAMERA_SERVICE_SCHEMA) hass.services.async_register( DOMAIN, SERVICE_ENABLE_MOTION, async_handle_camera_service, schema=CAMERA_SERVICE_SCHEMA) @@ -243,6 +291,11 @@ class Camera(Entity): """Return a link to the camera feed as entity picture.""" return ENTITY_IMAGE_URL.format(self.entity_id, self.access_tokens[-1]) + @property + def supported_features(self): + """Flag supported features.""" + return 0 + @property def is_recording(self): """Return true if the device is recording.""" @@ -337,10 +390,34 @@ class Camera(Entity): return STATE_STREAMING return STATE_IDLE + @property + def is_on(self): + """Return true if on.""" + return True + + def turn_off(self): + """Turn off camera.""" + raise NotImplementedError() + + @callback + def async_turn_off(self): + """Turn off camera.""" + return self.hass.async_add_job(self.turn_off) + + def turn_on(self): + """Turn off camera.""" + raise NotImplementedError() + + @callback + def async_turn_on(self): + """Turn off camera.""" + return self.hass.async_add_job(self.turn_on) + def enable_motion_detection(self): """Enable motion detection in the camera.""" raise NotImplementedError() + @callback def async_enable_motion_detection(self): """Call the job and enable motion detection.""" return self.hass.async_add_job(self.enable_motion_detection) @@ -349,6 +426,7 @@ class Camera(Entity): """Disable motion detection in camera.""" raise NotImplementedError() + @callback def async_disable_motion_detection(self): """Call the job and disable motion detection.""" return self.hass.async_add_job(self.disable_motion_detection) @@ -393,8 +471,7 @@ class CameraView(HomeAssistantView): camera = self.component.get_entity(entity_id) if camera is None: - status = 404 if request[KEY_AUTHENTICATED] else 401 - return web.Response(status=status) + raise web.HTTPNotFound() authenticated = (request[KEY_AUTHENTICATED] or request.query.get('token') in camera.access_tokens) @@ -402,6 +479,10 @@ class CameraView(HomeAssistantView): if not authenticated: raise web.HTTPUnauthorized() + if not camera.is_on: + _LOGGER.debug('Camera is off.') + raise web.HTTPServiceUnavailable() + return await self.handle(request, camera) async def handle(self, request, camera): diff --git a/homeassistant/components/camera/demo.py b/homeassistant/components/camera/demo.py index 3c1477d18286..0e77e6e95adc 100644 --- a/homeassistant/components/camera/demo.py +++ b/homeassistant/components/camera/demo.py @@ -4,10 +4,10 @@ Demo camera platform that has a fake camera. For more details about this platform, please refer to the documentation https://home-assistant.io/components/demo/ """ -import os import logging -import homeassistant.util.dt as dt_util -from homeassistant.components.camera import Camera +import os + +from homeassistant.components.camera import Camera, SUPPORT_ON_OFF _LOGGER = logging.getLogger(__name__) @@ -16,26 +16,29 @@ async def async_setup_platform(hass, config, async_add_devices, discovery_info=None): """Set up the Demo camera platform.""" async_add_devices([ - DemoCamera(hass, config, 'Demo camera') + DemoCamera('Demo camera') ]) class DemoCamera(Camera): """The representation of a Demo camera.""" - def __init__(self, hass, config, name): + def __init__(self, name): """Initialize demo camera component.""" super().__init__() - self._parent = hass self._name = name self._motion_status = False + self.is_streaming = True + self._images_index = 0 def camera_image(self): """Return a faked still image response.""" - now = dt_util.utcnow() + self._images_index = (self._images_index + 1) % 4 image_path = os.path.join( - os.path.dirname(__file__), 'demo_{}.jpg'.format(now.second % 4)) + os.path.dirname(__file__), + 'demo_{}.jpg'.format(self._images_index)) + _LOGGER.debug('Loading camera_image: %s', image_path) with open(image_path, 'rb') as file: return file.read() @@ -46,8 +49,21 @@ class DemoCamera(Camera): @property def should_poll(self): - """Camera should poll periodically.""" - return True + """Demo camera doesn't need poll. + + Need explicitly call schedule_update_ha_state() after state changed. + """ + return False + + @property + def supported_features(self): + """Camera support turn on/off features.""" + return SUPPORT_ON_OFF + + @property + def is_on(self): + """Whether camera is on (streaming).""" + return self.is_streaming @property def motion_detection_enabled(self): @@ -57,7 +73,19 @@ class DemoCamera(Camera): def enable_motion_detection(self): """Enable the Motion detection in base station (Arm).""" self._motion_status = True + self.schedule_update_ha_state() def disable_motion_detection(self): """Disable the motion detection in base station (Disarm).""" self._motion_status = False + self.schedule_update_ha_state() + + def turn_off(self): + """Turn off camera.""" + self.is_streaming = False + self.schedule_update_ha_state() + + def turn_on(self): + """Turn on camera.""" + self.is_streaming = True + self.schedule_update_ha_state() diff --git a/homeassistant/components/camera/services.yaml b/homeassistant/components/camera/services.yaml index 544fd0e6b8a4..b977fcd5c52f 100644 --- a/homeassistant/components/camera/services.yaml +++ b/homeassistant/components/camera/services.yaml @@ -1,5 +1,19 @@ # Describes the format for available camera services +turn_off: + description: Turn off camera. + fields: + entity_id: + description: Entity id. + example: 'camera.living_room' + +turn_on: + description: Turn on camera. + fields: + entity_id: + description: Entity id. + example: 'camera.living_room' + enable_motion_detection: description: Enable the motion detection in a camera. fields: diff --git a/tests/components/camera/test_demo.py b/tests/components/camera/test_demo.py index 51e04fca3517..b901b723c0be 100644 --- a/tests/components/camera/test_demo.py +++ b/tests/components/camera/test_demo.py @@ -1,14 +1,89 @@ """The tests for local file camera component.""" -import asyncio +from unittest.mock import mock_open, patch, PropertyMock + +import pytest + from homeassistant.components import camera +from homeassistant.components.camera import STATE_STREAMING, STATE_IDLE +from homeassistant.exceptions import HomeAssistantError from homeassistant.setup import async_setup_component -@asyncio.coroutine -def test_motion_detection(hass): +@pytest.fixture +def demo_camera(hass): + """Initialize a demo camera platform.""" + hass.loop.run_until_complete(async_setup_component(hass, 'camera', { + camera.DOMAIN: { + 'platform': 'demo' + } + })) + return hass.data['camera'].get_entity('camera.demo_camera') + + +async def test_init_state_is_streaming(hass, demo_camera): + """Demo camera initialize as streaming.""" + assert demo_camera.state == STATE_STREAMING + + mock_on_img = mock_open(read_data=b'ON') + with patch('homeassistant.components.camera.demo.open', mock_on_img, + create=True): + image = await camera.async_get_image(hass, demo_camera.entity_id) + assert mock_on_img.called + assert mock_on_img.call_args_list[0][0][0][-6:] \ + in ['_0.jpg', '_1.jpg', '_2.jpg', '_3.jpg'] + assert image.content == b'ON' + + +async def test_turn_on_state_back_to_streaming(hass, demo_camera): + """After turn on state back to streaming.""" + assert demo_camera.state == STATE_STREAMING + await camera.async_turn_off(hass, demo_camera.entity_id) + await hass.async_block_till_done() + + assert demo_camera.state == STATE_IDLE + + await camera.async_turn_on(hass, demo_camera.entity_id) + await hass.async_block_till_done() + + assert demo_camera.state == STATE_STREAMING + + +async def test_turn_off_image(hass, demo_camera): + """After turn off, Demo camera raise error.""" + await camera.async_turn_off(hass, demo_camera.entity_id) + await hass.async_block_till_done() + + with pytest.raises(HomeAssistantError) as error: + await camera.async_get_image(hass, demo_camera.entity_id) + assert error.args[0] == 'Camera is off' + + +async def test_turn_off_invalid_camera(hass, demo_camera): + """Turn off non-exist camera should quietly fail.""" + assert demo_camera.state == STATE_STREAMING + await camera.async_turn_off(hass, 'camera.invalid_camera') + await hass.async_block_till_done() + + assert demo_camera.state == STATE_STREAMING + + +async def test_turn_off_unsupport_camera(hass, demo_camera): + """Turn off unsupported camera should quietly fail.""" + assert demo_camera.state == STATE_STREAMING + with patch('homeassistant.components.camera.demo.DemoCamera' + '.supported_features', new_callable=PropertyMock) as m: + m.return_value = 0 + + await camera.async_turn_off(hass, demo_camera.entity_id) + await hass.async_block_till_done() + + assert demo_camera.state == STATE_STREAMING + + +async def test_motion_detection(hass): """Test motion detection services.""" # Setup platform - yield from async_setup_component(hass, 'camera', { + await async_setup_component(hass, 'camera', { 'camera': { 'platform': 'demo' } @@ -20,7 +95,7 @@ def test_motion_detection(hass): # Call service to turn on motion detection camera.enable_motion_detection(hass, 'camera.demo_camera') - yield from hass.async_block_till_done() + await hass.async_block_till_done() # Check if state has been updated. state = hass.states.get('camera.demo_camera') From 0cc9798c8f5756d36c9ca34fe8355e9e2e10c26b Mon Sep 17 00:00:00 2001 From: Daniel Kalmar Date: Tue, 24 Jul 2018 20:29:59 +0200 Subject: [PATCH 074/113] Allow defining default turn-on values for lights in the profiles file. (#15493) * Allow defining default turn-on values for lights in the profiles file. * Mock out file operations in unit test. * Fix unit test flakiness. * Avoid unnecessary copy --- homeassistant/components/light/__init__.py | 18 ++++- tests/components/light/test_init.py | 78 ++++++++++++++++++++++ 2 files changed, 95 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/light/__init__.py b/homeassistant/components/light/__init__.py index bd428a84bedc..472be92583ae 100644 --- a/homeassistant/components/light/__init__.py +++ b/homeassistant/components/light/__init__.py @@ -346,7 +346,12 @@ async def async_setup(hass, config): update_tasks = [] for light in target_lights: if service.service == SERVICE_TURN_ON: - await light.async_turn_on(**params) + pars = params + if not pars: + pars = params.copy() + pars[ATTR_PROFILE] = Profiles.get_default(light.entity_id) + preprocess_turn_on_alternatives(pars) + await light.async_turn_on(**pars) elif service.service == SERVICE_TURN_OFF: await light.async_turn_off(**params) else: @@ -431,6 +436,17 @@ class Profiles: """Return a named profile.""" return cls._all.get(name) + @classmethod + def get_default(cls, entity_id): + """Return the default turn-on profile for the given light.""" + name = entity_id + ".default" + if name in cls._all: + return name + name = ENTITY_ID_ALL_LIGHTS + ".default" + if name in cls._all: + return name + return None + class Light(ToggleEntity): """Representation of a light.""" diff --git a/tests/components/light/test_init.py b/tests/components/light/test_init.py index 634e3774b8a6..74f8c85b532e 100644 --- a/tests/components/light/test_init.py +++ b/tests/components/light/test_init.py @@ -1,7 +1,9 @@ """The tests for the Light component.""" # pylint: disable=protected-access import unittest +import unittest.mock as mock import os +from io import StringIO from homeassistant.setup import setup_component import homeassistant.loader as loader @@ -308,6 +310,82 @@ class TestLight(unittest.TestCase): light.ATTR_BRIGHTNESS: 100 }, data) + def test_default_profiles_group(self): + """Test default turn-on light profile for all lights.""" + platform = loader.get_component(self.hass, 'light.test') + platform.init() + + user_light_file = self.hass.config.path(light.LIGHT_PROFILES_FILE) + real_isfile = os.path.isfile + real_open = open + + def _mock_isfile(path): + if path == user_light_file: + return True + return real_isfile(path) + + def _mock_open(path): + if path == user_light_file: + return StringIO(profile_data) + return real_open(path) + + profile_data = "id,x,y,brightness\n" +\ + "group.all_lights.default,.4,.6,99\n" + with mock.patch('os.path.isfile', side_effect=_mock_isfile): + with mock.patch('builtins.open', side_effect=_mock_open): + self.assertTrue(setup_component( + self.hass, light.DOMAIN, + {light.DOMAIN: {CONF_PLATFORM: 'test'}} + )) + + dev, _, _ = platform.DEVICES + light.turn_on(self.hass, dev.entity_id) + self.hass.block_till_done() + _, data = dev.last_call('turn_on') + self.assertEqual({ + light.ATTR_HS_COLOR: (71.059, 100), + light.ATTR_BRIGHTNESS: 99 + }, data) + + def test_default_profiles_light(self): + """Test default turn-on light profile for a specific light.""" + platform = loader.get_component(self.hass, 'light.test') + platform.init() + + user_light_file = self.hass.config.path(light.LIGHT_PROFILES_FILE) + real_isfile = os.path.isfile + real_open = open + + def _mock_isfile(path): + if path == user_light_file: + return True + return real_isfile(path) + + def _mock_open(path): + if path == user_light_file: + return StringIO(profile_data) + return real_open(path) + + profile_data = "id,x,y,brightness\n" +\ + "group.all_lights.default,.3,.5,200\n" +\ + "light.ceiling_2.default,.6,.6,100\n" + with mock.patch('os.path.isfile', side_effect=_mock_isfile): + with mock.patch('builtins.open', side_effect=_mock_open): + self.assertTrue(setup_component( + self.hass, light.DOMAIN, + {light.DOMAIN: {CONF_PLATFORM: 'test'}} + )) + + dev = next(filter(lambda x: x.entity_id == 'light.ceiling_2', + platform.DEVICES)) + light.turn_on(self.hass, dev.entity_id) + self.hass.block_till_done() + _, data = dev.last_call('turn_on') + self.assertEqual({ + light.ATTR_HS_COLOR: (50.353, 100), + light.ATTR_BRIGHTNESS: 100 + }, data) + async def test_intent_set_color(hass): """Test the set color intent.""" From cbb5d34167480c1d162916a2112ff6f6c4c2f9dc Mon Sep 17 00:00:00 2001 From: Jerad Meisner Date: Wed, 25 Jul 2018 01:34:18 -0700 Subject: [PATCH 075/113] Added user credentials to current_user ws endpoint. (#15558) * Added user credentials to current_user ws endpoint. * Comments. Added another test. * Return list of credentials. --- homeassistant/components/auth/__init__.py | 3 +++ tests/components/auth/test_init.py | 18 ++++++++++++++++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/auth/__init__.py b/homeassistant/components/auth/__init__.py index 95a2a2f1fac6..f5b5ce62f8f2 100644 --- a/homeassistant/components/auth/__init__.py +++ b/homeassistant/components/auth/__init__.py @@ -433,4 +433,7 @@ def websocket_current_user(hass, connection, msg): 'id': user.id, 'name': user.name, 'is_owner': user.is_owner, + 'credentials': [{'auth_provider_type': c.auth_provider_type, + 'auth_provider_id': c.auth_provider_id} + for c in user.credentials] })) diff --git a/tests/components/auth/test_init.py b/tests/components/auth/test_init.py index 467d6dc3c6c0..eea768c96a09 100644 --- a/tests/components/auth/test_init.py +++ b/tests/components/auth/test_init.py @@ -2,6 +2,7 @@ from datetime import timedelta from unittest.mock import patch +from homeassistant.auth.models import Credentials from homeassistant.setup import async_setup_component from homeassistant.util.dt import utcnow from homeassistant.components import auth @@ -90,12 +91,20 @@ def test_credential_store_expiration(): async def test_ws_current_user(hass, hass_ws_client, hass_access_token): - """Test the current user command.""" + """Test the current user command with homeassistant creds.""" assert await async_setup_component(hass, 'auth', { 'http': { 'api_password': 'bla' } }) + + user = hass_access_token.refresh_token.user + credential = Credentials(auth_provider_type='homeassistant', + auth_provider_id=None, + data={}, id='test-id') + user.credentials.append(credential) + assert len(user.credentials) == 1 + with patch('homeassistant.auth.AuthManager.active', return_value=True): client = await hass_ws_client(hass, hass_access_token) @@ -107,12 +116,17 @@ async def test_ws_current_user(hass, hass_ws_client, hass_access_token): result = await client.receive_json() assert result['success'], result - user = hass_access_token.refresh_token.user user_dict = result['result'] assert user_dict['name'] == user.name assert user_dict['id'] == user.id assert user_dict['is_owner'] == user.is_owner + assert len(user_dict['credentials']) == 1 + + hass_cred = user_dict['credentials'][0] + assert hass_cred['auth_provider_type'] == 'homeassistant' + assert hass_cred['auth_provider_id'] is None + assert 'data' not in hass_cred async def test_cors_on_token(hass, aiohttp_client): From 397f551e6d168cfe16919296acc9281c1ecd6e98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Wed, 25 Jul 2018 12:35:22 +0300 Subject: [PATCH 076/113] Import collections abstract base classes from collections.abc (#15649) Accessing them directly through collections is deprecated since 3.7, and will no longer work in 3.8. --- homeassistant/components/google_assistant/smart_home.py | 4 ++-- homeassistant/components/notify/group.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/google_assistant/smart_home.py b/homeassistant/components/google_assistant/smart_home.py index 927139a483e0..63a3e641170b 100644 --- a/homeassistant/components/google_assistant/smart_home.py +++ b/homeassistant/components/google_assistant/smart_home.py @@ -1,5 +1,5 @@ """Support for Google Assistant Smart Home API.""" -import collections +from collections.abc import Mapping from itertools import product import logging @@ -50,7 +50,7 @@ DOMAIN_TO_GOOGLE_TYPES = { def deep_update(target, source): """Update a nested dictionary with another nested dictionary.""" for key, value in source.items(): - if isinstance(value, collections.Mapping): + if isinstance(value, Mapping): target[key] = deep_update(target.get(key, {}), value) else: target[key] = value diff --git a/homeassistant/components/notify/group.py b/homeassistant/components/notify/group.py index a98bb6c23178..94856c730b1a 100644 --- a/homeassistant/components/notify/group.py +++ b/homeassistant/components/notify/group.py @@ -5,7 +5,7 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/notify.group/ """ import asyncio -import collections +from collections.abc import Mapping from copy import deepcopy import logging import voluptuous as vol @@ -33,7 +33,7 @@ def update(input_dict, update_source): Async friendly. """ for key, val in update_source.items(): - if isinstance(val, collections.Mapping): + if isinstance(val, Mapping): recurse = update(input_dict.get(key, {}), val) input_dict[key] = recurse else: From 68f03dcc67e8e8c850b0e51ee769fba00cc82ebb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Wed, 25 Jul 2018 12:36:03 +0300 Subject: [PATCH 077/113] Auth typing improvements (#15640) * Always return bytes from auth.providers.homeassistant.hash_password Good for interface cleanliness, typing etc. * Add some homeassistant auth provider type annotations --- homeassistant/auth/providers/homeassistant.py | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/homeassistant/auth/providers/homeassistant.py b/homeassistant/auth/providers/homeassistant.py index d24110a47362..e9693b09634b 100644 --- a/homeassistant/auth/providers/homeassistant.py +++ b/homeassistant/auth/providers/homeassistant.py @@ -3,6 +3,7 @@ import base64 from collections import OrderedDict import hashlib import hmac +from typing import Dict # noqa: F401 pylint: disable=unused-import import voluptuous as vol @@ -68,12 +69,12 @@ class Data: """Return users.""" return self._data['users'] - def validate_login(self, username, password): + def validate_login(self, username: str, password: str) -> None: """Validate a username and password. Raises InvalidAuth if auth invalid. """ - password = self.hash_password(password) + hashed = self.hash_password(password) found = None @@ -84,33 +85,33 @@ class Data: if found is None: # Do one more compare to make timing the same as if user was found. - hmac.compare_digest(password, password) + hmac.compare_digest(hashed, hashed) raise InvalidAuth - if not hmac.compare_digest(password, + if not hmac.compare_digest(hashed, base64.b64decode(found['password'])): raise InvalidAuth - def hash_password(self, password, for_storage=False): + def hash_password(self, password: str, for_storage: bool = False) -> bytes: """Encode a password.""" hashed = hashlib.pbkdf2_hmac( 'sha512', password.encode(), self._data['salt'].encode(), 100000) if for_storage: - hashed = base64.b64encode(hashed).decode() + hashed = base64.b64encode(hashed) return hashed - def add_auth(self, username, password): + def add_auth(self, username: str, password: str) -> None: """Add a new authenticated user/pass.""" if any(user['username'] == username for user in self.users): raise InvalidUser self.users.append({ 'username': username, - 'password': self.hash_password(password, True), + 'password': self.hash_password(password, True).decode(), }) @callback - def async_remove_auth(self, username): + def async_remove_auth(self, username: str) -> None: """Remove authentication.""" index = None for i, user in enumerate(self.users): @@ -123,14 +124,15 @@ class Data: self.users.pop(index) - def change_password(self, username, new_password): + def change_password(self, username: str, new_password: str) -> None: """Update the password. Raises InvalidUser if user cannot be found. """ for user in self.users: if user['username'] == username: - user['password'] = self.hash_password(new_password, True) + user['password'] = self.hash_password( + new_password, True).decode() break else: raise InvalidUser @@ -160,7 +162,7 @@ class HassAuthProvider(AuthProvider): """Return a flow to login.""" return LoginFlow(self) - async def async_validate_login(self, username, password): + async def async_validate_login(self, username: str, password: str): """Helper to validate a username and password.""" if self.data is None: await self.async_initialize() @@ -225,7 +227,7 @@ class LoginFlow(data_entry_flow.FlowHandler): data=user_input ) - schema = OrderedDict() + schema = OrderedDict() # type: Dict[str, type] schema['username'] = str schema['password'] = str From 169c8d793aedc469c9d1e8233d8bcc38ff5555b3 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 25 Jul 2018 11:36:44 +0200 Subject: [PATCH 078/113] Fix CORS duplicate registration (#15670) --- homeassistant/components/http/cors.py | 40 +++++++++++++++------------ homeassistant/components/http/view.py | 8 ++---- tests/components/http/test_cors.py | 32 +++++++++++++++++++++ 3 files changed, 58 insertions(+), 22 deletions(-) diff --git a/homeassistant/components/http/cors.py b/homeassistant/components/http/cors.py index b01e68f701d9..555f302f8e19 100644 --- a/homeassistant/components/http/cors.py +++ b/homeassistant/components/http/cors.py @@ -27,30 +27,36 @@ def setup_cors(app, origins): ) for host in origins }) - def allow_cors(route, methods): - """Allow cors on a route.""" - cors.add(route, { - '*': aiohttp_cors.ResourceOptions( - allow_headers=ALLOWED_CORS_HEADERS, - allow_methods=methods, - ) - }) + cors_added = set() - app['allow_cors'] = allow_cors + def _allow_cors(route, config=None): + """Allow cors on a route.""" + if hasattr(route, 'resource'): + path = route.resource + else: + path = route + + path = path.canonical + + if path in cors_added: + return + + cors.add(route, config) + cors_added.add(path) + + app['allow_cors'] = lambda route: _allow_cors(route, { + '*': aiohttp_cors.ResourceOptions( + allow_headers=ALLOWED_CORS_HEADERS, + allow_methods='*', + ) + }) if not origins: return async def cors_startup(app): """Initialize cors when app starts up.""" - cors_added = set() - for route in list(app.router.routes()): - if hasattr(route, 'resource'): - route = route.resource - if route in cors_added: - continue - cors.add(route) - cors_added.add(route) + _allow_cors(route) app.on_startup.append(cors_startup) diff --git a/homeassistant/components/http/view.py b/homeassistant/components/http/view.py index f3d3cd06e22d..2b6c2a113c4d 100644 --- a/homeassistant/components/http/view.py +++ b/homeassistant/components/http/view.py @@ -69,15 +69,13 @@ class HomeAssistantView: handler = request_handler_factory(self, handler) for url in urls: - routes.append( - (method, router.add_route(method, url, handler)) - ) + routes.append(router.add_route(method, url, handler)) if not self.cors_allowed: return - for method, route in routes: - app['allow_cors'](route, [method.upper()]) + for route in routes: + app['allow_cors'](route) def request_handler_factory(view, handler): diff --git a/tests/components/http/test_cors.py b/tests/components/http/test_cors.py index 523d4943ba04..a510d2b3829e 100644 --- a/tests/components/http/test_cors.py +++ b/tests/components/http/test_cors.py @@ -14,6 +14,7 @@ import pytest from homeassistant.const import HTTP_HEADER_HA_AUTH from homeassistant.setup import async_setup_component from homeassistant.components.http.cors import setup_cors +from homeassistant.components.http.view import HomeAssistantView TRUSTED_ORIGIN = 'https://home-assistant.io' @@ -96,3 +97,34 @@ async def test_cors_preflight_allowed(client): assert req.headers[ACCESS_CONTROL_ALLOW_ORIGIN] == TRUSTED_ORIGIN assert req.headers[ACCESS_CONTROL_ALLOW_HEADERS] == \ HTTP_HEADER_HA_AUTH.upper() + + +async def test_cors_middleware_with_cors_allowed_view(hass): + """Test that we can configure cors and have a cors_allowed view.""" + class MyView(HomeAssistantView): + """Test view that allows CORS.""" + + requires_auth = False + cors_allowed = True + + def __init__(self, url, name): + """Initialize test view.""" + self.url = url + self.name = name + + async def get(self, request): + """Test response.""" + return "test" + + assert await async_setup_component(hass, 'http', { + 'http': { + 'cors_allowed_origins': ['http://home-assistant.io'] + } + }) + + hass.http.register_view(MyView('/api/test', 'api:test')) + hass.http.register_view(MyView('/api/test', 'api:test2')) + hass.http.register_view(MyView('/api/test2', 'api:test')) + + hass.http.app._on_startup.freeze() + await hass.http.app.startup() From 9ecbf86fa02c5f9765dac176c6dda5ac8625cf14 Mon Sep 17 00:00:00 2001 From: Peter Nijssen Date: Wed, 25 Jul 2018 11:51:48 +0200 Subject: [PATCH 079/113] Add spider thermostat (#15499) * add spider thermostats * Added load_platform. Added operation dictionary. Minor improvements * loop over spider components for load_platform * added empty dict to load_platform. changed add_devices * moved logic to the API * fix requirements_all.txt * minor code improvements --- .coveragerc | 3 + homeassistant/components/climate/spider.py | 140 +++++++++++++++++++++ homeassistant/components/spider.py | 56 +++++++++ requirements_all.txt | 3 + 4 files changed, 202 insertions(+) create mode 100644 homeassistant/components/climate/spider.py create mode 100644 homeassistant/components/spider.py diff --git a/.coveragerc b/.coveragerc index 73a79c2d87bf..09d2f765d2a1 100644 --- a/.coveragerc +++ b/.coveragerc @@ -346,6 +346,9 @@ omit = homeassistant/components/tuya.py homeassistant/components/*/tuya.py + homeassistant/components/spider.py + homeassistant/components/*/spider.py + homeassistant/components/alarm_control_panel/alarmdotcom.py homeassistant/components/alarm_control_panel/canary.py homeassistant/components/alarm_control_panel/concord232.py diff --git a/homeassistant/components/climate/spider.py b/homeassistant/components/climate/spider.py new file mode 100644 index 000000000000..9a8f7dd7fbd1 --- /dev/null +++ b/homeassistant/components/climate/spider.py @@ -0,0 +1,140 @@ +""" +Support for Spider thermostats. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/climate.spider/ +""" + +import logging + +from homeassistant.components.climate import ( + ATTR_TEMPERATURE, STATE_COOL, STATE_HEAT, STATE_IDLE, + SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE, ClimateDevice) +from homeassistant.components.spider import DOMAIN as SPIDER_DOMAIN +from homeassistant.const import TEMP_CELSIUS + +DEPENDENCIES = ['spider'] + +OPERATION_LIST = [ + STATE_HEAT, + STATE_COOL, +] + +HA_STATE_TO_SPIDER = { + STATE_COOL: 'Cool', + STATE_HEAT: 'Heat', + STATE_IDLE: 'Idle' +} + +SPIDER_STATE_TO_HA = {value: key for key, value in HA_STATE_TO_SPIDER.items()} + +_LOGGER = logging.getLogger(__name__) + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Set up the Spider thermostat.""" + if discovery_info is None: + return + + devices = [SpiderThermostat(hass.data[SPIDER_DOMAIN]['controller'], device) + for device in hass.data[SPIDER_DOMAIN]['thermostats']] + add_devices(devices, True) + + +class SpiderThermostat(ClimateDevice): + """Representation of a thermostat.""" + + def __init__(self, api, thermostat): + """Initialize the thermostat.""" + self.api = api + self.thermostat = thermostat + self.master = self.thermostat.has_operation_mode + + @property + def supported_features(self): + """Return the list of supported features.""" + supports = SUPPORT_TARGET_TEMPERATURE + + if self.thermostat.has_operation_mode: + supports = supports | SUPPORT_OPERATION_MODE + + return supports + + @property + def unique_id(self): + """Return the id of the thermostat, if any.""" + return self.thermostat.id + + @property + def name(self): + """Return the name of the thermostat, if any.""" + return self.thermostat.name + + @property + def temperature_unit(self): + """Return the unit of measurement.""" + return TEMP_CELSIUS + + @property + def current_temperature(self): + """Return the current temperature.""" + return self.thermostat.current_temperature + + @property + def target_temperature(self): + """Return the temperature we try to reach.""" + return self.thermostat.target_temperature + + @property + def target_temperature_step(self): + """Return the supported step of target temperature.""" + return self.thermostat.temperature_steps + + @property + def min_temp(self): + """Return the minimum temperature.""" + return self.thermostat.minimum_temperature + + @property + def max_temp(self): + """Return the maximum temperature.""" + return self.thermostat.maximum_temperature + + @property + def current_operation(self): + """Return current operation ie. heat, cool, idle.""" + return SPIDER_STATE_TO_HA[self.thermostat.operation_mode] + + @property + def operation_list(self): + """Return the list of available operation modes.""" + return OPERATION_LIST + + def set_temperature(self, **kwargs): + """Set new target temperature.""" + temperature = kwargs.get(ATTR_TEMPERATURE) + if temperature is None: + return + + self.thermostat.set_temperature(temperature) + + def set_operation_mode(self, operation_mode): + """Set new target operation mode.""" + self.thermostat.set_operation_mode( + HA_STATE_TO_SPIDER.get(operation_mode)) + + def update(self): + """Get the latest data.""" + try: + # Only let the master thermostat refresh + # and let the others use the cache + thermostats = self.api.get_thermostats( + force_refresh=self.master) + for thermostat in thermostats: + if thermostat.id == self.unique_id: + self.thermostat = thermostat + break + + except StopIteration: + _LOGGER.error("No data from the Spider API") + return diff --git a/homeassistant/components/spider.py b/homeassistant/components/spider.py new file mode 100644 index 000000000000..10dbd630b755 --- /dev/null +++ b/homeassistant/components/spider.py @@ -0,0 +1,56 @@ +""" +Support for Spider Smart devices. + +For more details about this component, please refer to the documentation at +https://home-assistant.io/components/spider/ +""" +import logging + +import voluptuous as vol + +from homeassistant.const import CONF_PASSWORD, CONF_USERNAME +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.discovery import load_platform + +REQUIREMENTS = ['spiderpy==1.0.7'] + +_LOGGER = logging.getLogger(__name__) + +DOMAIN = 'spider' + +SPIDER_COMPONENTS = [ + 'climate' +] + +CONFIG_SCHEMA = vol.Schema({ + DOMAIN: vol.Schema({ + vol.Required(CONF_PASSWORD): cv.string, + vol.Required(CONF_USERNAME): cv.string, + }) +}, extra=vol.ALLOW_EXTRA) + + +def setup(hass, config): + """Set up Spider Component.""" + from spiderpy.spiderapi import SpiderApi + from spiderpy.spiderapi import UnauthorizedException + + username = config[DOMAIN][CONF_USERNAME] + password = config[DOMAIN][CONF_PASSWORD] + + try: + api = SpiderApi(username, password) + + hass.data[DOMAIN] = { + 'controller': api, + 'thermostats': api.get_thermostats() + } + + for component in SPIDER_COMPONENTS: + load_platform(hass, component, DOMAIN, {}) + + _LOGGER.debug("Connection with Spider API succeeded") + return True + except UnauthorizedException: + _LOGGER.error("Can't connect to the Spider API") + return False diff --git a/requirements_all.txt b/requirements_all.txt index 8fb02383c275..2cd4d8d4a491 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1278,6 +1278,9 @@ somecomfort==0.5.2 # homeassistant.components.sensor.speedtest speedtest-cli==2.0.2 +# homeassistant.components.spider +spiderpy==1.0.7 + # homeassistant.components.sensor.spotcrime spotcrime==1.0.3 From 95dc06cca673b44ba7175e65faa7d60c74a16ab2 Mon Sep 17 00:00:00 2001 From: Eduard van Valkenburg Date: Wed, 25 Jul 2018 12:17:12 +0200 Subject: [PATCH 080/113] Add Brunt Cover Device (#15653) * New Brunt Branch * Some small changes and updates based on review. --- .coveragerc | 1 + homeassistant/components/cover/brunt.py | 182 ++++++++++++++++++++++++ requirements_all.txt | 3 + 3 files changed, 186 insertions(+) create mode 100644 homeassistant/components/cover/brunt.py diff --git a/.coveragerc b/.coveragerc index 09d2f765d2a1..7e10830d8761 100644 --- a/.coveragerc +++ b/.coveragerc @@ -401,6 +401,7 @@ omit = homeassistant/components/climate/touchline.py homeassistant/components/climate/venstar.py homeassistant/components/climate/zhong_hong.py + homeassistant/components/cover/brunt.py homeassistant/components/cover/garadget.py homeassistant/components/cover/gogogate2.py homeassistant/components/cover/homematic.py diff --git a/homeassistant/components/cover/brunt.py b/homeassistant/components/cover/brunt.py new file mode 100644 index 000000000000..713f06db7359 --- /dev/null +++ b/homeassistant/components/cover/brunt.py @@ -0,0 +1,182 @@ +""" +Support for Brunt Blind Engine covers. + +For more details about this component, please refer to the documentation at +https://home-assistant.io/components/cover.brunt +""" + +import logging + +import voluptuous as vol + +from homeassistant.const import ( + ATTR_ATTRIBUTION, CONF_PASSWORD, CONF_USERNAME) +from homeassistant.components.cover import ( + ATTR_POSITION, CoverDevice, + PLATFORM_SCHEMA, SUPPORT_CLOSE, + SUPPORT_OPEN, SUPPORT_SET_POSITION +) +import homeassistant.helpers.config_validation as cv + +REQUIREMENTS = ['brunt==0.1.2'] + +_LOGGER = logging.getLogger(__name__) + +COVER_FEATURES = SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_SET_POSITION +DEVICE_CLASS = 'window' + +ATTR_REQUEST_POSITION = 'request_position' +NOTIFICATION_ID = 'brunt_notification' +NOTIFICATION_TITLE = 'Brunt Cover Setup' +ATTRIBUTION = 'Based on an unofficial Brunt SDK.' + +CLOSED_POSITION = 0 +OPEN_POSITION = 100 + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_USERNAME): cv.string, + vol.Required(CONF_PASSWORD): cv.string +}) + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Set up the brunt platform.""" + # pylint: disable=no-name-in-module + from brunt import BruntAPI + username = config[CONF_USERNAME] + password = config[CONF_PASSWORD] + + bapi = BruntAPI(username=username, password=password) + try: + things = bapi.getThings()['things'] + if not things: + _LOGGER.error("No things present in account.") + else: + add_devices([BruntDevice( + bapi, thing['NAME'], + thing['thingUri']) for thing in things], True) + except (TypeError, KeyError, NameError, ValueError) as ex: + _LOGGER.error("%s", ex) + hass.components.persistent_notification.create( + 'Error: {}
' + 'You will need to restart hass after fixing.' + ''.format(ex), + title=NOTIFICATION_TITLE, + notification_id=NOTIFICATION_ID) + + +class BruntDevice(CoverDevice): + """ + Representation of a Brunt cover device. + + Contains the common logic for all Brunt devices. + """ + + def __init__(self, bapi, name, thing_uri): + """Init the Brunt device.""" + self._bapi = bapi + self._name = name + self._thing_uri = thing_uri + + self._state = {} + self._available = None + + @property + def name(self): + """Return the name of the device as reported by tellcore.""" + return self._name + + @property + def available(self): + """Could the device be accessed during the last update call.""" + return self._available + + @property + def current_cover_position(self): + """ + Return current position of cover. + + None is unknown, 0 is closed, 100 is fully open. + """ + pos = self._state.get('currentPosition') + return int(pos) if pos else None + + @property + def request_cover_position(self): + """ + Return request position of cover. + + The request position is the position of the last request + to Brunt, at times there is a diff of 1 to current + None is unknown, 0 is closed, 100 is fully open. + """ + pos = self._state.get('requestPosition') + return int(pos) if pos else None + + @property + def move_state(self): + """ + Return current moving state of cover. + + None is unknown, 0 when stopped, 1 when opening, 2 when closing + """ + mov = self._state.get('moveState') + return int(mov) if mov else None + + @property + def is_opening(self): + """Return if the cover is opening or not.""" + return self.move_state == 1 + + @property + def is_closing(self): + """Return if the cover is closing or not.""" + return self.move_state == 2 + + @property + def device_state_attributes(self): + """Return the detailed device state attributes.""" + return { + ATTR_ATTRIBUTION: ATTRIBUTION, + ATTR_REQUEST_POSITION: self.request_cover_position + } + + @property + def device_class(self): + """Return the class of this device, from component DEVICE_CLASSES.""" + return DEVICE_CLASS + + @property + def supported_features(self): + """Flag supported features.""" + return COVER_FEATURES + + @property + def is_closed(self): + """Return true if cover is closed, else False.""" + return self.current_cover_position == CLOSED_POSITION + + def update(self): + """Poll the current state of the device.""" + try: + self._state = self._bapi.getState( + thingUri=self._thing_uri).get('thing') + self._available = True + except (TypeError, KeyError, NameError, ValueError) as ex: + _LOGGER.error("%s", ex) + self._available = False + + def open_cover(self, **kwargs): + """Set the cover to the open position.""" + self._bapi.changeRequestPosition( + OPEN_POSITION, thingUri=self._thing_uri) + + def close_cover(self, **kwargs): + """Set the cover to the closed position.""" + self._bapi.changeRequestPosition( + CLOSED_POSITION, thingUri=self._thing_uri) + + def set_cover_position(self, **kwargs): + """Set the cover to a specific position.""" + self._bapi.changeRequestPosition( + kwargs[ATTR_POSITION], thingUri=self._thing_uri) diff --git a/requirements_all.txt b/requirements_all.txt index 2cd4d8d4a491..3d4b48398f9c 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -196,6 +196,9 @@ braviarc-homeassistant==0.3.7.dev0 # homeassistant.components.switch.broadlink broadlink==0.9.0 +# homeassistant.components.cover.brunt +brunt==0.1.2 + # homeassistant.components.device_tracker.bluetooth_tracker bt_proximity==0.1.2 From 1c42caba76fd23c1da536b148a5b21a864869d8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Wed, 25 Jul 2018 20:35:57 +0300 Subject: [PATCH 081/113] Pylint 2 useless-return fixes (#15677) --- homeassistant/components/climate/heatmiser.py | 1 - homeassistant/components/cloud/iot.py | 2 -- homeassistant/components/fan/comfoconnect.py | 1 - homeassistant/components/mailgun.py | 1 - homeassistant/components/notify/xmpp.py | 1 - homeassistant/components/sensor/fritzbox_callmonitor.py | 1 - homeassistant/components/sensor/modem_callerid.py | 2 -- homeassistant/monkey_patch.py | 2 -- homeassistant/util/async_.py | 1 - 9 files changed, 12 deletions(-) diff --git a/homeassistant/components/climate/heatmiser.py b/homeassistant/components/climate/heatmiser.py index 92e363228a8b..12057e886472 100644 --- a/homeassistant/components/climate/heatmiser.py +++ b/homeassistant/components/climate/heatmiser.py @@ -50,7 +50,6 @@ def setup_platform(hass, config, add_devices, discovery_info=None): HeatmiserV3Thermostat( heatmiser, tstat.get(CONF_ID), tstat.get(CONF_NAME), serport) ]) - return class HeatmiserV3Thermostat(ClimateDevice): diff --git a/homeassistant/components/cloud/iot.py b/homeassistant/components/cloud/iot.py index 12b81c9003bc..f4ce7bb3d1af 100644 --- a/homeassistant/components/cloud/iot.py +++ b/homeassistant/components/cloud/iot.py @@ -253,5 +253,3 @@ def async_handle_cloud(hass, cloud, payload): payload['reason']) else: _LOGGER.warning("Received unknown cloud action: %s", action) - - return None diff --git a/homeassistant/components/fan/comfoconnect.py b/homeassistant/components/fan/comfoconnect.py index 12dc0b1104f2..fd3265b82303 100644 --- a/homeassistant/components/fan/comfoconnect.py +++ b/homeassistant/components/fan/comfoconnect.py @@ -31,7 +31,6 @@ def setup_platform(hass, config, add_devices, discovery_info=None): ccb = hass.data[DOMAIN] add_devices([ComfoConnectFan(hass, name=ccb.name, ccb=ccb)], True) - return class ComfoConnectFan(FanEntity): diff --git a/homeassistant/components/mailgun.py b/homeassistant/components/mailgun.py index ec480ac12d60..7cb7ef7151dc 100644 --- a/homeassistant/components/mailgun.py +++ b/homeassistant/components/mailgun.py @@ -48,4 +48,3 @@ class MailgunReceiveMessageView(HomeAssistantView): hass = request.app['hass'] data = yield from request.post() hass.bus.async_fire(MESSAGE_RECEIVED, dict(data)) - return diff --git a/homeassistant/components/notify/xmpp.py b/homeassistant/components/notify/xmpp.py index 12ddf49fca8b..c5678dff3513 100644 --- a/homeassistant/components/notify/xmpp.py +++ b/homeassistant/components/notify/xmpp.py @@ -110,6 +110,5 @@ def send_message(sender, password, recipient, use_tls, def discard_ssl_invalid_cert(event): """Do nothing if ssl certificate is invalid.""" _LOGGER.info('Ignoring invalid ssl certificate as requested.') - return SendNotificationBot() diff --git a/homeassistant/components/sensor/fritzbox_callmonitor.py b/homeassistant/components/sensor/fritzbox_callmonitor.py index e42be861d378..3da9c512ebdd 100644 --- a/homeassistant/components/sensor/fritzbox_callmonitor.py +++ b/homeassistant/components/sensor/fritzbox_callmonitor.py @@ -187,7 +187,6 @@ class FritzBoxCallMonitor: line = response.split("\n", 1)[0] self._parse(line) time.sleep(1) - return def _parse(self, line): """Parse the call information and set the sensor states.""" diff --git a/homeassistant/components/sensor/modem_callerid.py b/homeassistant/components/sensor/modem_callerid.py index f80ea5853c8a..58e8becd6bb6 100644 --- a/homeassistant/components/sensor/modem_callerid.py +++ b/homeassistant/components/sensor/modem_callerid.py @@ -95,7 +95,6 @@ class ModemCalleridSensor(Entity): if self.modem: self.modem.close() self.modem = None - return def _incomingcallcallback(self, newstate): """Handle new states.""" @@ -117,4 +116,3 @@ class ModemCalleridSensor(Entity): elif newstate == self.modem.STATE_IDLE: self._state = STATE_IDLE self.schedule_update_ha_state() - return diff --git a/homeassistant/monkey_patch.py b/homeassistant/monkey_patch.py index aa330ffec167..edd25817f5af 100644 --- a/homeassistant/monkey_patch.py +++ b/homeassistant/monkey_patch.py @@ -57,7 +57,6 @@ def disable_c_asyncio() -> None: def __init__(self, path_entry: str) -> None: if path_entry != self.PATH_TRIGGER: raise ImportError() - return def find_module(self, fullname: str, path: Any = None) -> None: """Find a module.""" @@ -65,7 +64,6 @@ def disable_c_asyncio() -> None: # We lint in Py35, exception is introduced in Py36 # pylint: disable=undefined-variable raise ModuleNotFoundError() # type: ignore # noqa - return None sys.path_hooks.append(AsyncioImportFinder) sys.path.insert(0, AsyncioImportFinder.PATH_TRIGGER) diff --git a/homeassistant/util/async_.py b/homeassistant/util/async_.py index 1e2eb25245a9..aa030bf13c72 100644 --- a/homeassistant/util/async_.py +++ b/homeassistant/util/async_.py @@ -156,7 +156,6 @@ def fire_coroutine_threadsafe(coro: Coroutine, ensure_future(coro, loop=loop) loop.call_soon_threadsafe(callback) - return def run_callback_threadsafe(loop: AbstractEventLoop, callback: Callable, From 9fb8bc8991eda47488e2bfeac37c1d41378a9174 Mon Sep 17 00:00:00 2001 From: Jason Hu Date: Wed, 25 Jul 2018 14:17:38 -0700 Subject: [PATCH 082/113] Allow Nest Cam turn on/off (#15681) * Allow Nest Cam turn on/off * Don't raise Error * Remove unnecessary state update --- homeassistant/components/camera/nest.py | 34 +++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/camera/nest.py b/homeassistant/components/camera/nest.py index fcd9b7e8a074..bf6700371fd2 100644 --- a/homeassistant/components/camera/nest.py +++ b/homeassistant/components/camera/nest.py @@ -10,7 +10,8 @@ from datetime import timedelta import requests from homeassistant.components import nest -from homeassistant.components.camera import (PLATFORM_SCHEMA, Camera) +from homeassistant.components.camera import (PLATFORM_SCHEMA, Camera, + SUPPORT_ON_OFF) from homeassistant.util.dt import utcnow _LOGGER = logging.getLogger(__name__) @@ -76,7 +77,36 @@ class NestCamera(Camera): """Return the brand of the camera.""" return NEST_BRAND - # This doesn't seem to be getting called regularly, for some reason + @property + def supported_features(self): + """Nest Cam support turn on and off.""" + return SUPPORT_ON_OFF + + @property + def is_on(self): + """Return true if on.""" + return self._online and self._is_streaming + + def turn_off(self): + """Turn off camera.""" + _LOGGER.debug('Turn off camera %s', self._name) + # Calling Nest API in is_streaming setter. + # device.is_streaming would not immediately change until the process + # finished in Nest Cam. + self.device.is_streaming = False + + def turn_on(self): + """Turn on camera.""" + if not self._online: + _LOGGER.error('Camera %s is offline.', self._name) + return + + _LOGGER.debug('Turn on camera %s', self._name) + # Calling Nest API in is_streaming setter. + # device.is_streaming would not immediately change until the process + # finished in Nest Cam. + self.device.is_streaming = True + def update(self): """Cache value from Python-nest.""" self._location = self.device.where From eee9b50b70ea7795ce2b4736aee6e8a9a98501ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Thu, 26 Jul 2018 09:55:42 +0300 Subject: [PATCH 083/113] Upgrade pylint to 2.0.1 (#15683) * Upgrade pylint to 2.0.1 * Pylint 2 bad-whitespace fix * Pylint 2 possibly-unused-variable fixes * Pylint 2 try-except-raise fixes * Disable pylint fixme for todoist for now https://github.com/PyCQA/pylint/pull/2320 * Disable pylint 2 useless-return for now https://github.com/PyCQA/pylint/issues/2300 * Disable pylint 2 invalid-name for type variables for now https://github.com/PyCQA/pylint/issues/1290 * Disable pylint 2 not-an-iterable for now https://github.com/PyCQA/pylint/issues/2311 * Pylint 2 unsubscriptable-object workarounds * Disable intentional pylint 2 assignment-from-nones * Disable pylint 2 unsupported-membership-test apparent false positives * Disable pylint 2 assignment-from-no-return apparent false positives * Disable pylint 2 comparison-with-callable false positives https://github.com/PyCQA/pylint/issues/2306 --- homeassistant/components/apple_tv.py | 2 +- homeassistant/components/calendar/todoist.py | 3 +++ homeassistant/components/light/__init__.py | 1 + homeassistant/components/media_player/bluesound.py | 8 ++------ homeassistant/components/media_player/pandora.py | 2 ++ homeassistant/components/sensor/citybikes.py | 13 ++++--------- homeassistant/components/sensor/nzbget.py | 6 +----- homeassistant/components/sensor/pyload.py | 6 +----- homeassistant/core.py | 2 ++ homeassistant/helpers/intent.py | 3 ++- homeassistant/loader.py | 2 +- homeassistant/scripts/check_config.py | 4 ++-- homeassistant/scripts/influxdb_import.py | 1 + homeassistant/util/__init__.py | 5 +++++ homeassistant/util/decorator.py | 3 ++- homeassistant/util/dt.py | 2 +- homeassistant/util/yaml.py | 4 ++-- pylintrc | 6 +++++- requirements_test.txt | 2 +- requirements_test_all.txt | 2 +- 20 files changed, 40 insertions(+), 37 deletions(-) diff --git a/homeassistant/components/apple_tv.py b/homeassistant/components/apple_tv.py index 958e4a197775..97fb2363024f 100644 --- a/homeassistant/components/apple_tv.py +++ b/homeassistant/components/apple_tv.py @@ -45,7 +45,7 @@ NOTIFICATION_AUTH_TITLE = 'Apple TV Authentication' NOTIFICATION_SCAN_ID = 'apple_tv_scan_notification' NOTIFICATION_SCAN_TITLE = 'Apple TV Scan' -T = TypeVar('T') +T = TypeVar('T') # pylint: disable=invalid-name # This version of ensure_list interprets an empty dict as no value diff --git a/homeassistant/components/calendar/todoist.py b/homeassistant/components/calendar/todoist.py index ba1f60027ba3..30c5a6177b4a 100644 --- a/homeassistant/components/calendar/todoist.py +++ b/homeassistant/components/calendar/todoist.py @@ -26,6 +26,9 @@ CONF_PROJECT_DUE_DATE = 'due_date_days' CONF_PROJECT_LABEL_WHITELIST = 'labels' CONF_PROJECT_WHITELIST = 'include_projects' +# https://github.com/PyCQA/pylint/pull/2320 +# pylint: disable=fixme + # Calendar Platform: Does this calendar event last all day? ALL_DAY = 'all_day' # Attribute: All tasks in this project diff --git a/homeassistant/components/light/__init__.py b/homeassistant/components/light/__init__.py index 472be92583ae..58991a8e5059 100644 --- a/homeassistant/components/light/__init__.py +++ b/homeassistant/components/light/__init__.py @@ -439,6 +439,7 @@ class Profiles: @classmethod def get_default(cls, entity_id): """Return the default turn-on profile for the given light.""" + # pylint: disable=unsupported-membership-test name = entity_id + ".default" if name in cls._all: return name diff --git a/homeassistant/components/media_player/bluesound.py b/homeassistant/components/media_player/bluesound.py index a6b345b1d3bc..5631ec06cf1a 100644 --- a/homeassistant/components/media_player/bluesound.py +++ b/homeassistant/components/media_player/bluesound.py @@ -216,12 +216,8 @@ class BluesoundPlayer(MediaPlayerDevice): async def force_update_sync_status( self, on_updated_cb=None, raise_timeout=False): """Update the internal status.""" - resp = None - try: - resp = await self.send_bluesound_command( - 'SyncStatus', raise_timeout, raise_timeout) - except Exception: - raise + resp = await self.send_bluesound_command( + 'SyncStatus', raise_timeout, raise_timeout) if not resp: return None diff --git a/homeassistant/components/media_player/pandora.py b/homeassistant/components/media_player/pandora.py index 30e307fd1172..c4d8b7780955 100644 --- a/homeassistant/components/media_player/pandora.py +++ b/homeassistant/components/media_player/pandora.py @@ -253,9 +253,11 @@ class PandoraMediaPlayer(MediaPlayerDevice): _LOGGER.warning("On unexpected station list page") self._pianobar.sendcontrol('m') # press enter self._pianobar.sendcontrol('m') # do it again b/c an 'i' got in + # pylint: disable=assignment-from-none response = self.update_playing_status() elif match_idx == 3: _LOGGER.debug("Received new playlist list") + # pylint: disable=assignment-from-none response = self.update_playing_status() else: response = self._pianobar.before.decode('utf-8') diff --git a/homeassistant/components/sensor/citybikes.py b/homeassistant/components/sensor/citybikes.py index 24f8ea7e6a94..c9a69923135c 100644 --- a/homeassistant/components/sensor/citybikes.py +++ b/homeassistant/components/sensor/citybikes.py @@ -186,19 +186,14 @@ class CityBikesNetwork: networks = yield from async_citybikes_request( hass, NETWORKS_URI, NETWORKS_RESPONSE_SCHEMA) cls.NETWORKS_LIST = networks[ATTR_NETWORKS_LIST] - networks_list = cls.NETWORKS_LIST - network = networks_list[0] - result = network[ATTR_ID] - minimum_dist = location.distance( - latitude, longitude, - network[ATTR_LOCATION][ATTR_LATITUDE], - network[ATTR_LOCATION][ATTR_LONGITUDE]) - for network in networks_list[1:]: + result = None + minimum_dist = None + for network in cls.NETWORKS_LIST: network_latitude = network[ATTR_LOCATION][ATTR_LATITUDE] network_longitude = network[ATTR_LOCATION][ATTR_LONGITUDE] dist = location.distance( latitude, longitude, network_latitude, network_longitude) - if dist < minimum_dist: + if minimum_dist is None or dist < minimum_dist: minimum_dist = dist result = network[ATTR_ID] diff --git a/homeassistant/components/sensor/nzbget.py b/homeassistant/components/sensor/nzbget.py index a8dda416a547..a6fee5a69e8f 100644 --- a/homeassistant/components/sensor/nzbget.py +++ b/homeassistant/components/sensor/nzbget.py @@ -173,8 +173,4 @@ class NZBGetAPI: @Throttle(MIN_TIME_BETWEEN_UPDATES) def update(self): """Update cached response.""" - try: - self.status = self.post('status')['result'] - except requests.exceptions.ConnectionError: - # failed to update status - exception already logged in self.post - raise + self.status = self.post('status')['result'] diff --git a/homeassistant/components/sensor/pyload.py b/homeassistant/components/sensor/pyload.py index a5593c259a55..4aa121e0895c 100644 --- a/homeassistant/components/sensor/pyload.py +++ b/homeassistant/components/sensor/pyload.py @@ -162,8 +162,4 @@ class PyLoadAPI: @Throttle(MIN_TIME_BETWEEN_UPDATES) def update(self): """Update cached response.""" - try: - self.status = self.post('speed') - except requests.exceptions.ConnectionError: - # Failed to update status - exception already logged in self.post - raise + self.status = self.post('speed') diff --git a/homeassistant/core.py b/homeassistant/core.py index a7684d130ae4..828dfc24d6ca 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -49,9 +49,11 @@ from homeassistant.util.unit_system import UnitSystem, METRIC_SYSTEM # NOQA if TYPE_CHECKING: from homeassistant.config_entries import ConfigEntries # noqa +# pylint: disable=invalid-name T = TypeVar('T') CALLABLE_T = TypeVar('CALLABLE_T', bound=Callable) CALLBACK_TYPE = Callable[[], None] +# pylint: enable=invalid-name DOMAIN = 'homeassistant' diff --git a/homeassistant/helpers/intent.py b/homeassistant/helpers/intent.py index 4357c4109ebb..8f26d4fe0eee 100644 --- a/homeassistant/helpers/intent.py +++ b/homeassistant/helpers/intent.py @@ -63,7 +63,8 @@ async def async_handle(hass, platform, intent_type, slots=None, intent_type, err) raise InvalidSlotInfo( 'Received invalid slot info for {}'.format(intent_type)) from err - except IntentHandleError: + # https://github.com/PyCQA/pylint/issues/2284 + except IntentHandleError: # pylint: disable=try-except-raise raise except Exception as err: raise IntentUnexpectedError( diff --git a/homeassistant/loader.py b/homeassistant/loader.py index c5cf99de2349..3ac49e354b5d 100644 --- a/homeassistant/loader.py +++ b/homeassistant/loader.py @@ -27,7 +27,7 @@ from homeassistant.util import OrderedSet if TYPE_CHECKING: from homeassistant.core import HomeAssistant # NOQA -CALLABLE_T = TypeVar('CALLABLE_T', bound=Callable) +CALLABLE_T = TypeVar('CALLABLE_T', bound=Callable) # noqa pylint: disable=invalid-name PREPARED = False diff --git a/homeassistant/scripts/check_config.py b/homeassistant/scripts/check_config.py index 1924de88aafd..d7be5b1a91c7 100644 --- a/homeassistant/scripts/check_config.py +++ b/homeassistant/scripts/check_config.py @@ -163,13 +163,13 @@ def check(config_dir, secrets=False): 'secret_cache': None, } - # pylint: disable=unused-variable + # pylint: disable=possibly-unused-variable def mock_load(filename): """Mock hass.util.load_yaml to save config file names.""" res['yaml_files'][filename] = True return MOCKS['load'][1](filename) - # pylint: disable=unused-variable + # pylint: disable=possibly-unused-variable def mock_secrets(ldr, node): """Mock _get_secrets.""" try: diff --git a/homeassistant/scripts/influxdb_import.py b/homeassistant/scripts/influxdb_import.py index 421e84d503a7..031df1d3a72a 100644 --- a/homeassistant/scripts/influxdb_import.py +++ b/homeassistant/scripts/influxdb_import.py @@ -137,6 +137,7 @@ def run(script_args: List) -> int: override_measurement = args.override_measurement default_measurement = args.default_measurement + # pylint: disable=assignment-from-no-return query = session.query(func.count(models.Events.event_type)).filter( models.Events.event_type == 'state_changed') diff --git a/homeassistant/util/__init__.py b/homeassistant/util/__init__.py index 37f669944d97..ff098b24fb88 100644 --- a/homeassistant/util/__init__.py +++ b/homeassistant/util/__init__.py @@ -17,9 +17,11 @@ from typing import (Any, Optional, TypeVar, Callable, KeysView, Union, # noqa from .dt import as_local, utcnow +# pylint: disable=invalid-name T = TypeVar('T') U = TypeVar('U') ENUM_T = TypeVar('ENUM_T', bound=enum.Enum) +# pylint: enable=invalid-name RE_SANITIZE_FILENAME = re.compile(r'(~|\.\.|/|\\)') RE_SANITIZE_PATH = re.compile(r'(~|\.(\.)+)') @@ -121,6 +123,9 @@ def get_random_string(length: int = 10) -> str: class OrderedEnum(enum.Enum): """Taken from Python 3.4.0 docs.""" + # https://github.com/PyCQA/pylint/issues/2306 + # pylint: disable=comparison-with-callable + def __ge__(self: ENUM_T, other: ENUM_T) -> bool: """Return the greater than element.""" if self.__class__ is other.__class__: diff --git a/homeassistant/util/decorator.py b/homeassistant/util/decorator.py index 9d2a4600a64c..22ed1a4dae66 100644 --- a/homeassistant/util/decorator.py +++ b/homeassistant/util/decorator.py @@ -1,6 +1,7 @@ """Decorator utility functions.""" from typing import Callable, TypeVar -CALLABLE_T = TypeVar('CALLABLE_T', bound=Callable) + +CALLABLE_T = TypeVar('CALLABLE_T', bound=Callable) # noqa pylint: disable=invalid-name class Registry(dict): diff --git a/homeassistant/util/dt.py b/homeassistant/util/dt.py index 06159a944a26..ce6775b9ea7e 100644 --- a/homeassistant/util/dt.py +++ b/homeassistant/util/dt.py @@ -98,7 +98,7 @@ def utc_from_timestamp(timestamp: float) -> dt.datetime: def start_of_local_day(dt_or_d: - Union[dt.date, dt.datetime]=None) -> dt.datetime: + Union[dt.date, dt.datetime] = None) -> dt.datetime: """Return local datetime object of start of day from date or datetime.""" if dt_or_d is None: date = now().date() # type: dt.date diff --git a/homeassistant/util/yaml.py b/homeassistant/util/yaml.py index 40ddfdf7b966..69f83aefad79 100644 --- a/homeassistant/util/yaml.py +++ b/homeassistant/util/yaml.py @@ -24,8 +24,8 @@ _SECRET_NAMESPACE = 'homeassistant' SECRET_YAML = 'secrets.yaml' __SECRET_CACHE = {} # type: Dict[str, JSON_TYPE] -JSON_TYPE = Union[List, Dict, str] -DICT_T = TypeVar('DICT_T', bound=Dict) +JSON_TYPE = Union[List, Dict, str] # pylint: disable=invalid-name +DICT_T = TypeVar('DICT_T', bound=Dict) # pylint: disable=invalid-name class NodeListClass(list): diff --git a/pylintrc b/pylintrc index 1e9e490adfed..00bc6582f3a0 100644 --- a/pylintrc +++ b/pylintrc @@ -11,6 +11,8 @@ # too-few-* - same as too-many-* # abstract-method - with intro of async there are always methods missing # inconsistent-return-statements - doesn't handle raise +# useless-return - https://github.com/PyCQA/pylint/issues/2300 +# not-an-iterable - https://github.com/PyCQA/pylint/issues/2311 disable= abstract-class-little-used, abstract-method, @@ -19,6 +21,7 @@ disable= global-statement, inconsistent-return-statements, locally-disabled, + not-an-iterable, not-context-manager, redefined-variable-type, too-few-public-methods, @@ -30,7 +33,8 @@ disable= too-many-public-methods, too-many-return-statements, too-many-statements, - unused-argument + unused-argument, + useless-return [REPORTS] reports=no diff --git a/requirements_test.txt b/requirements_test.txt index 4d1f86059fcd..225958a722cd 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -8,7 +8,7 @@ flake8==3.5 mock-open==1.3.1 mypy==0.620 pydocstyle==1.1.1 -pylint==1.9.2 +pylint==2.0.1 pytest-aiohttp==0.3.0 pytest-cov==2.5.1 pytest-sugar==0.9.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index be979aa5374d..a38ac24dd7bc 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -9,7 +9,7 @@ flake8==3.5 mock-open==1.3.1 mypy==0.620 pydocstyle==1.1.1 -pylint==1.9.2 +pylint==2.0.1 pytest-aiohttp==0.3.0 pytest-cov==2.5.1 pytest-sugar==0.9.1 From feb8aff46b708062705708dbfee5de012ab3eec8 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 26 Jul 2018 09:38:10 +0200 Subject: [PATCH 084/113] Bump frontend to 20180726.0 --- homeassistant/components/frontend/__init__.py | 4 ++-- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/frontend/__init__.py b/homeassistant/components/frontend/__init__.py index 18704183ec53..5bf2b193957d 100644 --- a/homeassistant/components/frontend/__init__.py +++ b/homeassistant/components/frontend/__init__.py @@ -26,7 +26,7 @@ from homeassistant.helpers.translation import async_get_translations from homeassistant.loader import bind_hass from homeassistant.util.yaml import load_yaml -REQUIREMENTS = ['home-assistant-frontend==20180724.0'] +REQUIREMENTS = ['home-assistant-frontend==20180726.0'] DOMAIN = 'frontend' DEPENDENCIES = ['api', 'websocket_api', 'http', 'system_log', @@ -352,7 +352,7 @@ class IndexView(HomeAssistantView): def get_template(self, latest): """Get template.""" if self.repo_path is not None: - root = self.repo_path + root = os.path.join(self.repo_path, 'hass_frontend') elif latest: import hass_frontend root = hass_frontend.where() diff --git a/requirements_all.txt b/requirements_all.txt index 3d4b48398f9c..44f9602f818f 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -418,7 +418,7 @@ hole==0.3.0 holidays==0.9.5 # homeassistant.components.frontend -home-assistant-frontend==20180724.0 +home-assistant-frontend==20180726.0 # homeassistant.components.homekit_controller # homekit==0.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index a38ac24dd7bc..913117adc2dc 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -81,7 +81,7 @@ hbmqtt==0.9.2 holidays==0.9.5 # homeassistant.components.frontend -home-assistant-frontend==20180724.0 +home-assistant-frontend==20180726.0 # homeassistant.components.homematicip_cloud homematicip==0.9.8 From 974fe4d9236890553cae95758534cb66a41f73f1 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 26 Jul 2018 10:25:57 +0200 Subject: [PATCH 085/113] Fix frontend tests --- tests/components/frontend/test_init.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/components/frontend/test_init.py b/tests/components/frontend/test_init.py index 4a950910809d..dfa67f48614c 100644 --- a/tests/components/frontend/test_init.py +++ b/tests/components/frontend/test_init.py @@ -71,7 +71,7 @@ def test_frontend_and_static(mock_http_client): # Test we can retrieve frontend.js frontendjs = re.search( - r'(?P\/frontend_es5\/app-[A-Za-z0-9]{32}.js)', text) + r'(?P\/frontend_es5\/app-[A-Za-z0-9]{8}.js)', text) assert frontendjs is not None resp = yield from mock_http_client.get(frontendjs.groups(0)[0]) @@ -226,7 +226,7 @@ def test_extra_urls(mock_http_client_with_urls): resp = yield from mock_http_client_with_urls.get('/states?latest') assert resp.status == 200 text = yield from resp.text() - assert text.find('href="https://domain.com/my_extra_url.html"') >= 0 + assert text.find("href='https://domain.com/my_extra_url.html'") >= 0 @asyncio.coroutine @@ -235,7 +235,7 @@ def test_extra_urls_es5(mock_http_client_with_urls): resp = yield from mock_http_client_with_urls.get('/states?es5') assert resp.status == 200 text = yield from resp.text() - assert text.find('href="https://domain.com/my_extra_url_es5.html"') >= 0 + assert text.find("href='https://domain.com/my_extra_url_es5.html'") >= 0 async def test_get_panels(hass, hass_ws_client): From e30510a68860a7a12082498baed40a9b6e5d7e84 Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Thu, 26 Jul 2018 12:31:44 -0600 Subject: [PATCH 086/113] Fixes a bug with showing a subset of Pollen index conditions (#15694) --- homeassistant/components/sensor/pollen.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/sensor/pollen.py b/homeassistant/components/sensor/pollen.py index ea68a902c484..27750c9ac61b 100644 --- a/homeassistant/components/sensor/pollen.py +++ b/homeassistant/components/sensor/pollen.py @@ -307,7 +307,7 @@ class PollenComData: _LOGGER.error('Unable to get allergy history: %s', err) self.data[TYPE_ALLERGY_HISTORIC] = {} - if all(s in self._sensor_types + if any(s in self._sensor_types for s in [TYPE_ALLERGY_TODAY, TYPE_ALLERGY_TOMORROW, TYPE_ALLERGY_YESTERDAY]): try: From 33f3e72dda7df9e265b9ccca747a976ee9f424a2 Mon Sep 17 00:00:00 2001 From: Peter Nijssen Date: Fri, 27 Jul 2018 06:43:20 +0200 Subject: [PATCH 087/113] Add spider power plug component (#15682) * Add spider power plug component * rounding down the numbers * ability to throttle the API * updated to the lastest api * resolved an issue within the API --- homeassistant/components/climate/spider.py | 15 +---- homeassistant/components/spider.py | 19 ++++-- homeassistant/components/switch/spider.py | 77 ++++++++++++++++++++++ requirements_all.txt | 2 +- 4 files changed, 93 insertions(+), 20 deletions(-) create mode 100644 homeassistant/components/switch/spider.py diff --git a/homeassistant/components/climate/spider.py b/homeassistant/components/climate/spider.py index 9a8f7dd7fbd1..a6916b22a25a 100644 --- a/homeassistant/components/climate/spider.py +++ b/homeassistant/components/climate/spider.py @@ -48,7 +48,6 @@ class SpiderThermostat(ClimateDevice): """Initialize the thermostat.""" self.api = api self.thermostat = thermostat - self.master = self.thermostat.has_operation_mode @property def supported_features(self): @@ -125,16 +124,4 @@ class SpiderThermostat(ClimateDevice): def update(self): """Get the latest data.""" - try: - # Only let the master thermostat refresh - # and let the others use the cache - thermostats = self.api.get_thermostats( - force_refresh=self.master) - for thermostat in thermostats: - if thermostat.id == self.unique_id: - self.thermostat = thermostat - break - - except StopIteration: - _LOGGER.error("No data from the Spider API") - return + self.thermostat = self.api.get_thermostat(self.unique_id) diff --git a/homeassistant/components/spider.py b/homeassistant/components/spider.py index 10dbd630b755..359aa029794f 100644 --- a/homeassistant/components/spider.py +++ b/homeassistant/components/spider.py @@ -4,28 +4,35 @@ Support for Spider Smart devices. For more details about this component, please refer to the documentation at https://home-assistant.io/components/spider/ """ +from datetime import timedelta import logging import voluptuous as vol -from homeassistant.const import CONF_PASSWORD, CONF_USERNAME +from homeassistant.const import ( + CONF_PASSWORD, CONF_SCAN_INTERVAL, CONF_USERNAME) import homeassistant.helpers.config_validation as cv from homeassistant.helpers.discovery import load_platform -REQUIREMENTS = ['spiderpy==1.0.7'] +REQUIREMENTS = ['spiderpy==1.1.0'] _LOGGER = logging.getLogger(__name__) DOMAIN = 'spider' SPIDER_COMPONENTS = [ - 'climate' + 'climate', + 'switch' ] +SCAN_INTERVAL = timedelta(seconds=120) + CONFIG_SCHEMA = vol.Schema({ DOMAIN: vol.Schema({ vol.Required(CONF_PASSWORD): cv.string, vol.Required(CONF_USERNAME): cv.string, + vol.Optional(CONF_SCAN_INTERVAL, default=SCAN_INTERVAL): + cv.time_period, }) }, extra=vol.ALLOW_EXTRA) @@ -37,13 +44,15 @@ def setup(hass, config): username = config[DOMAIN][CONF_USERNAME] password = config[DOMAIN][CONF_PASSWORD] + refresh_rate = config[DOMAIN][CONF_SCAN_INTERVAL] try: - api = SpiderApi(username, password) + api = SpiderApi(username, password, refresh_rate.total_seconds()) hass.data[DOMAIN] = { 'controller': api, - 'thermostats': api.get_thermostats() + 'thermostats': api.get_thermostats(), + 'power_plugs': api.get_power_plugs() } for component in SPIDER_COMPONENTS: diff --git a/homeassistant/components/switch/spider.py b/homeassistant/components/switch/spider.py new file mode 100644 index 000000000000..94b7db8f1e5b --- /dev/null +++ b/homeassistant/components/switch/spider.py @@ -0,0 +1,77 @@ +""" +Support for Spider switches. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/switch.spider/ +""" + +import logging + +from homeassistant.components.spider import DOMAIN as SPIDER_DOMAIN +from homeassistant.components.switch import SwitchDevice + +DEPENDENCIES = ['spider'] + +_LOGGER = logging.getLogger(__name__) + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Set up the Spider thermostat.""" + if discovery_info is None: + return + + devices = [SpiderPowerPlug(hass.data[SPIDER_DOMAIN]['controller'], device) + for device in hass.data[SPIDER_DOMAIN]['power_plugs']] + + add_devices(devices, True) + + +class SpiderPowerPlug(SwitchDevice): + """Representation of a Spider Power Plug.""" + + def __init__(self, api, power_plug): + """Initialize the Vera device.""" + self.api = api + self.power_plug = power_plug + + @property + def unique_id(self): + """Return the ID of this switch.""" + return self.power_plug.id + + @property + def name(self): + """Return the name of the switch if any.""" + return self.power_plug.name + + @property + def current_power_w(self): + """Return the current power usage in W.""" + return round(self.power_plug.current_energy_consumption) + + @property + def today_energy_kwh(self): + """Return the current power usage in Kwh.""" + return round(self.power_plug.today_energy_consumption / 1000, 2) + + @property + def is_on(self): + """Return true if switch is on. Standby is on.""" + return self.power_plug.is_on + + @property + def available(self): + """Return true if switch is available.""" + return self.power_plug.is_available + + def turn_on(self, **kwargs): + """Turn device on.""" + self.power_plug.turn_on() + + def turn_off(self, **kwargs): + """Turn device off.""" + self.power_plug.turn_off() + + def update(self): + """Get the latest data.""" + self.power_plug = self.api.get_power_plug(self.unique_id) diff --git a/requirements_all.txt b/requirements_all.txt index 44f9602f818f..3b89719651f8 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1282,7 +1282,7 @@ somecomfort==0.5.2 speedtest-cli==2.0.2 # homeassistant.components.spider -spiderpy==1.0.7 +spiderpy==1.1.0 # homeassistant.components.sensor.spotcrime spotcrime==1.0.3 From a99b4472a8bfa63edf774765db3f1d1aecd57cdf Mon Sep 17 00:00:00 2001 From: Juha Niemi Date: Fri, 27 Jul 2018 12:11:32 +0300 Subject: [PATCH 088/113] Add support for P5 FutureNow light platform (#15662) * Added support for FutureNow light platform and relay/dimmer units * Pinned specific version for requirement * Added support for FutureNow light platform and relay/dimmer units * Added futurenow.py to .coveragerc. * Minor fixes and enhancements as requested in the code review. * Minor fixes and enhancements as requested in the code review. * Use device_config's value directly as it's validated as boolean. * Simplify state check. * Fixed brightness update that was broken in previous commit. --- .coveragerc | 1 + homeassistant/components/light/futurenow.py | 135 ++++++++++++++++++++ requirements_all.txt | 3 + 3 files changed, 139 insertions(+) create mode 100644 homeassistant/components/light/futurenow.py diff --git a/.coveragerc b/.coveragerc index 7e10830d8761..84f4da0e3715 100644 --- a/.coveragerc +++ b/.coveragerc @@ -465,6 +465,7 @@ omit = homeassistant/components/light/decora_wifi.py homeassistant/components/light/decora.py homeassistant/components/light/flux_led.py + homeassistant/components/light/futurenow.py homeassistant/components/light/greenwave.py homeassistant/components/light/hue.py homeassistant/components/light/hyperion.py diff --git a/homeassistant/components/light/futurenow.py b/homeassistant/components/light/futurenow.py new file mode 100644 index 000000000000..56ecaa7b2ca5 --- /dev/null +++ b/homeassistant/components/light/futurenow.py @@ -0,0 +1,135 @@ +""" +Support for FutureNow Ethernet unit outputs as Lights. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/light.futurenow/ +""" + +import logging + +import voluptuous as vol + +from homeassistant.const import ( + CONF_NAME, CONF_HOST, CONF_PORT, CONF_DEVICES) +from homeassistant.components.light import ( + ATTR_BRIGHTNESS, SUPPORT_BRIGHTNESS, Light, + PLATFORM_SCHEMA) +import homeassistant.helpers.config_validation as cv + +REQUIREMENTS = ['pyfnip==0.1'] + +_LOGGER = logging.getLogger(__name__) + +CONF_DRIVER = 'driver' +CONF_DRIVER_FNIP6X10AD = 'FNIP6x10ad' +CONF_DRIVER_FNIP8X10A = 'FNIP8x10a' +CONF_DRIVER_TYPES = [CONF_DRIVER_FNIP6X10AD, CONF_DRIVER_FNIP8X10A] + +DEVICE_SCHEMA = vol.Schema({ + vol.Required(CONF_NAME): cv.string, + vol.Optional('dimmable', default=False): cv.boolean, +}) + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_DRIVER): vol.In(CONF_DRIVER_TYPES), + vol.Required(CONF_HOST): cv.string, + vol.Required(CONF_PORT): cv.port, + vol.Required(CONF_DEVICES): {cv.string: DEVICE_SCHEMA}, +}) + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Set up the light platform for each FutureNow unit.""" + lights = [] + for channel, device_config in config[CONF_DEVICES].items(): + device = {} + device['name'] = device_config[CONF_NAME] + device['dimmable'] = device_config['dimmable'] + device['channel'] = channel + device['driver'] = config[CONF_DRIVER] + device['host'] = config[CONF_HOST] + device['port'] = config[CONF_PORT] + lights.append(FutureNowLight(device)) + + add_devices(lights, True) + + +def to_futurenow_level(level): + """Convert the given HASS light level (0-255) to FutureNow (0-100).""" + return int((level * 100) / 255) + + +def to_hass_level(level): + """Convert the given FutureNow (0-100) light level to HASS (0-255).""" + return int((level * 255) / 100) + + +class FutureNowLight(Light): + """Representation of an FutureNow light.""" + + def __init__(self, device): + """Initialize the light.""" + import pyfnip + + self._name = device['name'] + self._dimmable = device['dimmable'] + self._channel = device['channel'] + self._brightness = None + self._state = None + self._skip_update = False + + if device['driver'] == CONF_DRIVER_FNIP6X10AD: + self._light = pyfnip.FNIP6x2adOutput(device['host'], + device['port'], + self._channel) + if device['driver'] == CONF_DRIVER_FNIP8X10A: + self._light = pyfnip.FNIP8x10aOutput(device['host'], + device['port'], + self._channel) + + @property + def name(self): + """Return the name of the device if any.""" + return self._name + + @property + def is_on(self): + """Return true if device is on.""" + return self._state + + @property + def brightness(self): + """Return the brightness of this light between 0..255.""" + return self._brightness + + @property + def supported_features(self): + """Flag supported features.""" + if self._dimmable: + return SUPPORT_BRIGHTNESS + return 0 + + def turn_on(self, **kwargs): + """Turn the light on.""" + level = kwargs.get(ATTR_BRIGHTNESS, 255) if self._dimmable else 255 + self._light.turn_on(to_futurenow_level(level)) + self._brightness = level + self._state = True + self._skip_update = True + + def turn_off(self, **kwargs): + """Turn the light off.""" + self._light.turn_off() + self._brightness = 0 + self._state = False + self._skip_update = True + + def update(self): + """Fetch new state data for this light.""" + if self._skip_update: + self._skip_update = False + return + + state = int(self._light.is_on()) + self._state = bool(state) + self._brightness = to_hass_level(state) diff --git a/requirements_all.txt b/requirements_all.txt index 3b89719651f8..51ab8100372a 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -825,6 +825,9 @@ pyflexit==0.3 # homeassistant.components.binary_sensor.flic pyflic-homeassistant==0.4.dev0 +# homeassistant.components.light.futurenow +pyfnip==0.1 + # homeassistant.components.fritzbox pyfritzhome==0.3.7 From b2f4bbf93b3ab370b8a89e1bbc4a08b1160a541b Mon Sep 17 00:00:00 2001 From: Jason Hu Date: Fri, 27 Jul 2018 06:53:46 -0700 Subject: [PATCH 089/113] Only log change to use access token warning once (#15690) --- homeassistant/components/http/auth.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/http/auth.py b/homeassistant/components/http/auth.py index 4c71504104e4..77621e3bc7c2 100644 --- a/homeassistant/components/http/auth.py +++ b/homeassistant/components/http/auth.py @@ -20,6 +20,8 @@ _LOGGER = logging.getLogger(__name__) def setup_auth(app, trusted_networks, use_auth, support_legacy=False, api_password=None): """Create auth middleware for the app.""" + old_auth_warning = set() + @middleware async def auth_middleware(request, handler): """Authenticate as middleware.""" @@ -27,8 +29,10 @@ def setup_auth(app, trusted_networks, use_auth, if use_auth and (HTTP_HEADER_HA_AUTH in request.headers or DATA_API_PASSWORD in request.query): - _LOGGER.warning('Please change to use bearer token access %s', - request.path) + if request.path not in old_auth_warning: + _LOGGER.warning('Please change to use bearer token access %s', + request.path) + old_auth_warning.add(request.path) legacy_auth = (not use_auth or support_legacy) and api_password if (hdrs.AUTHORIZATION in request.headers and From cd6544d32a094f727bf338c194830d94c1f24daa Mon Sep 17 00:00:00 2001 From: Richard Orr <27837292+rorr73@users.noreply.github.com> Date: Sat, 28 Jul 2018 01:16:49 +1000 Subject: [PATCH 090/113] Add support for alarm_control_panel to MQTT Discovery. (#15689) --- .../components/alarm_control_panel/mqtt.py | 3 +++ homeassistant/components/mqtt/discovery.py | 4 +++- tests/components/mqtt/test_discovery.py | 22 +++++++++++++++++++ 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/alarm_control_panel/mqtt.py b/homeassistant/components/alarm_control_panel/mqtt.py index c5408304018f..54b85ffbe232 100644 --- a/homeassistant/components/alarm_control_panel/mqtt.py +++ b/homeassistant/components/alarm_control_panel/mqtt.py @@ -49,6 +49,9 @@ PLATFORM_SCHEMA = mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend({ @asyncio.coroutine def async_setup_platform(hass, config, async_add_devices, discovery_info=None): """Set up the MQTT Alarm Control Panel platform.""" + if discovery_info is not None: + config = PLATFORM_SCHEMA(discovery_info) + async_add_devices([MqttAlarm( config.get(CONF_NAME), config.get(CONF_STATE_TOPIC), diff --git a/homeassistant/components/mqtt/discovery.py b/homeassistant/components/mqtt/discovery.py index 1cbe099ddde8..128c45f1311a 100644 --- a/homeassistant/components/mqtt/discovery.py +++ b/homeassistant/components/mqtt/discovery.py @@ -21,7 +21,8 @@ TOPIC_MATCHER = re.compile( SUPPORTED_COMPONENTS = [ 'binary_sensor', 'camera', 'cover', 'fan', - 'light', 'sensor', 'switch', 'lock', 'climate'] + 'light', 'sensor', 'switch', 'lock', 'climate', + 'alarm_control_panel'] ALLOWED_PLATFORMS = { 'binary_sensor': ['mqtt'], @@ -33,6 +34,7 @@ ALLOWED_PLATFORMS = { 'sensor': ['mqtt'], 'switch': ['mqtt'], 'climate': ['mqtt'], + 'alarm_control_panel': ['mqtt'], } ALREADY_DISCOVERED = 'mqtt_discovered_components' diff --git a/tests/components/mqtt/test_discovery.py b/tests/components/mqtt/test_discovery.py index ed6c77f676ce..9e0ef14a3fac 100644 --- a/tests/components/mqtt/test_discovery.py +++ b/tests/components/mqtt/test_discovery.py @@ -124,6 +124,28 @@ def test_discover_climate(hass, mqtt_mock, caplog): assert ('climate', 'bla') in hass.data[ALREADY_DISCOVERED] +@asyncio.coroutine +def test_discover_alarm_control_panel(hass, mqtt_mock, caplog): + """Test discovering an MQTT alarm control panel component.""" + yield from async_start(hass, 'homeassistant', {}) + + data = ( + '{ "name": "AlarmControlPanelTest",' + ' "state_topic": "test_topic",' + ' "command_topic": "test_topic" }' + ) + + async_fire_mqtt_message( + hass, 'homeassistant/alarm_control_panel/bla/config', data) + yield from hass.async_block_till_done() + + state = hass.states.get('alarm_control_panel.AlarmControlPanelTest') + + assert state is not None + assert state.name == 'AlarmControlPanelTest' + assert ('alarm_control_panel', 'bla') in hass.data[ALREADY_DISCOVERED] + + @asyncio.coroutine def test_discovery_incl_nodeid(hass, mqtt_mock, caplog): """Test sending in correct JSON with optional node_id included.""" From 944f4f7c051c21dc00238595e086d1a01f6f235c Mon Sep 17 00:00:00 2001 From: JC Connell Date: Fri, 27 Jul 2018 17:48:56 -0400 Subject: [PATCH 091/113] Add Magicseaweed API support (#15132) * Added support for magicseaweed surf forecasting * Added support for magicseaweed surf forecasting * Added support for magicseaweed surf forecasting * Incorporate @bachya requested changes. * Adding support for magicseaweed package. * Run tests and fix errors. * Incorporate @balloob requested changes. * Attempt to fix pylint error e1101. * Two spaces before inline comments * Add @MartinHjelmare & @balloob requested changes. * Remove MagicSeaweedData object inheritance. * Fix variable logic. --- .coveragerc | 1 + .../components/sensor/magicseaweed.py | 201 ++++++++++++++++++ requirements_all.txt | 3 + 3 files changed, 205 insertions(+) create mode 100644 homeassistant/components/sensor/magicseaweed.py diff --git a/.coveragerc b/.coveragerc index 84f4da0e3715..1d7fbd13f3ea 100644 --- a/.coveragerc +++ b/.coveragerc @@ -662,6 +662,7 @@ omit = homeassistant/components/sensor/loopenergy.py homeassistant/components/sensor/luftdaten.py homeassistant/components/sensor/lyft.py + homeassistant/components/sensor/magicseaweed.py homeassistant/components/sensor/metoffice.py homeassistant/components/sensor/miflora.py homeassistant/components/sensor/mitemp_bt.py diff --git a/homeassistant/components/sensor/magicseaweed.py b/homeassistant/components/sensor/magicseaweed.py new file mode 100644 index 000000000000..02c61024e30c --- /dev/null +++ b/homeassistant/components/sensor/magicseaweed.py @@ -0,0 +1,201 @@ +""" +Support for magicseaweed data from magicseaweed.com. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/sensor.magicseaweed/ +""" +from datetime import timedelta +import logging +import voluptuous as vol + +from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.const import ( + CONF_API_KEY, CONF_NAME, CONF_MONITORED_CONDITIONS, ATTR_ATTRIBUTION) +import homeassistant.helpers.config_validation as cv +import homeassistant.util.dt as dt_util +from homeassistant.helpers.entity import Entity +from homeassistant.util import Throttle + +REQUIREMENTS = ['magicseaweed==1.0.0'] + +_LOGGER = logging.getLogger(__name__) + +CONF_HOURS = 'hours' +CONF_SPOT_ID = 'spot_id' +CONF_UNITS = 'units' +CONF_UPDATE_INTERVAL = 'update_interval' + +DEFAULT_UNIT = 'us' +DEFAULT_NAME = 'MSW' +DEFAULT_ATTRIBUTION = "Data provided by magicseaweed.com" + +ICON = 'mdi:waves' + +HOURS = ['12AM', '3AM', '6AM', '9AM', '12PM', '3PM', '6PM', '9PM'] + +SENSOR_TYPES = { + 'max_breaking_swell': ['Max'], + 'min_breaking_swell': ['Min'], + 'swell_forecast': ['Forecast'], +} + +UNITS = ['eu', 'uk', 'us'] + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_MONITORED_CONDITIONS): + vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]), + vol.Required(CONF_API_KEY): cv.string, + vol.Required(CONF_SPOT_ID): vol.All(cv.ensure_list, [cv.string]), + vol.Optional(CONF_HOURS, default=None): + vol.All(cv.ensure_list, [vol.In(HOURS)]), + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_UNITS): vol.In(UNITS), +}) + +# Return cached results if last scan was less then this time ago. +MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=30) + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Set up the Magicseaweed sensor.""" + name = config.get(CONF_NAME) + spot_id = config[CONF_SPOT_ID] + api_key = config[CONF_API_KEY] + hours = config.get(CONF_HOURS) + + if CONF_UNITS in config: + units = config.get(CONF_UNITS) + elif hass.config.units.is_metric: + units = UNITS[0] + else: + units = UNITS[2] + + forecast_data = MagicSeaweedData( + api_key=api_key, + spot_id=spot_id, + units=units) + forecast_data.update() + + # If connection failed don't setup platform. + if forecast_data.currently is None or forecast_data.hourly is None: + return + + sensors = [] + for variable in config[CONF_MONITORED_CONDITIONS]: + sensors.append(MagicSeaweedSensor(forecast_data, variable, name, + units)) + if 'forecast' not in variable and hours is not None: + for hour in hours: + sensors.append(MagicSeaweedSensor( + forecast_data, variable, name, units, hour)) + add_devices(sensors, True) + + +class MagicSeaweedSensor(Entity): + """Implementation of a MagicSeaweed sensor.""" + + def __init__(self, forecast_data, sensor_type, name, unit_system, + hour=None): + """Initialize the sensor.""" + self.client_name = name + self.data = forecast_data + self.hour = hour + self.type = sensor_type + self._attrs = {ATTR_ATTRIBUTION: DEFAULT_ATTRIBUTION} + self._name = SENSOR_TYPES[sensor_type][0] + self._icon = None + self._state = None + self._unit_system = unit_system + self._unit_of_measurement = None + + @property + def name(self): + """Return the name of the sensor.""" + if self.hour is None and 'forecast' in self.type: + return "{} {}".format(self.client_name, self._name) + if self.hour is None: + return "Current {} {}".format(self.client_name, self._name) + return "{} {} {}".format( + self.hour, self.client_name, self._name) + + @property + def state(self): + """Return the state of the sensor.""" + return self._state + + @property + def unit_system(self): + """Return the unit system of this entity.""" + return self._unit_system + + @property + def unit_of_measurement(self): + """Return the unit of measurement of this entity, if any.""" + return self._unit_of_measurement + + @property + def icon(self): + """Return the entity weather icon, if any.""" + return ICON + + @property + def device_state_attributes(self): + """Return the state attributes.""" + return self._attrs + + def update(self): + """Get the latest data from Magicseaweed and updates the states.""" + self.data.update() + if self.hour is None: + forecast = self.data.currently + else: + forecast = self.data.hourly[self.hour] + + self._unit_of_measurement = forecast.swell_unit + if self.type == 'min_breaking_swell': + self._state = forecast.swell_minBreakingHeight + elif self.type == 'max_breaking_swell': + self._state = forecast.swell_maxBreakingHeight + elif self.type == 'swell_forecast': + summary = "{} - {}".format( + forecast.swell_minBreakingHeight, + forecast.swell_maxBreakingHeight) + self._state = summary + if self.hour is None: + for hour, data in self.data.hourly.items(): + occurs = hour + hr_summary = "{} - {} {}".format( + data.swell_minBreakingHeight, + data.swell_maxBreakingHeight, + data.swell_unit) + self._attrs[occurs] = hr_summary + + if self.type != 'swell_forecast': + self._attrs.update(forecast.attrs) + + +class MagicSeaweedData: + """Get the latest data from MagicSeaweed.""" + + def __init__(self, api_key, spot_id, units): + """Initialize the data object.""" + import magicseaweed + self._msw = magicseaweed.MSW_Forecast(api_key, spot_id, + None, units) + self.currently = None + self.hourly = {} + + # Apply throttling to methods using configured interval + self.update = Throttle(MIN_TIME_BETWEEN_UPDATES)(self._update) + + def _update(self): + """Get the latest data from MagicSeaweed.""" + try: + forecasts = self._msw.get_future() + self.currently = forecasts.data[0] + for forecast in forecasts.data[:8]: + hour = dt_util.utc_from_timestamp( + forecast.localTimestamp).strftime("%-I%p") + self.hourly[hour] = forecast + except ConnectionError: + _LOGGER.error("Unable to retrieve data from Magicseaweed") diff --git a/requirements_all.txt b/requirements_all.txt index 51ab8100372a..b59d874f1cb2 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -532,6 +532,9 @@ lw12==0.9.2 # homeassistant.components.sensor.lyft lyft_rides==0.2 +# homeassistant.components.sensor.magicseaweed +magicseaweed==1.0.0 + # homeassistant.components.matrix matrix-client==0.2.0 From 29e668e887b8955f57938c375b36e1b9f39276a7 Mon Sep 17 00:00:00 2001 From: Alan Fischer Date: Sat, 28 Jul 2018 11:17:04 -0600 Subject: [PATCH 092/113] Upgrade pyvera to 0.2.44 (#15708) --- homeassistant/components/vera.py | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/vera.py b/homeassistant/components/vera.py index 0ab5e7ce39aa..5bc6260c0a71 100644 --- a/homeassistant/components/vera.py +++ b/homeassistant/components/vera.py @@ -19,7 +19,7 @@ from homeassistant.const import ( EVENT_HOMEASSISTANT_STOP, CONF_LIGHTS, CONF_EXCLUDE) from homeassistant.helpers.entity import Entity -REQUIREMENTS = ['pyvera==0.2.43'] +REQUIREMENTS = ['pyvera==0.2.44'] _LOGGER = logging.getLogger(__name__) diff --git a/requirements_all.txt b/requirements_all.txt index b59d874f1cb2..fb209f5e95a6 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1150,7 +1150,7 @@ pyuptimerobot==0.0.5 # pyuserinput==0.1.11 # homeassistant.components.vera -pyvera==0.2.43 +pyvera==0.2.44 # homeassistant.components.switch.vesync pyvesync==0.1.1 From 867f80715e66fc93d961c4baac5b44ad70ad00fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jens=20=C3=98stergaard=20Nielsen?= Date: Sat, 28 Jul 2018 19:37:12 +0200 Subject: [PATCH 093/113] Remove IHC XML Element from discovery data (#15719) --- homeassistant/components/binary_sensor/ihc.py | 4 +--- homeassistant/components/ihc/__init__.py | 5 ++++- homeassistant/components/ihc/ihcdevice.py | 9 ++++----- homeassistant/components/light/ihc.py | 4 +--- homeassistant/components/sensor/ihc.py | 4 +--- homeassistant/components/switch/ihc.py | 4 +--- 6 files changed, 12 insertions(+), 18 deletions(-) diff --git a/homeassistant/components/binary_sensor/ihc.py b/homeassistant/components/binary_sensor/ihc.py index 96efa6e6c196..25435d373fd8 100644 --- a/homeassistant/components/binary_sensor/ihc.py +++ b/homeassistant/components/binary_sensor/ihc.py @@ -3,8 +3,6 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/binary_sensor.ihc/ """ -from xml.etree.ElementTree import Element - import voluptuous as vol from homeassistant.components.binary_sensor import ( @@ -70,7 +68,7 @@ class IHCBinarySensor(IHCDevice, BinarySensorDevice): def __init__(self, ihc_controller, name, ihc_id: int, info: bool, sensor_type: str, inverting: bool, - product: Element = None) -> None: + product=None) -> None: """Initialize the IHC binary sensor.""" super().__init__(ihc_controller, name, ihc_id, info, product) self._state = None diff --git a/homeassistant/components/ihc/__init__.py b/homeassistant/components/ihc/__init__.py index 0c0100bc9f59..672964f765e1 100644 --- a/homeassistant/components/ihc/__init__.py +++ b/homeassistant/components/ihc/__init__.py @@ -167,7 +167,10 @@ def get_discovery_info(component_setup, groups): name = '{}_{}'.format(groupname, ihc_id) device = { 'ihc_id': ihc_id, - 'product': product, + 'product': { + 'name': product.attrib['name'], + 'note': product.attrib['note'], + 'position': product.attrib['position']}, 'product_cfg': product_cfg} discovery_data[name] = device return discovery_data diff --git a/homeassistant/components/ihc/ihcdevice.py b/homeassistant/components/ihc/ihcdevice.py index de6db875def0..2ccca366d901 100644 --- a/homeassistant/components/ihc/ihcdevice.py +++ b/homeassistant/components/ihc/ihcdevice.py @@ -1,6 +1,5 @@ """Implementation of a base class for all IHC devices.""" import asyncio -from xml.etree.ElementTree import Element from homeassistant.helpers.entity import Entity @@ -14,16 +13,16 @@ class IHCDevice(Entity): """ def __init__(self, ihc_controller, name, ihc_id: int, info: bool, - product: Element = None) -> None: + product=None) -> None: """Initialize IHC attributes.""" self.ihc_controller = ihc_controller self._name = name self.ihc_id = ihc_id self.info = info if product: - self.ihc_name = product.attrib['name'] - self.ihc_note = product.attrib['note'] - self.ihc_position = product.attrib['position'] + self.ihc_name = product['name'] + self.ihc_note = product['note'] + self.ihc_position = product['position'] else: self.ihc_name = '' self.ihc_note = '' diff --git a/homeassistant/components/light/ihc.py b/homeassistant/components/light/ihc.py index c9ceda8651ac..5a7e85d50dc5 100644 --- a/homeassistant/components/light/ihc.py +++ b/homeassistant/components/light/ihc.py @@ -3,8 +3,6 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/light.ihc/ """ -from xml.etree.ElementTree import Element - import voluptuous as vol from homeassistant.components.ihc import ( @@ -64,7 +62,7 @@ class IhcLight(IHCDevice, Light): """ def __init__(self, ihc_controller, name, ihc_id: int, info: bool, - dimmable=False, product: Element = None) -> None: + dimmable=False, product=None) -> None: """Initialize the light.""" super().__init__(ihc_controller, name, ihc_id, info, product) self._brightness = 0 diff --git a/homeassistant/components/sensor/ihc.py b/homeassistant/components/sensor/ihc.py index b30a242c17c1..2dcf2c3f7bee 100644 --- a/homeassistant/components/sensor/ihc.py +++ b/homeassistant/components/sensor/ihc.py @@ -3,8 +3,6 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.ihc/ """ -from xml.etree.ElementTree import Element - import voluptuous as vol from homeassistant.components.ihc import ( @@ -62,7 +60,7 @@ class IHCSensor(IHCDevice, Entity): """Implementation of the IHC sensor.""" def __init__(self, ihc_controller, name, ihc_id: int, info: bool, - unit, product: Element = None) -> None: + unit, product=None) -> None: """Initialize the IHC sensor.""" super().__init__(ihc_controller, name, ihc_id, info, product) self._state = None diff --git a/homeassistant/components/switch/ihc.py b/homeassistant/components/switch/ihc.py index 499a4ca53a71..3f4617846930 100644 --- a/homeassistant/components/switch/ihc.py +++ b/homeassistant/components/switch/ihc.py @@ -3,8 +3,6 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/switch.ihc/ """ -from xml.etree.ElementTree import Element - import voluptuous as vol from homeassistant.components.ihc import ( @@ -53,7 +51,7 @@ class IHCSwitch(IHCDevice, SwitchDevice): """IHC Switch.""" def __init__(self, ihc_controller, name: str, ihc_id: int, - info: bool, product: Element = None) -> None: + info: bool, product=None) -> None: """Initialize the IHC switch.""" super().__init__(ihc_controller, name, ihc_id, product) self._state = False From c7f4bdafc047f8d8a2430cd235bfa087b5395d1d Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 29 Jul 2018 01:53:37 +0100 Subject: [PATCH 094/113] Context (#15674) * Add context * Add context to switch/light services * Test set_state API * Lint * Fix tests * Do not include context yet in comparison * Do not pass in loop * Fix Z-Wave tests * Add websocket test without user --- homeassistant/components/api.py | 9 +- homeassistant/components/http/view.py | 10 +- homeassistant/components/light/__init__.py | 4 +- homeassistant/components/switch/__init__.py | 3 +- homeassistant/components/websocket_api.py | 19 ++- homeassistant/const.py | 3 - homeassistant/core.py | 178 ++++++++++++-------- homeassistant/helpers/entity.py | 4 +- tests/common.py | 2 +- tests/components/light/test_init.py | 27 ++- tests/components/switch/test_init.py | 25 ++- tests/components/test_api.py | 57 +++++++ tests/components/test_mqtt_eventstream.py | 6 +- tests/components/test_websocket_api.py | 93 +++++++++- tests/components/zwave/test_init.py | 8 +- tests/test_core.py | 24 +-- 16 files changed, 363 insertions(+), 109 deletions(-) diff --git a/homeassistant/components/api.py b/homeassistant/components/api.py index b80a57160616..de28eeff5ca5 100644 --- a/homeassistant/components/api.py +++ b/homeassistant/components/api.py @@ -220,7 +220,8 @@ class APIEntityStateView(HomeAssistantView): is_new_state = hass.states.get(entity_id) is None # Write state - hass.states.async_set(entity_id, new_state, attributes, force_update) + hass.states.async_set(entity_id, new_state, attributes, force_update, + self.context(request)) # Read the state back for our response status_code = HTTP_CREATED if is_new_state else 200 @@ -279,7 +280,8 @@ class APIEventView(HomeAssistantView): event_data[key] = state request.app['hass'].bus.async_fire( - event_type, event_data, ha.EventOrigin.remote) + event_type, event_data, ha.EventOrigin.remote, + self.context(request)) return self.json_message("Event {} fired.".format(event_type)) @@ -316,7 +318,8 @@ class APIDomainServicesView(HomeAssistantView): "Data should be valid JSON.", HTTP_BAD_REQUEST) with AsyncTrackStates(hass) as changed_states: - await hass.services.async_call(domain, service, data, True) + await hass.services.async_call( + domain, service, data, True, self.context(request)) return self.json(changed_states) diff --git a/homeassistant/components/http/view.py b/homeassistant/components/http/view.py index 2b6c2a113c4d..22ef34de54ad 100644 --- a/homeassistant/components/http/view.py +++ b/homeassistant/components/http/view.py @@ -13,7 +13,7 @@ from aiohttp.web_exceptions import HTTPUnauthorized, HTTPInternalServerError import homeassistant.remote as rem from homeassistant.components.http.ban import process_success_login -from homeassistant.core import is_callback +from homeassistant.core import Context, is_callback from homeassistant.const import CONTENT_TYPE_JSON from .const import KEY_AUTHENTICATED, KEY_REAL_IP @@ -32,6 +32,14 @@ class HomeAssistantView: cors_allowed = False # pylint: disable=no-self-use + def context(self, request): + """Generate a context from a request.""" + user = request.get('hass_user') + if user is None: + return Context() + + return Context(user_id=user.id) + def json(self, result, status_code=200, headers=None): """Return a JSON response.""" try: diff --git a/homeassistant/components/light/__init__.py b/homeassistant/components/light/__init__.py index 58991a8e5059..8b4b21377116 100644 --- a/homeassistant/components/light/__init__.py +++ b/homeassistant/components/light/__init__.py @@ -359,7 +359,9 @@ async def async_setup(hass, config): if not light.should_poll: continue - update_tasks.append(light.async_update_ha_state(True)) + + update_tasks.append( + light.async_update_ha_state(True, service.context)) if update_tasks: await asyncio.wait(update_tasks, loop=hass.loop) diff --git a/homeassistant/components/switch/__init__.py b/homeassistant/components/switch/__init__.py index b9ee8126ed3c..cb69240ee73d 100644 --- a/homeassistant/components/switch/__init__.py +++ b/homeassistant/components/switch/__init__.py @@ -114,7 +114,8 @@ async def async_setup(hass, config): if not switch.should_poll: continue - update_tasks.append(switch.async_update_ha_state(True)) + update_tasks.append( + switch.async_update_ha_state(True, service.context)) if update_tasks: await asyncio.wait(update_tasks, loop=hass.loop) diff --git a/homeassistant/components/websocket_api.py b/homeassistant/components/websocket_api.py index 98e3057338a6..ed478550c7a7 100644 --- a/homeassistant/components/websocket_api.py +++ b/homeassistant/components/websocket_api.py @@ -18,7 +18,7 @@ from voluptuous.humanize import humanize_error from homeassistant.const import ( MATCH_ALL, EVENT_TIME_CHANGED, EVENT_HOMEASSISTANT_STOP, __version__) -from homeassistant.core import callback +from homeassistant.core import Context, callback from homeassistant.loader import bind_hass from homeassistant.remote import JSONEncoder from homeassistant.helpers import config_validation as cv @@ -262,6 +262,18 @@ class ActiveConnection: self._handle_task = None self._writer_task = None + @property + def user(self): + """Return the user associated with the connection.""" + return self.request.get('hass_user') + + def context(self, msg): + """Return a context.""" + user = self.user + if user is None: + return Context() + return Context(user_id=user.id) + def debug(self, message1, message2=''): """Print a debug message.""" _LOGGER.debug("WS %s: %s %s", id(self.wsock), message1, message2) @@ -287,7 +299,7 @@ class ActiveConnection: @callback def send_message_outside(self, message): - """Send a message to the client outside of the main task. + """Send a message to the client. Closes connection if the client is not reading the messages. @@ -508,7 +520,8 @@ def handle_call_service(hass, connection, msg): async def call_service_helper(msg): """Call a service and fire complete message.""" await hass.services.async_call( - msg['domain'], msg['service'], msg.get('service_data'), True) + msg['domain'], msg['service'], msg.get('service_data'), True, + connection.context(msg)) connection.send_message_outside(result_message(msg['id'])) hass.async_add_job(call_service_helper(msg)) diff --git a/homeassistant/const.py b/homeassistant/const.py index a84c278350f5..33a00b655339 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -224,9 +224,6 @@ ATTR_ID = 'id' # Name ATTR_NAME = 'name' -# Data for a SERVICE_EXECUTED event -ATTR_SERVICE_CALL_ID = 'service_call_id' - # Contains one string or a list of strings, each being an entity id ATTR_ENTITY_ID = 'entity_id' diff --git a/homeassistant/core.py b/homeassistant/core.py index 828dfc24d6ca..b17df2c11fec 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -15,6 +15,7 @@ import re import sys import threading from time import monotonic +import uuid from types import MappingProxyType # pylint: disable=unused-import @@ -23,12 +24,13 @@ from typing import ( # NOQA TYPE_CHECKING, Awaitable, Iterator) from async_timeout import timeout +import attr import voluptuous as vol from voluptuous.humanize import humanize_error from homeassistant.const import ( ATTR_DOMAIN, ATTR_FRIENDLY_NAME, ATTR_NOW, ATTR_SERVICE, - ATTR_SERVICE_CALL_ID, ATTR_SERVICE_DATA, EVENT_CALL_SERVICE, + ATTR_SERVICE_DATA, EVENT_CALL_SERVICE, EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, EVENT_SERVICE_EXECUTED, EVENT_SERVICE_REGISTERED, EVENT_STATE_CHANGED, EVENT_TIME_CHANGED, MATCH_ALL, EVENT_HOMEASSISTANT_CLOSE, @@ -191,7 +193,7 @@ class HomeAssistant: try: # Only block for EVENT_HOMEASSISTANT_START listener self.async_stop_track_tasks() - with timeout(TIMEOUT_EVENT_START, loop=self.loop): + with timeout(TIMEOUT_EVENT_START): await self.async_block_till_done() except asyncio.TimeoutError: _LOGGER.warning( @@ -201,7 +203,7 @@ class HomeAssistant: ', '.join(self.config.components)) # Allow automations to set up the start triggers before changing state - await asyncio.sleep(0, loop=self.loop) + await asyncio.sleep(0) self.state = CoreState.running _async_create_timer(self) @@ -307,16 +309,16 @@ class HomeAssistant: async def async_block_till_done(self) -> None: """Block till all pending work is done.""" # To flush out any call_soon_threadsafe - await asyncio.sleep(0, loop=self.loop) + await asyncio.sleep(0) while self._pending_tasks: pending = [task for task in self._pending_tasks if not task.done()] self._pending_tasks.clear() if pending: - await asyncio.wait(pending, loop=self.loop) + await asyncio.wait(pending) else: - await asyncio.sleep(0, loop=self.loop) + await asyncio.sleep(0) def stop(self) -> None: """Stop Home Assistant and shuts down all threads.""" @@ -343,6 +345,27 @@ class HomeAssistant: self.loop.stop() +@attr.s(slots=True, frozen=True) +class Context: + """The context that triggered something.""" + + user_id = attr.ib( + type=str, + default=None, + ) + id = attr.ib( + type=str, + default=attr.Factory(lambda: uuid.uuid4().hex), + ) + + def as_dict(self) -> dict: + """Return a dictionary representation of the context.""" + return { + 'id': self.id, + 'user_id': self.user_id, + } + + class EventOrigin(enum.Enum): """Represent the origin of an event.""" @@ -357,16 +380,18 @@ class EventOrigin(enum.Enum): class Event: """Representation of an event within the bus.""" - __slots__ = ['event_type', 'data', 'origin', 'time_fired'] + __slots__ = ['event_type', 'data', 'origin', 'time_fired', 'context'] def __init__(self, event_type: str, data: Optional[Dict] = None, origin: EventOrigin = EventOrigin.local, - time_fired: Optional[int] = None) -> None: + time_fired: Optional[int] = None, + context: Optional[Context] = None) -> None: """Initialize a new event.""" self.event_type = event_type self.data = data or {} self.origin = origin self.time_fired = time_fired or dt_util.utcnow() + self.context = context or Context() def as_dict(self) -> Dict: """Create a dict representation of this Event. @@ -378,6 +403,7 @@ class Event: 'data': dict(self.data), 'origin': str(self.origin), 'time_fired': self.time_fired, + 'context': self.context.as_dict() } def __repr__(self) -> str: @@ -425,14 +451,16 @@ class EventBus: ).result() def fire(self, event_type: str, event_data: Optional[Dict] = None, - origin: EventOrigin = EventOrigin.local) -> None: + origin: EventOrigin = EventOrigin.local, + context: Optional[Context] = None) -> None: """Fire an event.""" self._hass.loop.call_soon_threadsafe( - self.async_fire, event_type, event_data, origin) + self.async_fire, event_type, event_data, origin, context) @callback def async_fire(self, event_type: str, event_data: Optional[Dict] = None, - origin: EventOrigin = EventOrigin.local) -> None: + origin: EventOrigin = EventOrigin.local, + context: Optional[Context] = None) -> None: """Fire an event. This method must be run in the event loop. @@ -445,7 +473,7 @@ class EventBus: event_type != EVENT_HOMEASSISTANT_CLOSE): listeners = match_all_listeners + listeners - event = Event(event_type, event_data, origin) + event = Event(event_type, event_data, origin, None, context) if event_type != EVENT_TIME_CHANGED: _LOGGER.info("Bus:Handling %s", event) @@ -569,15 +597,17 @@ class State: attributes: extra information on entity and state last_changed: last time the state was changed, not the attributes. last_updated: last time this object was updated. + context: Context in which it was created """ __slots__ = ['entity_id', 'state', 'attributes', - 'last_changed', 'last_updated'] + 'last_changed', 'last_updated', 'context'] def __init__(self, entity_id: str, state: Any, attributes: Optional[Dict] = None, last_changed: Optional[datetime.datetime] = None, - last_updated: Optional[datetime.datetime] = None) -> None: + last_updated: Optional[datetime.datetime] = None, + context: Optional[Context] = None) -> None: """Initialize a new state.""" state = str(state) @@ -596,6 +626,7 @@ class State: self.attributes = MappingProxyType(attributes or {}) self.last_updated = last_updated or dt_util.utcnow() self.last_changed = last_changed or self.last_updated + self.context = context or Context() @property def domain(self) -> str: @@ -626,7 +657,8 @@ class State: 'state': self.state, 'attributes': dict(self.attributes), 'last_changed': self.last_changed, - 'last_updated': self.last_updated} + 'last_updated': self.last_updated, + 'context': self.context.as_dict()} @classmethod def from_dict(cls, json_dict: Dict) -> Any: @@ -650,8 +682,13 @@ class State: if isinstance(last_updated, str): last_updated = dt_util.parse_datetime(last_updated) + context = json_dict.get('context') + if context: + context = Context(**context) + return cls(json_dict['entity_id'], json_dict['state'], - json_dict.get('attributes'), last_changed, last_updated) + json_dict.get('attributes'), last_changed, last_updated, + context) def __eq__(self, other: Any) -> bool: """Return the comparison of the state.""" @@ -662,11 +699,11 @@ class State: def __repr__(self) -> str: """Return the representation of the states.""" - attr = "; {}".format(util.repr_helper(self.attributes)) \ - if self.attributes else "" + attrs = "; {}".format(util.repr_helper(self.attributes)) \ + if self.attributes else "" return "".format( - self.entity_id, self.state, attr, + self.entity_id, self.state, attrs, dt_util.as_local(self.last_changed).isoformat()) @@ -761,7 +798,8 @@ class StateMachine: def set(self, entity_id: str, new_state: Any, attributes: Optional[Dict] = None, - force_update: bool = False) -> None: + force_update: bool = False, + context: Optional[Context] = None) -> None: """Set the state of an entity, add entity if it does not exist. Attributes is an optional dict to specify attributes of this state. @@ -772,12 +810,14 @@ class StateMachine: run_callback_threadsafe( self._loop, self.async_set, entity_id, new_state, attributes, force_update, + context, ).result() @callback def async_set(self, entity_id: str, new_state: Any, attributes: Optional[Dict] = None, - force_update: bool = False) -> None: + force_update: bool = False, + context: Optional[Context] = None) -> None: """Set the state of an entity, add entity if it does not exist. Attributes is an optional dict to specify attributes of this state. @@ -804,13 +844,17 @@ class StateMachine: if same_state and same_attr: return - state = State(entity_id, new_state, attributes, last_changed) + if context is None: + context = Context() + + state = State(entity_id, new_state, attributes, last_changed, None, + context) self._states[entity_id] = state self._bus.async_fire(EVENT_STATE_CHANGED, { 'entity_id': entity_id, 'old_state': old_state, 'new_state': state, - }) + }, EventOrigin.local, context) class Service: @@ -818,7 +862,8 @@ class Service: __slots__ = ['func', 'schema', 'is_callback', 'is_coroutinefunction'] - def __init__(self, func: Callable, schema: Optional[vol.Schema]) -> None: + def __init__(self, func: Callable, schema: Optional[vol.Schema], + context: Optional[Context] = None) -> None: """Initialize a service.""" self.func = func self.schema = schema @@ -829,23 +874,25 @@ class Service: class ServiceCall: """Representation of a call to a service.""" - __slots__ = ['domain', 'service', 'data', 'call_id'] + __slots__ = ['domain', 'service', 'data', 'context'] def __init__(self, domain: str, service: str, data: Optional[Dict] = None, - call_id: Optional[str] = None) -> None: + context: Optional[Context] = None) -> None: """Initialize a service call.""" self.domain = domain.lower() self.service = service.lower() self.data = MappingProxyType(data or {}) - self.call_id = call_id + self.context = context or Context() def __repr__(self) -> str: """Return the representation of the service.""" if self.data: - return "".format( - self.domain, self.service, util.repr_helper(self.data)) + return "".format( + self.domain, self.service, self.context.id, + util.repr_helper(self.data)) - return "".format(self.domain, self.service) + return "".format( + self.domain, self.service, self.context.id) class ServiceRegistry: @@ -857,15 +904,6 @@ class ServiceRegistry: self._hass = hass self._async_unsub_call_event = None # type: Optional[CALLBACK_TYPE] - def _gen_unique_id() -> Iterator[str]: - cur_id = 1 - while True: - yield '{}-{}'.format(id(self), cur_id) - cur_id += 1 - - gen = _gen_unique_id() - self._generate_unique_id = lambda: next(gen) - @property def services(self) -> Dict[str, Dict[str, Service]]: """Return dictionary with per domain a list of available services.""" @@ -957,7 +995,8 @@ class ServiceRegistry: def call(self, domain: str, service: str, service_data: Optional[Dict] = None, - blocking: bool = False) -> Optional[bool]: + blocking: bool = False, + context: Optional[Context] = None) -> Optional[bool]: """ Call a service. @@ -975,13 +1014,14 @@ class ServiceRegistry: the keys ATTR_DOMAIN and ATTR_SERVICE in your service_data. """ return run_coroutine_threadsafe( # type: ignore - self.async_call(domain, service, service_data, blocking), + self.async_call(domain, service, service_data, blocking, context), self._hass.loop ).result() async def async_call(self, domain: str, service: str, service_data: Optional[Dict] = None, - blocking: bool = False) -> Optional[bool]: + blocking: bool = False, + context: Optional[Context] = None) -> Optional[bool]: """ Call a service. @@ -1000,44 +1040,42 @@ class ServiceRegistry: This method is a coroutine. """ - call_id = self._generate_unique_id() - + context = context or Context() event_data = { ATTR_DOMAIN: domain.lower(), ATTR_SERVICE: service.lower(), ATTR_SERVICE_DATA: service_data, - ATTR_SERVICE_CALL_ID: call_id, } - if blocking: - fut = asyncio.Future(loop=self._hass.loop) # type: asyncio.Future + if not blocking: + self._hass.bus.async_fire( + EVENT_CALL_SERVICE, event_data, EventOrigin.local, context) + return None - @callback - def service_executed(event: Event) -> None: - """Handle an executed service.""" - if event.data[ATTR_SERVICE_CALL_ID] == call_id: - fut.set_result(True) + fut = asyncio.Future() # type: asyncio.Future - unsub = self._hass.bus.async_listen( - EVENT_SERVICE_EXECUTED, service_executed) + @callback + def service_executed(event: Event) -> None: + """Handle an executed service.""" + if event.context == context: + fut.set_result(True) - self._hass.bus.async_fire(EVENT_CALL_SERVICE, event_data) + unsub = self._hass.bus.async_listen( + EVENT_SERVICE_EXECUTED, service_executed) - done, _ = await asyncio.wait( - [fut], loop=self._hass.loop, timeout=SERVICE_CALL_LIMIT) - success = bool(done) - unsub() - return success + self._hass.bus.async_fire(EVENT_CALL_SERVICE, event_data, + EventOrigin.local, context) - self._hass.bus.async_fire(EVENT_CALL_SERVICE, event_data) - return None + done, _ = await asyncio.wait([fut], timeout=SERVICE_CALL_LIMIT) + success = bool(done) + unsub() + return success async def _event_to_service_call(self, event: Event) -> None: """Handle the SERVICE_CALLED events from the EventBus.""" service_data = event.data.get(ATTR_SERVICE_DATA) or {} domain = event.data.get(ATTR_DOMAIN).lower() # type: ignore service = event.data.get(ATTR_SERVICE).lower() # type: ignore - call_id = event.data.get(ATTR_SERVICE_CALL_ID) if not self.has_service(domain, service): if event.origin == EventOrigin.local: @@ -1049,16 +1087,13 @@ class ServiceRegistry: def fire_service_executed() -> None: """Fire service executed event.""" - if not call_id: - return - - data = {ATTR_SERVICE_CALL_ID: call_id} - if (service_handler.is_coroutinefunction or service_handler.is_callback): - self._hass.bus.async_fire(EVENT_SERVICE_EXECUTED, data) + self._hass.bus.async_fire(EVENT_SERVICE_EXECUTED, {}, + EventOrigin.local, event.context) else: - self._hass.bus.fire(EVENT_SERVICE_EXECUTED, data) + self._hass.bus.fire(EVENT_SERVICE_EXECUTED, {}, + EventOrigin.local, event.context) try: if service_handler.schema: @@ -1069,7 +1104,8 @@ class ServiceRegistry: fire_service_executed() return - service_call = ServiceCall(domain, service, service_data, call_id) + service_call = ServiceCall( + domain, service, service_data, event.context) try: if service_handler.is_callback: diff --git a/homeassistant/helpers/entity.py b/homeassistant/helpers/entity.py index f466664fc61f..c356c266db69 100644 --- a/homeassistant/helpers/entity.py +++ b/homeassistant/helpers/entity.py @@ -179,7 +179,7 @@ class Entity: # produce undesirable effects in the entity's operation. @asyncio.coroutine - def async_update_ha_state(self, force_refresh=False): + def async_update_ha_state(self, force_refresh=False, context=None): """Update Home Assistant with current state of entity. If force_refresh == True will update entity before setting state. @@ -279,7 +279,7 @@ class Entity: pass self.hass.states.async_set( - self.entity_id, state, attr, self.force_update) + self.entity_id, state, attr, self.force_update, context) def schedule_update_ha_state(self, force_refresh=False): """Schedule an update ha state change task. diff --git a/tests/common.py b/tests/common.py index 314799b185bf..5567a431e58f 100644 --- a/tests/common.py +++ b/tests/common.py @@ -187,7 +187,7 @@ def async_mock_service(hass, domain, service, schema=None): """Set up a fake service & return a calls log list to this service.""" calls = [] - @asyncio.coroutine + @ha.callback def mock_service_log(call): # pylint: disable=unnecessary-lambda """Mock service call.""" calls.append(call) diff --git a/tests/components/light/test_init.py b/tests/components/light/test_init.py index 74f8c85b532e..4d779eef4612 100644 --- a/tests/components/light/test_init.py +++ b/tests/components/light/test_init.py @@ -5,12 +5,12 @@ import unittest.mock as mock import os from io import StringIO -from homeassistant.setup import setup_component -import homeassistant.loader as loader +from homeassistant import core, loader +from homeassistant.setup import setup_component, async_setup_component from homeassistant.const import ( ATTR_ENTITY_ID, STATE_ON, STATE_OFF, CONF_PLATFORM, SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_TOGGLE, ATTR_SUPPORTED_FEATURES) -import homeassistant.components.light as light +from homeassistant.components import light from homeassistant.helpers.intent import IntentHandleError from tests.common import ( @@ -475,3 +475,24 @@ async def test_intent_set_color_and_brightness(hass): assert call.data.get(ATTR_ENTITY_ID) == 'light.hello_2' assert call.data.get(light.ATTR_RGB_COLOR) == (0, 0, 255) assert call.data.get(light.ATTR_BRIGHTNESS_PCT) == 20 + + +async def test_light_context(hass): + """Test that light context works.""" + assert await async_setup_component(hass, 'light', { + 'light': { + 'platform': 'test' + } + }) + + state = hass.states.get('light.ceiling') + assert state is not None + + await hass.services.async_call('light', 'toggle', { + 'entity_id': state.entity_id, + }, True, core.Context(user_id='abcd')) + + state2 = hass.states.get('light.ceiling') + assert state2 is not None + assert state.state != state2.state + assert state2.context.user_id == 'abcd' diff --git a/tests/components/switch/test_init.py b/tests/components/switch/test_init.py index d679aa2c8273..55e442992948 100644 --- a/tests/components/switch/test_init.py +++ b/tests/components/switch/test_init.py @@ -2,8 +2,8 @@ # pylint: disable=protected-access import unittest -from homeassistant.setup import setup_component -from homeassistant import loader +from homeassistant.setup import setup_component, async_setup_component +from homeassistant import core, loader from homeassistant.components import switch from homeassistant.const import STATE_ON, STATE_OFF, CONF_PLATFORM @@ -91,3 +91,24 @@ class TestSwitch(unittest.TestCase): '{} 2'.format(switch.DOMAIN): {CONF_PLATFORM: 'test2'}, } )) + + +async def test_switch_context(hass): + """Test that switch context works.""" + assert await async_setup_component(hass, 'switch', { + 'switch': { + 'platform': 'test' + } + }) + + state = hass.states.get('switch.ac') + assert state is not None + + await hass.services.async_call('switch', 'toggle', { + 'entity_id': state.entity_id, + }, True, core.Context(user_id='abcd')) + + state2 = hass.states.get('switch.ac') + assert state2 is not None + assert state.state != state2.state + assert state2.context.user_id == 'abcd' diff --git a/tests/components/test_api.py b/tests/components/test_api.py index f53010ef27fa..09dc27e97c1f 100644 --- a/tests/components/test_api.py +++ b/tests/components/test_api.py @@ -12,6 +12,8 @@ from homeassistant.bootstrap import DATA_LOGGING import homeassistant.core as ha from homeassistant.setup import async_setup_component +from tests.common import async_mock_service + @pytest.fixture def mock_api_client(hass, aiohttp_client): @@ -429,3 +431,58 @@ async def test_api_error_log(hass, aiohttp_client): assert mock_file.mock_calls[0][1][0] == hass.data[DATA_LOGGING] assert resp.status == 200 assert await resp.text() == 'Hello' + + +async def test_api_fire_event_context(hass, mock_api_client, + hass_access_token): + """Test if the API sets right context if we fire an event.""" + test_value = [] + + @ha.callback + def listener(event): + """Helper method that will verify our event got called.""" + test_value.append(event) + + hass.bus.async_listen("test.event", listener) + + await mock_api_client.post( + const.URL_API_EVENTS_EVENT.format("test.event"), + headers={ + 'authorization': 'Bearer {}'.format(hass_access_token.token) + }) + await hass.async_block_till_done() + + assert len(test_value) == 1 + assert test_value[0].context.user_id == \ + hass_access_token.refresh_token.user.id + + +async def test_api_call_service_context(hass, mock_api_client, + hass_access_token): + """Test if the API sets right context if we call a service.""" + calls = async_mock_service(hass, 'test_domain', 'test_service') + + await mock_api_client.post( + '/api/services/test_domain/test_service', + headers={ + 'authorization': 'Bearer {}'.format(hass_access_token.token) + }) + await hass.async_block_till_done() + + assert len(calls) == 1 + assert calls[0].context.user_id == hass_access_token.refresh_token.user.id + + +async def test_api_set_state_context(hass, mock_api_client, hass_access_token): + """Test if the API sets right context if we set state.""" + await mock_api_client.post( + '/api/states/light.kitchen', + json={ + 'state': 'on' + }, + headers={ + 'authorization': 'Bearer {}'.format(hass_access_token.token) + }) + + state = hass.states.get('light.kitchen') + assert state.context.user_id == hass_access_token.refresh_token.user.id diff --git a/tests/components/test_mqtt_eventstream.py b/tests/components/test_mqtt_eventstream.py index 389248179803..8da1311c87dc 100644 --- a/tests/components/test_mqtt_eventstream.py +++ b/tests/components/test_mqtt_eventstream.py @@ -104,12 +104,14 @@ class TestMqttEventStream: "state": "on", "entity_id": e_id, "attributes": {}, - "last_changed": now.isoformat() + "last_changed": now.isoformat(), } event['event_data'] = {"new_state": new_state, "entity_id": e_id} # Verify that the message received was that expected - assert json.loads(msg) == event + result = json.loads(msg) + result['event_data']['new_state'].pop('context') + assert result == event @patch('homeassistant.components.mqtt.async_publish') def test_time_event_does_not_send_message(self, mock_pub): diff --git a/tests/components/test_websocket_api.py b/tests/components/test_websocket_api.py index dc1688bae166..1fac1af9f64e 100644 --- a/tests/components/test_websocket_api.py +++ b/tests/components/test_websocket_api.py @@ -10,7 +10,7 @@ from homeassistant.core import callback from homeassistant.components import websocket_api as wapi from homeassistant.setup import async_setup_component -from tests.common import mock_coro +from tests.common import mock_coro, async_mock_service API_PASSWORD = 'test1234' @@ -443,3 +443,94 @@ async def test_auth_with_invalid_token(hass, aiohttp_client): auth_msg = await ws.receive_json() assert auth_msg['type'] == wapi.TYPE_AUTH_INVALID + + +async def test_call_service_context_with_user(hass, aiohttp_client, + hass_access_token): + """Test that the user is set in the service call context.""" + assert await async_setup_component(hass, 'websocket_api', { + 'http': { + 'api_password': API_PASSWORD + } + }) + + calls = async_mock_service(hass, 'domain_test', 'test_service') + client = await aiohttp_client(hass.http.app) + + async with client.ws_connect(wapi.URL) as ws: + with patch('homeassistant.auth.AuthManager.active') as auth_active: + auth_active.return_value = True + auth_msg = await ws.receive_json() + assert auth_msg['type'] == wapi.TYPE_AUTH_REQUIRED + + await ws.send_json({ + 'type': wapi.TYPE_AUTH, + 'access_token': hass_access_token.token + }) + + auth_msg = await ws.receive_json() + assert auth_msg['type'] == wapi.TYPE_AUTH_OK + + await ws.send_json({ + 'id': 5, + 'type': wapi.TYPE_CALL_SERVICE, + 'domain': 'domain_test', + 'service': 'test_service', + 'service_data': { + 'hello': 'world' + } + }) + + msg = await ws.receive_json() + assert msg['success'] + + assert len(calls) == 1 + call = calls[0] + assert call.domain == 'domain_test' + assert call.service == 'test_service' + assert call.data == {'hello': 'world'} + assert call.context.user_id == hass_access_token.refresh_token.user.id + + +async def test_call_service_context_no_user(hass, aiohttp_client): + """Test that connection without user sets context.""" + assert await async_setup_component(hass, 'websocket_api', { + 'http': { + 'api_password': API_PASSWORD + } + }) + + calls = async_mock_service(hass, 'domain_test', 'test_service') + client = await aiohttp_client(hass.http.app) + + async with client.ws_connect(wapi.URL) as ws: + auth_msg = await ws.receive_json() + assert auth_msg['type'] == wapi.TYPE_AUTH_REQUIRED + + await ws.send_json({ + 'type': wapi.TYPE_AUTH, + 'api_password': API_PASSWORD + }) + + auth_msg = await ws.receive_json() + assert auth_msg['type'] == wapi.TYPE_AUTH_OK + + await ws.send_json({ + 'id': 5, + 'type': wapi.TYPE_CALL_SERVICE, + 'domain': 'domain_test', + 'service': 'test_service', + 'service_data': { + 'hello': 'world' + } + }) + + msg = await ws.receive_json() + assert msg['success'] + + assert len(calls) == 1 + call = calls[0] + assert call.domain == 'domain_test' + assert call.service == 'test_service' + assert call.data == {'hello': 'world'} + assert call.context.user_id is None diff --git a/tests/components/zwave/test_init.py b/tests/components/zwave/test_init.py index e608dcccaba1..39abf6f588f7 100644 --- a/tests/components/zwave/test_init.py +++ b/tests/components/zwave/test_init.py @@ -163,10 +163,10 @@ def test_zwave_ready_wait(hass, mock_openzwave): asyncio_sleep = asyncio.sleep @asyncio.coroutine - def sleep(duration, loop): + def sleep(duration, loop=None): if duration > 0: sleeps.append(duration) - yield from asyncio_sleep(0, loop=loop) + yield from asyncio_sleep(0) with patch('homeassistant.components.zwave.dt_util.utcnow', new=utcnow): with patch('asyncio.sleep', new=sleep): @@ -248,10 +248,10 @@ async def test_unparsed_node_discovery(hass, mock_openzwave): asyncio_sleep = asyncio.sleep - async def sleep(duration, loop): + async def sleep(duration, loop=None): if duration > 0: sleeps.append(duration) - await asyncio_sleep(0, loop=loop) + await asyncio_sleep(0) with patch('homeassistant.components.zwave.dt_util.utcnow', new=utcnow): with patch('asyncio.sleep', new=sleep): diff --git a/tests/test_core.py b/tests/test_core.py index 7633c820d2da..9de801e0bb4f 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -277,6 +277,10 @@ class TestEvent(unittest.TestCase): 'data': data, 'origin': 'LOCAL', 'time_fired': now, + 'context': { + 'id': event.context.id, + 'user_id': event.context.user_id, + }, } self.assertEqual(expected, event.as_dict()) @@ -598,18 +602,16 @@ class TestStateMachine(unittest.TestCase): self.assertEqual(1, len(events)) -class TestServiceCall(unittest.TestCase): - """Test ServiceCall class.""" +def test_service_call_repr(): + """Test ServiceCall repr.""" + call = ha.ServiceCall('homeassistant', 'start') + assert str(call) == \ + "".format(call.context.id) - def test_repr(self): - """Test repr method.""" - self.assertEqual( - "", - str(ha.ServiceCall('homeassistant', 'start'))) - - self.assertEqual( - "", - str(ha.ServiceCall('homeassistant', 'start', {"fast": "yes"}))) + call2 = ha.ServiceCall('homeassistant', 'start', {'fast': 'yes'}) + assert str(call2) == \ + "".format( + call2.context.id) class TestServiceRegistry(unittest.TestCase): From 93d6fb8c608dc952db89591b4186fc1b163fd063 Mon Sep 17 00:00:00 2001 From: Jason Hu Date: Sat, 28 Jul 2018 17:54:26 -0700 Subject: [PATCH 095/113] Break up components/auth (#15713) --- homeassistant/components/auth/__init__.py | 170 +---------------- homeassistant/components/auth/login_flow.py | 172 ++++++++++++++++++ ..._init_login_flow.py => test_login_flow.py} | 29 +++ 3 files changed, 208 insertions(+), 163 deletions(-) create mode 100644 homeassistant/components/auth/login_flow.py rename tests/components/auth/{test_init_login_flow.py => test_login_flow.py} (66%) diff --git a/homeassistant/components/auth/__init__.py b/homeassistant/components/auth/__init__.py index f5b5ce62f8f2..0b2b4fb1a2ec 100644 --- a/homeassistant/components/auth/__init__.py +++ b/homeassistant/components/auth/__init__.py @@ -1,62 +1,5 @@ """Component to allow users to login and get tokens. -All requests will require passing in a valid client ID and secret via HTTP -Basic Auth. - -# GET /auth/providers - -Return a list of auth providers. Example: - -[ - { - "name": "Local", - "id": null, - "type": "local_provider", - } -] - -# POST /auth/login_flow - -Create a login flow. Will return the first step of the flow. - -Pass in parameter 'handler' to specify the auth provider to use. Auth providers -are identified by type and id. - -{ - "handler": ["local_provider", null] -} - -Return value will be a step in a data entry flow. See the docs for data entry -flow for details. - -{ - "data_schema": [ - {"name": "username", "type": "string"}, - {"name": "password", "type": "string"} - ], - "errors": {}, - "flow_id": "8f7e42faab604bcab7ac43c44ca34d58", - "handler": ["insecure_example", null], - "step_id": "init", - "type": "form" -} - -# POST /auth/login_flow/{flow_id} - -Progress the flow. Most flows will be 1 page, but could optionally add extra -login challenges, like TFA. Once the flow has finished, the returned step will -have type "create_entry" and "result" key will contain an authorization code. - -{ - "flow_id": "8f7e42faab604bcab7ac43c44ca34d58", - "handler": ["insecure_example", null], - "result": "411ee2f916e648d691e937ae9344681e", - "source": "user", - "title": "Example", - "type": "create_entry", - "version": 1 -} - # POST /auth/token This is an OAuth2 endpoint for granting tokens. We currently support the grant @@ -102,26 +45,20 @@ a limited expiration. "token_type": "Bearer" } """ -from datetime import timedelta import logging import uuid +from datetime import timedelta -import aiohttp.web import voluptuous as vol -from homeassistant import data_entry_flow -from homeassistant.components.http.ban import process_wrong_login, \ - log_invalid_auth -from homeassistant.core import callback -from homeassistant.helpers.data_entry_flow import ( - FlowManagerIndexView, FlowManagerResourceView) from homeassistant.components import websocket_api -from homeassistant.components.http.view import HomeAssistantView +from homeassistant.components.http.ban import log_invalid_auth from homeassistant.components.http.data_validator import RequestDataValidator +from homeassistant.components.http.view import HomeAssistantView +from homeassistant.core import callback from homeassistant.util import dt as dt_util - from . import indieauth - +from . import login_flow DOMAIN = 'auth' DEPENDENCIES = ['http'] @@ -138,10 +75,6 @@ async def async_setup(hass, config): """Component to allow users to login.""" store_credentials, retrieve_credentials = _create_cred_store() - hass.http.register_view(AuthProvidersView) - hass.http.register_view(LoginFlowIndexView(hass.auth.login_flow)) - hass.http.register_view( - LoginFlowResourceView(hass.auth.login_flow, store_credentials)) hass.http.register_view(GrantTokenView(retrieve_credentials)) hass.http.register_view(LinkUserView(retrieve_credentials)) @@ -150,100 +83,11 @@ async def async_setup(hass, config): SCHEMA_WS_CURRENT_USER ) + await login_flow.async_setup(hass, store_credentials) + return True -class AuthProvidersView(HomeAssistantView): - """View to get available auth providers.""" - - url = '/auth/providers' - name = 'api:auth:providers' - requires_auth = False - - async def get(self, request): - """Get available auth providers.""" - return self.json([{ - 'name': provider.name, - 'id': provider.id, - 'type': provider.type, - } for provider in request.app['hass'].auth.auth_providers]) - - -class LoginFlowIndexView(FlowManagerIndexView): - """View to create a config flow.""" - - url = '/auth/login_flow' - name = 'api:auth:login_flow' - requires_auth = False - - async def get(self, request): - """Do not allow index of flows in progress.""" - return aiohttp.web.Response(status=405) - - @RequestDataValidator(vol.Schema({ - vol.Required('client_id'): str, - vol.Required('handler'): vol.Any(str, list), - vol.Required('redirect_uri'): str, - })) - @log_invalid_auth - async def post(self, request, data): - """Create a new login flow.""" - if not indieauth.verify_redirect_uri(data['client_id'], - data['redirect_uri']): - return self.json_message('invalid client id or redirect uri', 400) - - # pylint: disable=no-value-for-parameter - return await super().post(request) - - -class LoginFlowResourceView(FlowManagerResourceView): - """View to interact with the flow manager.""" - - url = '/auth/login_flow/{flow_id}' - name = 'api:auth:login_flow:resource' - requires_auth = False - - def __init__(self, flow_mgr, store_credentials): - """Initialize the login flow resource view.""" - super().__init__(flow_mgr) - self._store_credentials = store_credentials - - async def get(self, request, flow_id): - """Do not allow getting status of a flow in progress.""" - return self.json_message('Invalid flow specified', 404) - - @RequestDataValidator(vol.Schema({ - 'client_id': str - }, extra=vol.ALLOW_EXTRA)) - @log_invalid_auth - async def post(self, request, flow_id, data): - """Handle progressing a login flow request.""" - client_id = data.pop('client_id') - - if not indieauth.verify_client_id(client_id): - return self.json_message('Invalid client id', 400) - - try: - result = await self._flow_mgr.async_configure(flow_id, data) - except data_entry_flow.UnknownFlow: - return self.json_message('Invalid flow specified', 404) - except vol.Invalid: - return self.json_message('User input malformed', 400) - - if result['type'] != data_entry_flow.RESULT_TYPE_CREATE_ENTRY: - # @log_invalid_auth does not work here since it returns HTTP 200 - # need manually log failed login attempts - if result['errors'] is not None and \ - result['errors'].get('base') == 'invalid_auth': - await process_wrong_login(request) - return self.json(self._prepare_result_json(result)) - - result.pop('data') - result['result'] = self._store_credentials(client_id, result['result']) - - return self.json(result) - - class GrantTokenView(HomeAssistantView): """View to grant tokens.""" diff --git a/homeassistant/components/auth/login_flow.py b/homeassistant/components/auth/login_flow.py new file mode 100644 index 000000000000..6d1b6cf4ecfd --- /dev/null +++ b/homeassistant/components/auth/login_flow.py @@ -0,0 +1,172 @@ +"""HTTP views handle login flow. + +# GET /auth/providers + +Return a list of auth providers. Example: + +[ + { + "name": "Local", + "id": null, + "type": "local_provider", + } +] + + +# POST /auth/login_flow + +Create a login flow. Will return the first step of the flow. + +Pass in parameter 'client_id' and 'redirect_url' validate by indieauth. + +Pass in parameter 'handler' to specify the auth provider to use. Auth providers +are identified by type and id. + +{ + "client_id": "https://hassbian.local:8123/", + "handler": ["local_provider", null], + "redirect_url": "https://hassbian.local:8123/" +} + +Return value will be a step in a data entry flow. See the docs for data entry +flow for details. + +{ + "data_schema": [ + {"name": "username", "type": "string"}, + {"name": "password", "type": "string"} + ], + "errors": {}, + "flow_id": "8f7e42faab604bcab7ac43c44ca34d58", + "handler": ["insecure_example", null], + "step_id": "init", + "type": "form" +} + + +# POST /auth/login_flow/{flow_id} + +Progress the flow. Most flows will be 1 page, but could optionally add extra +login challenges, like TFA. Once the flow has finished, the returned step will +have type "create_entry" and "result" key will contain an authorization code. + +{ + "flow_id": "8f7e42faab604bcab7ac43c44ca34d58", + "handler": ["insecure_example", null], + "result": "411ee2f916e648d691e937ae9344681e", + "source": "user", + "title": "Example", + "type": "create_entry", + "version": 1 +} +""" +import aiohttp.web +import voluptuous as vol + +from homeassistant import data_entry_flow +from homeassistant.components.http.ban import process_wrong_login, \ + log_invalid_auth +from homeassistant.components.http.data_validator import RequestDataValidator +from homeassistant.components.http.view import HomeAssistantView +from homeassistant.helpers.data_entry_flow import ( + FlowManagerIndexView, FlowManagerResourceView) +from . import indieauth + + +async def async_setup(hass, store_credentials): + """Component to allow users to login.""" + hass.http.register_view(AuthProvidersView) + hass.http.register_view(LoginFlowIndexView(hass.auth.login_flow)) + hass.http.register_view( + LoginFlowResourceView(hass.auth.login_flow, store_credentials)) + + +class AuthProvidersView(HomeAssistantView): + """View to get available auth providers.""" + + url = '/auth/providers' + name = 'api:auth:providers' + requires_auth = False + + async def get(self, request): + """Get available auth providers.""" + return self.json([{ + 'name': provider.name, + 'id': provider.id, + 'type': provider.type, + } for provider in request.app['hass'].auth.auth_providers]) + + +class LoginFlowIndexView(FlowManagerIndexView): + """View to create a config flow.""" + + url = '/auth/login_flow' + name = 'api:auth:login_flow' + requires_auth = False + + async def get(self, request): + """Do not allow index of flows in progress.""" + return aiohttp.web.Response(status=405) + + @RequestDataValidator(vol.Schema({ + vol.Required('client_id'): str, + vol.Required('handler'): vol.Any(str, list), + vol.Required('redirect_uri'): str, + })) + @log_invalid_auth + async def post(self, request, data): + """Create a new login flow.""" + if not indieauth.verify_redirect_uri(data['client_id'], + data['redirect_uri']): + return self.json_message('invalid client id or redirect uri', 400) + + # pylint: disable=no-value-for-parameter + return await super().post(request) + + +class LoginFlowResourceView(FlowManagerResourceView): + """View to interact with the flow manager.""" + + url = '/auth/login_flow/{flow_id}' + name = 'api:auth:login_flow:resource' + requires_auth = False + + def __init__(self, flow_mgr, store_credentials): + """Initialize the login flow resource view.""" + super().__init__(flow_mgr) + self._store_credentials = store_credentials + + async def get(self, request, flow_id): + """Do not allow getting status of a flow in progress.""" + return self.json_message('Invalid flow specified', 404) + + @RequestDataValidator(vol.Schema({ + 'client_id': str + }, extra=vol.ALLOW_EXTRA)) + @log_invalid_auth + async def post(self, request, flow_id, data): + """Handle progressing a login flow request.""" + client_id = data.pop('client_id') + + if not indieauth.verify_client_id(client_id): + return self.json_message('Invalid client id', 400) + + try: + result = await self._flow_mgr.async_configure(flow_id, data) + except data_entry_flow.UnknownFlow: + return self.json_message('Invalid flow specified', 404) + except vol.Invalid: + return self.json_message('User input malformed', 400) + + if result['type'] != data_entry_flow.RESULT_TYPE_CREATE_ENTRY: + # @log_invalid_auth does not work here since it returns HTTP 200 + # need manually log failed login attempts + if result['errors'] is not None and \ + result['errors'].get('base') == 'invalid_auth': + await process_wrong_login(request) + return self.json(self._prepare_result_json(result)) + + result.pop('data') + result['result'] = self._store_credentials(client_id, result['result']) + + return self.json(result) diff --git a/tests/components/auth/test_init_login_flow.py b/tests/components/auth/test_login_flow.py similarity index 66% rename from tests/components/auth/test_init_login_flow.py rename to tests/components/auth/test_login_flow.py index 50bd03d6cedb..8b6108067c52 100644 --- a/tests/components/auth/test_init_login_flow.py +++ b/tests/components/auth/test_login_flow.py @@ -8,6 +8,7 @@ async def test_fetch_auth_providers(hass, aiohttp_client): """Test fetching auth providers.""" client = await async_setup_auth(hass, aiohttp_client) resp = await client.get('/auth/providers') + assert resp.status == 200 assert await resp.json() == [{ 'name': 'Example', 'type': 'insecure_example', @@ -60,3 +61,31 @@ async def test_invalid_username_password(hass, aiohttp_client): assert step['step_id'] == 'init' assert step['errors']['base'] == 'invalid_auth' + + +async def test_login_exist_user(hass, aiohttp_client): + """Test logging in with exist user.""" + client = await async_setup_auth(hass, aiohttp_client, setup_api=True) + cred = await hass.auth.auth_providers[0].async_get_or_create_credentials( + {'username': 'test-user'}) + await hass.auth.async_get_or_create_user(cred) + + resp = await client.post('/auth/login_flow', json={ + 'client_id': CLIENT_ID, + 'handler': ['insecure_example', None], + 'redirect_uri': CLIENT_REDIRECT_URI, + }) + assert resp.status == 200 + step = await resp.json() + + resp = await client.post( + '/auth/login_flow/{}'.format(step['flow_id']), json={ + 'client_id': CLIENT_ID, + 'username': 'test-user', + 'password': 'test-pass', + }) + + assert resp.status == 200 + step = await resp.json() + assert step['type'] == 'create_entry' + assert len(step['result']) > 1 From a2b793c61b411df86348a674a7ca6d1e42972ea5 Mon Sep 17 00:00:00 2001 From: Jonathan Keljo Date: Sat, 28 Jul 2018 22:34:43 -0700 Subject: [PATCH 096/113] Add a component for Sisyphus Kinetic Art Tables (#14472) * Add a component for Sisyphus Kinetic Art Tables The [Sisyphus Kinetic Art Table](https://sisyphus-industries.com/) uses a steel ball to draw intricate patterns in sand, thrown into sharp relief by a ring of LED lights around the outside. This component enables basic control of these tables through Home Assistant. * Fix lints * Docstrings, other lints * More lints * Yet more. * Feedback * Lint * Missed one piece of feedback * - Use async_added_to_hass in media player - async_schedule_update_ha_state in listeners - constants for supported features - subscripting for required keys - asyncio.wait - update to sisyphus-control with passed-in session * Update requirements * lint --- .coveragerc | 3 + homeassistant/components/light/sisyphus.py | 78 +++++++ .../components/media_player/sisyphus.py | 197 ++++++++++++++++++ homeassistant/components/sisyphus.py | 84 ++++++++ requirements_all.txt | 3 + 5 files changed, 365 insertions(+) create mode 100644 homeassistant/components/light/sisyphus.py create mode 100644 homeassistant/components/media_player/sisyphus.py create mode 100644 homeassistant/components/sisyphus.py diff --git a/.coveragerc b/.coveragerc index 1d7fbd13f3ea..a33b996cb861 100644 --- a/.coveragerc +++ b/.coveragerc @@ -251,6 +251,9 @@ omit = homeassistant/components/scsgate.py homeassistant/components/*/scsgate.py + homeassistant/components/sisyphus.py + homeassistant/components/*/sisyphus.py + homeassistant/components/skybell.py homeassistant/components/*/skybell.py diff --git a/homeassistant/components/light/sisyphus.py b/homeassistant/components/light/sisyphus.py new file mode 100644 index 000000000000..ded787163171 --- /dev/null +++ b/homeassistant/components/light/sisyphus.py @@ -0,0 +1,78 @@ +""" +Support for the light on the Sisyphus Kinetic Art Table. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/light.sisyphus/ +""" +import logging + +from homeassistant.const import CONF_NAME +from homeassistant.components.light import SUPPORT_BRIGHTNESS, Light +from homeassistant.components.sisyphus import DATA_SISYPHUS + +_LOGGER = logging.getLogger(__name__) + +DEPENDENCIES = ['sisyphus'] + +SUPPORTED_FEATURES = SUPPORT_BRIGHTNESS + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Set up a single Sisyphus table.""" + name = discovery_info[CONF_NAME] + add_devices( + [SisyphusLight(name, hass.data[DATA_SISYPHUS][name])], + update_before_add=True) + + +class SisyphusLight(Light): + """Represents a Sisyphus table as a light.""" + + def __init__(self, name, table): + """ + Constructor. + + :param name: name of the table + :param table: sisyphus-control Table object + """ + self._name = name + self._table = table + + async def async_added_to_hass(self): + """Add listeners after this object has been initialized.""" + self._table.add_listener( + lambda: self.async_schedule_update_ha_state(False)) + + @property + def name(self): + """Return the ame of the table.""" + return self._name + + @property + def is_on(self): + """Return True if the table is on.""" + return not self._table.is_sleeping + + @property + def brightness(self): + """Return the current brightness of the table's ring light.""" + return self._table.brightness * 255 + + @property + def supported_features(self): + """Return the features supported by the table; i.e. brightness.""" + return SUPPORTED_FEATURES + + async def async_turn_off(self, **kwargs): + """Put the table to sleep.""" + await self._table.sleep() + _LOGGER.debug("Sisyphus table %s: sleep") + + async def async_turn_on(self, **kwargs): + """Wake up the table if necessary, optionally changes brightness.""" + if not self.is_on: + await self._table.wakeup() + _LOGGER.debug("Sisyphus table %s: wakeup") + + if "brightness" in kwargs: + await self._table.set_brightness(kwargs["brightness"] / 255.0) diff --git a/homeassistant/components/media_player/sisyphus.py b/homeassistant/components/media_player/sisyphus.py new file mode 100644 index 000000000000..9a94da158a19 --- /dev/null +++ b/homeassistant/components/media_player/sisyphus.py @@ -0,0 +1,197 @@ +""" +Support for track controls on the Sisyphus Kinetic Art Table. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/media_player.sisyphus/ +""" +import logging + +from homeassistant.components.media_player import ( + SUPPORT_NEXT_TRACK, + SUPPORT_PAUSE, + SUPPORT_PLAY, + SUPPORT_PREVIOUS_TRACK, + SUPPORT_SHUFFLE_SET, + SUPPORT_TURN_OFF, + SUPPORT_TURN_ON, + SUPPORT_VOLUME_MUTE, + SUPPORT_VOLUME_SET, + MediaPlayerDevice) +from homeassistant.components.sisyphus import DATA_SISYPHUS +from homeassistant.const import CONF_HOST, CONF_NAME, STATE_PLAYING, \ + STATE_PAUSED, STATE_IDLE, STATE_OFF + +_LOGGER = logging.getLogger(__name__) + +DEPENDENCIES = ['sisyphus'] + +MEDIA_TYPE_TRACK = "sisyphus_track" + +SUPPORTED_FEATURES = SUPPORT_VOLUME_MUTE \ + | SUPPORT_VOLUME_SET \ + | SUPPORT_TURN_OFF \ + | SUPPORT_TURN_ON \ + | SUPPORT_PAUSE \ + | SUPPORT_SHUFFLE_SET \ + | SUPPORT_PREVIOUS_TRACK \ + | SUPPORT_NEXT_TRACK \ + | SUPPORT_PLAY + + +# pylint: disable=unused-argument +def setup_platform(hass, config, add_devices, discovery_info=None): + """Set up a media player entity for a Sisyphus table.""" + name = discovery_info[CONF_NAME] + host = discovery_info[CONF_HOST] + add_devices( + [SisyphusPlayer(name, host, hass.data[DATA_SISYPHUS][name])], + update_before_add=True) + + +class SisyphusPlayer(MediaPlayerDevice): + """Represents a single Sisyphus table as a media player device.""" + + def __init__(self, name, host, table): + """ + Constructor. + + :param name: name of the table + :param host: hostname or ip address + :param table: sisyphus-control Table object + """ + self._name = name + self._host = host + self._table = table + + async def async_added_to_hass(self): + """Add listeners after this object has been initialized.""" + self._table.add_listener( + lambda: self.async_schedule_update_ha_state(False)) + + @property + def name(self): + """Return the name of the table.""" + return self._name + + @property + def state(self): + """Return the current state of the table; sleeping maps to off.""" + if self._table.state in ["homing", "playing"]: + return STATE_PLAYING + if self._table.state == "paused": + if self._table.is_sleeping: + return STATE_OFF + + return STATE_PAUSED + if self._table.state == "waiting": + return STATE_IDLE + + return None + + @property + def volume_level(self): + """Return the current playback speed (0..1).""" + return self._table.speed + + @property + def shuffle(self): + """Return True if the current playlist is in shuffle mode.""" + return self._table.is_shuffle + + async def async_set_shuffle(self, shuffle): + """ + Change the shuffle mode of the current playlist. + + :param shuffle: True to shuffle, False not to + """ + await self._table.set_shuffle(shuffle) + + @property + def media_playlist(self): + """Return the name of the current playlist.""" + return self._table.active_playlist.name \ + if self._table.active_playlist \ + else None + + @property + def media_title(self): + """Return the title of the current track.""" + return self._table.active_track.name \ + if self._table.active_track \ + else None + + @property + def media_content_type(self): + """Return the content type currently playing; i.e. a Sisyphus track.""" + return MEDIA_TYPE_TRACK + + @property + def media_content_id(self): + """Return the track ID of the current track.""" + return self._table.active_track.id \ + if self._table.active_track \ + else None + + @property + def supported_features(self): + """Return the features supported by this table.""" + return SUPPORTED_FEATURES + + @property + def media_image_url(self): + """Return the URL for a thumbnail image of the current track.""" + from sisyphus_control import Track + if self._table.active_track: + return self._table.active_track.get_thumbnail_url( + Track.ThumbnailSize.LARGE) + + return super.media_image_url() + + async def async_turn_on(self): + """Wake up a sleeping table.""" + await self._table.wakeup() + + async def async_turn_off(self): + """Put the table to sleep.""" + await self._table.sleep() + + async def async_volume_down(self): + """Slow down playback.""" + await self._table.set_speed(max(0, self._table.speed - 0.1)) + + async def async_volume_up(self): + """Speed up playback.""" + await self._table.set_speed(min(1.0, self._table.speed + 0.1)) + + async def async_set_volume_level(self, volume): + """Set playback speed (0..1).""" + await self._table.set_speed(volume) + + async def async_media_play(self): + """Start playing.""" + await self._table.play() + + async def async_media_pause(self): + """Pause.""" + await self._table.pause() + + async def async_media_next_track(self): + """Skip to next track.""" + cur_track_index = self._get_current_track_index() + + await self._table.active_playlist.play( + self._table.active_playlist.tracks[cur_track_index + 1]) + + async def async_media_previous_track(self): + """Skip to previous track.""" + cur_track_index = self._get_current_track_index() + + await self._table.active_playlist.play( + self._table.active_playlist.tracks[cur_track_index - 1]) + + def _get_current_track_index(self): + for index, track in enumerate(self._table.active_playlist.tracks): + if track.id == self._table.active_track.id: + return index + + return -1 diff --git a/homeassistant/components/sisyphus.py b/homeassistant/components/sisyphus.py new file mode 100644 index 000000000000..dc9f9cc4c257 --- /dev/null +++ b/homeassistant/components/sisyphus.py @@ -0,0 +1,84 @@ +""" +Support for controlling Sisyphus Kinetic Art Tables. + +For more details about this component, please refer to the documentation at +https://home-assistant.io/components/sisyphus/ +""" +import asyncio +import logging + +import voluptuous as vol + +from homeassistant.const import ( + CONF_HOST, + CONF_NAME, + EVENT_HOMEASSISTANT_STOP +) +from homeassistant.helpers.aiohttp_client import async_get_clientsession +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.discovery import async_load_platform + +REQUIREMENTS = ['sisyphus-control==2.1'] + +_LOGGER = logging.getLogger(__name__) + +DATA_SISYPHUS = 'sisyphus' +DOMAIN = 'sisyphus' + +AUTODETECT_SCHEMA = vol.Schema({}) + +TABLE_SCHEMA = vol.Schema({ + vol.Required(CONF_NAME): cv.string, + vol.Required(CONF_HOST): cv.string, +}) + +TABLES_SCHEMA = vol.Schema([TABLE_SCHEMA]) + +CONFIG_SCHEMA = vol.Schema({ + DOMAIN: vol.Any(AUTODETECT_SCHEMA, TABLES_SCHEMA), +}, extra=vol.ALLOW_EXTRA) + + +async def async_setup(hass, config): + """Set up the sisyphus component.""" + from sisyphus_control import Table + tables = hass.data.setdefault(DATA_SISYPHUS, {}) + table_configs = config.get(DOMAIN) + session = async_get_clientsession(hass) + + async def add_table(host, name=None): + """Add platforms for a single table with the given hostname.""" + table = await Table.connect(host, session) + if name is None: + name = table.name + tables[name] = table + _LOGGER.debug("Connected to %s at %s", name, host) + + hass.async_add_job(async_load_platform( + hass, 'light', DOMAIN, { + CONF_NAME: name, + }, config + )) + hass.async_add_job(async_load_platform( + hass, 'media_player', DOMAIN, { + CONF_NAME: name, + CONF_HOST: host, + }, config + )) + + if isinstance(table_configs, dict): # AUTODETECT_SCHEMA + for ip_address in await Table.find_table_ips(session): + await add_table(ip_address) + else: # TABLES_SCHEMA + for conf in table_configs: + await add_table(conf[CONF_HOST], conf[CONF_NAME]) + + async def close_tables(*args): + """Close all table objects.""" + tasks = [table.close() for table in tables.values()] + if tasks: + await asyncio.wait(tasks) + + hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, close_tables) + + return True diff --git a/requirements_all.txt b/requirements_all.txt index fb209f5e95a6..c951601ca3e9 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1252,6 +1252,9 @@ simplepush==1.1.4 # homeassistant.components.alarm_control_panel.simplisafe simplisafe-python==2.0.2 +# homeassistant.components.sisyphus +sisyphus-control==2.1 + # homeassistant.components.skybell skybellpy==0.1.2 From 1d68f4e2799e5ee6b20c9141f3f0e7ebf3611672 Mon Sep 17 00:00:00 2001 From: Alexander Hardwicke Date: Sun, 29 Jul 2018 08:37:34 +0200 Subject: [PATCH 097/113] Command Line Sensor - json_attributes (#15679) * Add tests to command_line for json_attrs * Add json_attrs to command_line * Remove whitespace on blank line * Stick to <80 row length * Use collections.Mapping, not dict * Rename *attrs to *attributes * Remove extraneous + for string concat * Test multiple keys * Add test Makes sure the sensor's attributes don't contain a value for a missing key, even if we want that key. * Test that unwanted keys are skipped * Remove additional log line * Update tests for log changes * Fix ordering --- .../components/sensor/command_line.py | 60 +++++++--- tests/components/sensor/test_command_line.py | 109 +++++++++++++++++- 2 files changed, 153 insertions(+), 16 deletions(-) diff --git a/homeassistant/components/sensor/command_line.py b/homeassistant/components/sensor/command_line.py index 1db7c58d3281..846604a9ff58 100644 --- a/homeassistant/components/sensor/command_line.py +++ b/homeassistant/components/sensor/command_line.py @@ -4,39 +4,42 @@ Allows to configure custom shell commands to turn a value for a sensor. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.command_line/ """ -import logging -import subprocess -import shlex - +import collections from datetime import timedelta +import json +import logging +import shlex +import subprocess import voluptuous as vol -import homeassistant.helpers.config_validation as cv from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.helpers import template -from homeassistant.exceptions import TemplateError from homeassistant.const import ( - CONF_NAME, CONF_VALUE_TEMPLATE, CONF_UNIT_OF_MEASUREMENT, CONF_COMMAND, + CONF_COMMAND, CONF_NAME, CONF_UNIT_OF_MEASUREMENT, CONF_VALUE_TEMPLATE, STATE_UNKNOWN) +from homeassistant.exceptions import TemplateError +from homeassistant.helpers import template +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) +CONF_COMMAND_TIMEOUT = 'command_timeout' +CONF_JSON_ATTRIBUTES = 'json_attributes' + DEFAULT_NAME = 'Command Sensor' +DEFAULT_TIMEOUT = 15 SCAN_INTERVAL = timedelta(seconds=60) -CONF_COMMAND_TIMEOUT = 'command_timeout' -DEFAULT_TIMEOUT = 15 - PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_COMMAND): cv.string, + vol.Optional(CONF_COMMAND_TIMEOUT, default=DEFAULT_TIMEOUT): + cv.positive_int, + vol.Optional(CONF_JSON_ATTRIBUTES): cv.ensure_list_csv, vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, vol.Optional(CONF_UNIT_OF_MEASUREMENT): cv.string, vol.Optional(CONF_VALUE_TEMPLATE): cv.template, - vol.Optional( - CONF_COMMAND_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int, }) @@ -49,18 +52,23 @@ def setup_platform(hass, config, add_devices, discovery_info=None): command_timeout = config.get(CONF_COMMAND_TIMEOUT) if value_template is not None: value_template.hass = hass + json_attributes = config.get(CONF_JSON_ATTRIBUTES) data = CommandSensorData(hass, command, command_timeout) - add_devices([CommandSensor(hass, data, name, unit, value_template)], True) + add_devices([CommandSensor( + hass, data, name, unit, value_template, json_attributes)], True) class CommandSensor(Entity): """Representation of a sensor that is using shell commands.""" - def __init__(self, hass, data, name, unit_of_measurement, value_template): + def __init__(self, hass, data, name, unit_of_measurement, value_template, + json_attributes): """Initialize the sensor.""" self._hass = hass self.data = data + self._attributes = None + self._json_attributes = json_attributes self._name = name self._state = None self._unit_of_measurement = unit_of_measurement @@ -81,11 +89,33 @@ class CommandSensor(Entity): """Return the state of the device.""" return self._state + @property + def device_state_attributes(self): + """Return the state attributes.""" + return self._attributes + def update(self): """Get the latest data and updates the state.""" self.data.update() value = self.data.value + if self._json_attributes: + self._attributes = {} + if value: + try: + json_dict = json.loads(value) + if isinstance(json_dict, collections.Mapping): + self._attributes = {k: json_dict[k] for k in + self._json_attributes + if k in json_dict} + else: + _LOGGER.warning("JSON result was not a dictionary") + except ValueError: + _LOGGER.warning( + "Unable to parse output as JSON: %s", value) + else: + _LOGGER.warning("Empty reply found when expecting JSON data") + if value is None: value = STATE_UNKNOWN elif self._value_template is not None: diff --git a/tests/components/sensor/test_command_line.py b/tests/components/sensor/test_command_line.py index 3104ce897a1c..808f8cff6a14 100644 --- a/tests/components/sensor/test_command_line.py +++ b/tests/components/sensor/test_command_line.py @@ -1,5 +1,6 @@ """The tests for the Command line sensor platform.""" import unittest +from unittest.mock import patch from homeassistant.helpers.template import Template from homeassistant.components.sensor import command_line @@ -17,6 +18,10 @@ class TestCommandSensorSensor(unittest.TestCase): """Stop everything that was started.""" self.hass.stop() + def update_side_effect(self, data): + """Side effect function for mocking CommandSensorData.update().""" + self.commandline.data = data + def test_setup(self): """Test sensor setup.""" config = {'name': 'Test', @@ -46,7 +51,7 @@ class TestCommandSensorSensor(unittest.TestCase): entity = command_line.CommandSensor( self.hass, data, 'test', 'in', - Template('{{ value | multiply(0.1) }}', self.hass)) + Template('{{ value | multiply(0.1) }}', self.hass), []) entity.update() self.assertEqual(5, float(entity.state)) @@ -68,3 +73,105 @@ class TestCommandSensorSensor(unittest.TestCase): data.update() self.assertEqual(None, data.value) + + def test_update_with_json_attrs(self): + """Test attributes get extracted from a JSON result.""" + data = command_line.CommandSensorData( + self.hass, + ('echo { \\"key\\": \\"some_json_value\\", \\"another_key\\":\ + \\"another_json_value\\", \\"key_three\\": \\"value_three\\" }'), + 15 + ) + + self.sensor = command_line.CommandSensor(self.hass, data, 'test', + None, None, ['key', + 'another_key', + 'key_three']) + self.sensor.update() + self.assertEqual('some_json_value', + self.sensor.device_state_attributes['key']) + self.assertEqual('another_json_value', + self.sensor.device_state_attributes['another_key']) + self.assertEqual('value_three', + self.sensor.device_state_attributes['key_three']) + + @patch('homeassistant.components.sensor.command_line._LOGGER') + def test_update_with_json_attrs_no_data(self, mock_logger): + """Test attributes when no JSON result fetched.""" + data = command_line.CommandSensorData( + self.hass, + 'echo ', 15 + ) + self.sensor = command_line.CommandSensor(self.hass, data, 'test', + None, None, ['key']) + self.sensor.update() + self.assertEqual({}, self.sensor.device_state_attributes) + self.assertTrue(mock_logger.warning.called) + + @patch('homeassistant.components.sensor.command_line._LOGGER') + def test_update_with_json_attrs_not_dict(self, mock_logger): + """Test attributes get extracted from a JSON result.""" + data = command_line.CommandSensorData( + self.hass, + 'echo [1, 2, 3]', 15 + ) + self.sensor = command_line.CommandSensor(self.hass, data, 'test', + None, None, ['key']) + self.sensor.update() + self.assertEqual({}, self.sensor.device_state_attributes) + self.assertTrue(mock_logger.warning.called) + + @patch('homeassistant.components.sensor.command_line._LOGGER') + def test_update_with_json_attrs_bad_JSON(self, mock_logger): + """Test attributes get extracted from a JSON result.""" + data = command_line.CommandSensorData( + self.hass, + 'echo This is text rather than JSON data.', 15 + ) + self.sensor = command_line.CommandSensor(self.hass, data, 'test', + None, None, ['key']) + self.sensor.update() + self.assertEqual({}, self.sensor.device_state_attributes) + self.assertTrue(mock_logger.warning.called) + + def test_update_with_missing_json_attrs(self): + """Test attributes get extracted from a JSON result.""" + data = command_line.CommandSensorData( + self.hass, + ('echo { \\"key\\": \\"some_json_value\\", \\"another_key\\":\ + \\"another_json_value\\", \\"key_three\\": \\"value_three\\" }'), + 15 + ) + + self.sensor = command_line.CommandSensor(self.hass, data, 'test', + None, None, ['key', + 'another_key', + 'key_three', + 'special_key']) + self.sensor.update() + self.assertEqual('some_json_value', + self.sensor.device_state_attributes['key']) + self.assertEqual('another_json_value', + self.sensor.device_state_attributes['another_key']) + self.assertEqual('value_three', + self.sensor.device_state_attributes['key_three']) + self.assertFalse('special_key' in self.sensor.device_state_attributes) + + def test_update_with_unnecessary_json_attrs(self): + """Test attributes get extracted from a JSON result.""" + data = command_line.CommandSensorData( + self.hass, + ('echo { \\"key\\": \\"some_json_value\\", \\"another_key\\":\ + \\"another_json_value\\", \\"key_three\\": \\"value_three\\" }'), + 15 + ) + + self.sensor = command_line.CommandSensor(self.hass, data, 'test', + None, None, ['key', + 'another_key']) + self.sensor.update() + self.assertEqual('some_json_value', + self.sensor.device_state_attributes['key']) + self.assertEqual('another_json_value', + self.sensor.device_state_attributes['another_key']) + self.assertFalse('key_three' in self.sensor.device_state_attributes) From 1a97ba1b4691a640a69ec115632de98dbdef7770 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Sun, 29 Jul 2018 08:46:06 +0200 Subject: [PATCH 098/113] Upgrade youtube_dl to 2018.07.21 (#15718) --- homeassistant/components/media_extractor.py | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/media_extractor.py b/homeassistant/components/media_extractor.py index 2ecee06d8a39..e17823a1e441 100644 --- a/homeassistant/components/media_extractor.py +++ b/homeassistant/components/media_extractor.py @@ -14,7 +14,7 @@ from homeassistant.components.media_player import ( SERVICE_PLAY_MEDIA) from homeassistant.helpers import config_validation as cv -REQUIREMENTS = ['youtube_dl==2018.07.04'] +REQUIREMENTS = ['youtube_dl==2018.07.21'] _LOGGER = logging.getLogger(__name__) diff --git a/requirements_all.txt b/requirements_all.txt index c951601ca3e9..6862c7cad1ef 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1460,7 +1460,7 @@ yeelight==0.4.0 yeelightsunflower==0.0.10 # homeassistant.components.media_extractor -youtube_dl==2018.07.04 +youtube_dl==2018.07.21 # homeassistant.components.light.zengge zengge==0.2 From baa974a4871f8babeff0906ba1ea775b55556db9 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Sun, 29 Jul 2018 08:46:20 +0200 Subject: [PATCH 099/113] Upgrade numpy to 1.15.0 (#15722) --- homeassistant/components/binary_sensor/trend.py | 2 +- homeassistant/components/image_processing/opencv.py | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/binary_sensor/trend.py b/homeassistant/components/binary_sensor/trend.py index 6a53569798b0..78f471d125bc 100644 --- a/homeassistant/components/binary_sensor/trend.py +++ b/homeassistant/components/binary_sensor/trend.py @@ -23,7 +23,7 @@ from homeassistant.helpers.entity import generate_entity_id from homeassistant.helpers.event import async_track_state_change from homeassistant.util import utcnow -REQUIREMENTS = ['numpy==1.14.5'] +REQUIREMENTS = ['numpy==1.15.0'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/image_processing/opencv.py b/homeassistant/components/image_processing/opencv.py index ca0f3527f734..00ae01f11231 100644 --- a/homeassistant/components/image_processing/opencv.py +++ b/homeassistant/components/image_processing/opencv.py @@ -16,7 +16,7 @@ from homeassistant.components.image_processing import ( from homeassistant.core import split_entity_id import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['numpy==1.14.5'] +REQUIREMENTS = ['numpy==1.15.0'] _LOGGER = logging.getLogger(__name__) diff --git a/requirements_all.txt b/requirements_all.txt index 6862c7cad1ef..79d1e231d24a 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -596,7 +596,7 @@ nuheat==0.3.0 # homeassistant.components.binary_sensor.trend # homeassistant.components.image_processing.opencv -numpy==1.14.5 +numpy==1.15.0 # homeassistant.components.google oauth2client==4.0.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 913117adc2dc..469e48377d4e 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -102,7 +102,7 @@ mficlient==0.3.0 # homeassistant.components.binary_sensor.trend # homeassistant.components.image_processing.opencv -numpy==1.14.5 +numpy==1.15.0 # homeassistant.components.mqtt # homeassistant.components.shiftr From 5849381dfb3c800170f4296bf70c48490f0c6c5c Mon Sep 17 00:00:00 2001 From: Peter Nijssen Date: Sun, 29 Jul 2018 19:49:36 +0200 Subject: [PATCH 100/113] Upgrade spiderpy to 1.2.0 (#15729) --- homeassistant/components/spider.py | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/spider.py b/homeassistant/components/spider.py index 359aa029794f..48632be6badf 100644 --- a/homeassistant/components/spider.py +++ b/homeassistant/components/spider.py @@ -14,7 +14,7 @@ from homeassistant.const import ( import homeassistant.helpers.config_validation as cv from homeassistant.helpers.discovery import load_platform -REQUIREMENTS = ['spiderpy==1.1.0'] +REQUIREMENTS = ['spiderpy==1.2.0'] _LOGGER = logging.getLogger(__name__) diff --git a/requirements_all.txt b/requirements_all.txt index 79d1e231d24a..af1d93e04c58 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1291,7 +1291,7 @@ somecomfort==0.5.2 speedtest-cli==2.0.2 # homeassistant.components.spider -spiderpy==1.1.0 +spiderpy==1.2.0 # homeassistant.components.sensor.spotcrime spotcrime==1.0.3 From 28ad0017e1061e974ad5d9d1bb374e6bf34d4bb8 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Sun, 29 Jul 2018 19:55:49 +0200 Subject: [PATCH 101/113] Upgrade beautifulsoup4 to 4.6.1 (#15727) --- homeassistant/components/device_tracker/linksys_ap.py | 2 +- homeassistant/components/sensor/geizhals.py | 2 +- homeassistant/components/sensor/scrape.py | 2 +- homeassistant/components/sensor/sytadin.py | 6 +++--- requirements_all.txt | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/device_tracker/linksys_ap.py b/homeassistant/components/device_tracker/linksys_ap.py index bf3916f3abe0..a2a371163fd6 100644 --- a/homeassistant/components/device_tracker/linksys_ap.py +++ b/homeassistant/components/device_tracker/linksys_ap.py @@ -19,7 +19,7 @@ from homeassistant.const import ( INTERFACES = 2 DEFAULT_TIMEOUT = 10 -REQUIREMENTS = ['beautifulsoup4==4.6.0'] +REQUIREMENTS = ['beautifulsoup4==4.6.1'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/sensor/geizhals.py b/homeassistant/components/sensor/geizhals.py index 9e32bf27c295..06062b26b004 100644 --- a/homeassistant/components/sensor/geizhals.py +++ b/homeassistant/components/sensor/geizhals.py @@ -15,7 +15,7 @@ from homeassistant.util import Throttle from homeassistant.helpers.entity import Entity from homeassistant.const import (CONF_DOMAIN, CONF_NAME) -REQUIREMENTS = ['beautifulsoup4==4.6.0'] +REQUIREMENTS = ['beautifulsoup4==4.6.1'] _LOGGER = logging.getLogger(__name__) CONF_PRODUCT_ID = 'product_id' diff --git a/homeassistant/components/sensor/scrape.py b/homeassistant/components/sensor/scrape.py index 0065f3e09274..e7aace8ec6d2 100644 --- a/homeassistant/components/sensor/scrape.py +++ b/homeassistant/components/sensor/scrape.py @@ -19,7 +19,7 @@ from homeassistant.const import ( from homeassistant.helpers.entity import Entity import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['beautifulsoup4==4.6.0'] +REQUIREMENTS = ['beautifulsoup4==4.6.1'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/sensor/sytadin.py b/homeassistant/components/sensor/sytadin.py index 6b2284f4376b..ff8e7d7ddfee 100644 --- a/homeassistant/components/sensor/sytadin.py +++ b/homeassistant/components/sensor/sytadin.py @@ -18,7 +18,7 @@ from homeassistant.const import ( from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle -REQUIREMENTS = ['beautifulsoup4==4.6.0'] +REQUIREMENTS = ['beautifulsoup4==4.6.1'] _LOGGER = logging.getLogger(__name__) @@ -35,9 +35,9 @@ OPTION_MEAN_VELOCITY = 'mean_velocity' OPTION_CONGESTION = 'congestion' SENSOR_TYPES = { - OPTION_TRAFFIC_JAM: ['Traffic Jam', LENGTH_KILOMETERS], - OPTION_MEAN_VELOCITY: ['Mean Velocity', LENGTH_KILOMETERS+'/h'], OPTION_CONGESTION: ['Congestion', ''], + OPTION_MEAN_VELOCITY: ['Mean Velocity', LENGTH_KILOMETERS+'/h'], + OPTION_TRAFFIC_JAM: ['Traffic Jam', LENGTH_KILOMETERS], } MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=5) diff --git a/requirements_all.txt b/requirements_all.txt index af1d93e04c58..509d5faa7c7d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -154,7 +154,7 @@ batinfo==0.4.2 # homeassistant.components.sensor.geizhals # homeassistant.components.sensor.scrape # homeassistant.components.sensor.sytadin -beautifulsoup4==4.6.0 +beautifulsoup4==4.6.1 # homeassistant.components.zha bellows==0.6.0 From 491bc006b23b54b3658dc5370290ef0ed0035233 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Sun, 29 Jul 2018 23:35:27 +0200 Subject: [PATCH 102/113] Upgrade mutagen to 1.41.0 (#15739) --- homeassistant/components/tts/__init__.py | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/tts/__init__.py b/homeassistant/components/tts/__init__.py index f8a521c3e2f9..f060c9f353ae 100644 --- a/homeassistant/components/tts/__init__.py +++ b/homeassistant/components/tts/__init__.py @@ -29,7 +29,7 @@ from homeassistant.helpers import config_per_platform import homeassistant.helpers.config_validation as cv from homeassistant.setup import async_prepare_setup_platform -REQUIREMENTS = ['mutagen==1.40.0'] +REQUIREMENTS = ['mutagen==1.41.0'] _LOGGER = logging.getLogger(__name__) diff --git a/requirements_all.txt b/requirements_all.txt index 509d5faa7c7d..deb2a9d22c23 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -558,7 +558,7 @@ mitemp_bt==0.0.1 motorparts==1.0.2 # homeassistant.components.tts -mutagen==1.40.0 +mutagen==1.41.0 # homeassistant.components.mychevy mychevy==0.4.0 From 4b257c3d016a8126001092f434cc0de61108069d Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Sun, 29 Jul 2018 23:35:47 +0200 Subject: [PATCH 103/113] Upgrade sqlalchemy to 1.2.10 (#15737) --- homeassistant/components/recorder/__init__.py | 2 +- homeassistant/components/sensor/sql.py | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/recorder/__init__.py b/homeassistant/components/recorder/__init__.py index 60df63270098..f3d8e269a42a 100644 --- a/homeassistant/components/recorder/__init__.py +++ b/homeassistant/components/recorder/__init__.py @@ -35,7 +35,7 @@ from . import migration, purge from .const import DATA_INSTANCE from .util import session_scope -REQUIREMENTS = ['sqlalchemy==1.2.9'] +REQUIREMENTS = ['sqlalchemy==1.2.10'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/sensor/sql.py b/homeassistant/components/sensor/sql.py index 8574a7231da5..83f5478867fe 100644 --- a/homeassistant/components/sensor/sql.py +++ b/homeassistant/components/sensor/sql.py @@ -20,7 +20,7 @@ from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) -REQUIREMENTS = ['sqlalchemy==1.2.9'] +REQUIREMENTS = ['sqlalchemy==1.2.10'] CONF_COLUMN_NAME = 'column' CONF_QUERIES = 'queries' diff --git a/requirements_all.txt b/requirements_all.txt index deb2a9d22c23..0047ce38b739 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1302,7 +1302,7 @@ spotipy-homeassistant==2.4.4.dev1 # homeassistant.components.recorder # homeassistant.scripts.db_migrator # homeassistant.components.sensor.sql -sqlalchemy==1.2.9 +sqlalchemy==1.2.10 # homeassistant.components.statsd statsd==3.2.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 469e48377d4e..ff95cd3be25f 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -197,7 +197,7 @@ somecomfort==0.5.2 # homeassistant.components.recorder # homeassistant.scripts.db_migrator # homeassistant.components.sensor.sql -sqlalchemy==1.2.9 +sqlalchemy==1.2.10 # homeassistant.components.statsd statsd==3.2.1 From a8dd81e986d94c15c6630adb9a609d4bc9787136 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Sun, 29 Jul 2018 23:36:28 +0200 Subject: [PATCH 104/113] Upgrade voluptuous to 0.11.3 (#15735) --- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- setup.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 66b17cf9bd99..e832314cf175 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -8,7 +8,7 @@ pip>=8.0.3 pytz>=2018.04 pyyaml>=3.13,<4 requests==2.19.1 -voluptuous==0.11.1 +voluptuous==0.11.3 # Breaks Python 3.6 and is not needed for our supported Python versions enum34==1000000000.0.0 diff --git a/requirements_all.txt b/requirements_all.txt index 0047ce38b739..6ba57da573d7 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -9,7 +9,7 @@ pip>=8.0.3 pytz>=2018.04 pyyaml>=3.13,<4 requests==2.19.1 -voluptuous==0.11.1 +voluptuous==0.11.3 # homeassistant.components.nuimo_controller --only-binary=all nuimo==0.1.0 diff --git a/setup.py b/setup.py index bbf10dd309dc..7519fc6a8736 100755 --- a/setup.py +++ b/setup.py @@ -42,7 +42,7 @@ REQUIRES = [ 'pytz>=2018.04', 'pyyaml>=3.13,<4', 'requests==2.19.1', - 'voluptuous==0.11.1', + 'voluptuous==0.11.3', ] MIN_PY_VERSION = '.'.join(map(str, hass_const.REQUIRED_PYTHON_VER)) From 316ef89541527554ea80c6ffbc9879421f0ff19a Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Sun, 29 Jul 2018 23:37:10 +0200 Subject: [PATCH 105/113] Upgrade youtube_dl to 2018.07.29 (#15734) --- homeassistant/components/media_extractor.py | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/media_extractor.py b/homeassistant/components/media_extractor.py index e17823a1e441..029d10ea00a1 100644 --- a/homeassistant/components/media_extractor.py +++ b/homeassistant/components/media_extractor.py @@ -14,7 +14,7 @@ from homeassistant.components.media_player import ( SERVICE_PLAY_MEDIA) from homeassistant.helpers import config_validation as cv -REQUIREMENTS = ['youtube_dl==2018.07.21'] +REQUIREMENTS = ['youtube_dl==2018.07.29'] _LOGGER = logging.getLogger(__name__) diff --git a/requirements_all.txt b/requirements_all.txt index 6ba57da573d7..6ad190f40ef9 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1460,7 +1460,7 @@ yeelight==0.4.0 yeelightsunflower==0.0.10 # homeassistant.components.media_extractor -youtube_dl==2018.07.21 +youtube_dl==2018.07.29 # homeassistant.components.light.zengge zengge==0.2 From 4013a90f330eed082fcdc0b2b10d721cd137ba57 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Sun, 29 Jul 2018 23:37:38 +0200 Subject: [PATCH 106/113] Upgrade pyowm to 2.9.0 (#15736) --- homeassistant/components/sensor/openweathermap.py | 2 +- homeassistant/components/weather/openweathermap.py | 2 +- requirements_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/sensor/openweathermap.py b/homeassistant/components/sensor/openweathermap.py index 6241f3e53781..ba7fc4f90951 100644 --- a/homeassistant/components/sensor/openweathermap.py +++ b/homeassistant/components/sensor/openweathermap.py @@ -17,7 +17,7 @@ import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle -REQUIREMENTS = ['pyowm==2.8.0'] +REQUIREMENTS = ['pyowm==2.9.0'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/weather/openweathermap.py b/homeassistant/components/weather/openweathermap.py index 0c876d9e2d74..334948b67fb5 100644 --- a/homeassistant/components/weather/openweathermap.py +++ b/homeassistant/components/weather/openweathermap.py @@ -18,7 +18,7 @@ from homeassistant.const import ( import homeassistant.helpers.config_validation as cv from homeassistant.util import Throttle -REQUIREMENTS = ['pyowm==2.8.0'] +REQUIREMENTS = ['pyowm==2.9.0'] _LOGGER = logging.getLogger(__name__) diff --git a/requirements_all.txt b/requirements_all.txt index 6ad190f40ef9..a8e57097bf29 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -964,7 +964,7 @@ pyotp==2.2.6 # homeassistant.components.sensor.openweathermap # homeassistant.components.weather.openweathermap -pyowm==2.8.0 +pyowm==2.9.0 # homeassistant.components.sensor.pollen pypollencom==2.1.0 From 681082a3adc40db146197fa295305f47b5c98746 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Sun, 29 Jul 2018 23:39:01 +0200 Subject: [PATCH 107/113] Various updates (#15738) --- homeassistant/components/camera/proxy.py | 71 +++++++++++------------- requirements_all.txt | 2 +- 2 files changed, 33 insertions(+), 40 deletions(-) diff --git a/homeassistant/components/camera/proxy.py b/homeassistant/components/camera/proxy.py index d88d52c4c8a0..a695848d1fa9 100644 --- a/homeassistant/components/camera/proxy.py +++ b/homeassistant/components/camera/proxy.py @@ -2,56 +2,53 @@ Proxy camera platform that enables image processing of camera data. For more details about this platform, please refer to the documentation -https://home-assistant.io/components/proxy +https://www.home-assistant.io/components/camera.proxy/ """ -import logging import asyncio +import logging + import aiohttp import async_timeout - import voluptuous as vol -from homeassistant.util.async_ import run_coroutine_threadsafe +from homeassistant.components.camera import PLATFORM_SCHEMA, Camera +from homeassistant.const import CONF_ENTITY_ID, CONF_NAME, HTTP_HEADER_HA_AUTH from homeassistant.helpers import config_validation as cv - -import homeassistant.util.dt as dt_util -from homeassistant.const import ( - CONF_NAME, CONF_ENTITY_ID, HTTP_HEADER_HA_AUTH) -from homeassistant.components.camera import ( - PLATFORM_SCHEMA, Camera) from homeassistant.helpers.aiohttp_client import ( - async_get_clientsession, async_aiohttp_proxy_web) + async_aiohttp_proxy_web, async_get_clientsession) +from homeassistant.util.async_ import run_coroutine_threadsafe +import homeassistant.util.dt as dt_util -REQUIREMENTS = ['pillow==5.0.0'] +REQUIREMENTS = ['pillow==5.2.0'] _LOGGER = logging.getLogger(__name__) -CONF_MAX_IMAGE_WIDTH = "max_image_width" -CONF_IMAGE_QUALITY = "image_quality" -CONF_IMAGE_REFRESH_RATE = "image_refresh_rate" -CONF_FORCE_RESIZE = "force_resize" -CONF_MAX_STREAM_WIDTH = "max_stream_width" -CONF_STREAM_QUALITY = "stream_quality" -CONF_CACHE_IMAGES = "cache_images" +CONF_CACHE_IMAGES = 'cache_images' +CONF_FORCE_RESIZE = 'force_resize' +CONF_IMAGE_QUALITY = 'image_quality' +CONF_IMAGE_REFRESH_RATE = 'image_refresh_rate' +CONF_MAX_IMAGE_WIDTH = 'max_image_width' +CONF_MAX_STREAM_WIDTH = 'max_stream_width' +CONF_STREAM_QUALITY = 'stream_quality' DEFAULT_BASENAME = "Camera Proxy" DEFAULT_QUALITY = 75 PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_ENTITY_ID): cv.entity_id, - vol.Optional(CONF_NAME): cv.string, - vol.Optional(CONF_MAX_IMAGE_WIDTH): int, + vol.Optional(CONF_CACHE_IMAGES, False): cv.boolean, + vol.Optional(CONF_FORCE_RESIZE, False): cv.boolean, vol.Optional(CONF_IMAGE_QUALITY): int, vol.Optional(CONF_IMAGE_REFRESH_RATE): float, - vol.Optional(CONF_FORCE_RESIZE, False): cv.boolean, - vol.Optional(CONF_CACHE_IMAGES, False): cv.boolean, + vol.Optional(CONF_MAX_IMAGE_WIDTH): int, vol.Optional(CONF_MAX_STREAM_WIDTH): int, + vol.Optional(CONF_NAME): cv.string, vol.Optional(CONF_STREAM_QUALITY): int, }) -async def async_setup_platform(hass, config, async_add_devices, - discovery_info=None): +async def async_setup_platform( + hass, config, async_add_devices, discovery_info=None): """Set up the Proxy camera platform.""" async_add_devices([ProxyCamera(hass, config)]) @@ -77,7 +74,7 @@ def _resize_image(image, opts): old_size = len(image) if old_width <= new_width: if opts.quality is None: - _LOGGER.debug("Image is smaller-than / equal-to requested width") + _LOGGER.debug("Image is smaller-than/equal-to requested width") return image new_width = old_width @@ -86,7 +83,7 @@ def _resize_image(image, opts): img = img.resize((new_width, new_height), Image.ANTIALIAS) imgbuf = io.BytesIO() - img.save(imgbuf, "JPEG", optimize=True, quality=quality) + img.save(imgbuf, 'JPEG', optimize=True, quality=quality) newimage = imgbuf.getvalue() if not opts.force_resize and len(newimage) >= old_size: _LOGGER.debug("Using original image(%d bytes) " @@ -94,11 +91,9 @@ def _resize_image(image, opts): old_size, len(newimage)) return image - _LOGGER.debug("Resized image " - "from (%dx%d - %d bytes) " - "to (%dx%d - %d bytes)", - old_width, old_height, old_size, - new_width, new_height, len(newimage)) + _LOGGER.debug( + "Resized image from (%dx%d - %d bytes) to (%dx%d - %d bytes)", + old_width, old_height, old_size, new_width, new_height, len(newimage)) return newimage @@ -112,7 +107,7 @@ class ImageOpts(): self.force_resize = force_resize def __bool__(self): - """Bool evalution rules.""" + """Bool evaluation rules.""" return bool(self.max_width or self.quality) @@ -133,8 +128,7 @@ class ProxyCamera(Camera): config.get(CONF_FORCE_RESIZE)) self._stream_opts = ImageOpts( - config.get(CONF_MAX_STREAM_WIDTH), - config.get(CONF_STREAM_QUALITY), + config.get(CONF_MAX_STREAM_WIDTH), config.get(CONF_STREAM_QUALITY), True) self._image_refresh_rate = config.get(CONF_IMAGE_REFRESH_RATE) @@ -145,8 +139,7 @@ class ProxyCamera(Camera): self._last_image = None self._headers = ( {HTTP_HEADER_HA_AUTH: self.hass.config.api.api_password} - if self.hass.config.api.api_password is not None - else None) + if self.hass.config.api.api_password is not None else None) def camera_image(self): """Return camera image.""" @@ -195,8 +188,8 @@ class ProxyCamera(Camera): self.hass, request, stream_coro) response = aiohttp.web.StreamResponse() - response.content_type = ('multipart/x-mixed-replace; ' - 'boundary=--frameboundary') + response.content_type = ( + 'multipart/x-mixed-replace; boundary=--frameboundary') await response.prepare(request) async def write(img_bytes): diff --git a/requirements_all.txt b/requirements_all.txt index a8e57097bf29..887b158899eb 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -649,7 +649,7 @@ piglow==1.2.4 pilight==0.1.1 # homeassistant.components.camera.proxy -pillow==5.0.0 +pillow==5.2.0 # homeassistant.components.dominos pizzapi==0.0.3 From 48ba13bc6c941c2d77689484b6113232876686c8 Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Mon, 30 Jul 2018 00:42:23 +0200 Subject: [PATCH 108/113] Denonavr version push to 0.7.5 (#15743) * Version push to 0.7.5 Improve logger warning * Denonavr v.0.7.5 --- homeassistant/components/media_player/denonavr.py | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/media_player/denonavr.py b/homeassistant/components/media_player/denonavr.py index 654374de08a0..604fb91451e1 100644 --- a/homeassistant/components/media_player/denonavr.py +++ b/homeassistant/components/media_player/denonavr.py @@ -21,7 +21,7 @@ from homeassistant.const import ( CONF_NAME, STATE_ON, CONF_ZONE, CONF_TIMEOUT) import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['denonavr==0.7.4'] +REQUIREMENTS = ['denonavr==0.7.5'] _LOGGER = logging.getLogger(__name__) diff --git a/requirements_all.txt b/requirements_all.txt index 887b158899eb..000d4108a144 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -263,7 +263,7 @@ defusedxml==0.5.0 deluge-client==1.4.0 # homeassistant.components.media_player.denonavr -denonavr==0.7.4 +denonavr==0.7.5 # homeassistant.components.media_player.directv directpy==0.5 From 3959f82030b560efec136ec4a77f3efa9eeb4f23 Mon Sep 17 00:00:00 2001 From: Juha Niemi Date: Mon, 30 Jul 2018 08:09:59 +0300 Subject: [PATCH 109/113] Make FutureNow light remember last brightness when turning on (#15733) * Remember last brightness value and use it on turn_on() * Pyfnip-0.2 now returns state reliably, no manual changes needed. * Split too long line of code * Updated pyfnip library version --- homeassistant/components/light/futurenow.py | 21 ++++++++------------- requirements_all.txt | 2 +- 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/homeassistant/components/light/futurenow.py b/homeassistant/components/light/futurenow.py index 56ecaa7b2ca5..1777376881e3 100644 --- a/homeassistant/components/light/futurenow.py +++ b/homeassistant/components/light/futurenow.py @@ -16,7 +16,7 @@ from homeassistant.components.light import ( PLATFORM_SCHEMA) import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['pyfnip==0.1'] +REQUIREMENTS = ['pyfnip==0.2'] _LOGGER = logging.getLogger(__name__) @@ -75,8 +75,8 @@ class FutureNowLight(Light): self._dimmable = device['dimmable'] self._channel = device['channel'] self._brightness = None + self._last_brightness = 255 self._state = None - self._skip_update = False if device['driver'] == CONF_DRIVER_FNIP6X10AD: self._light = pyfnip.FNIP6x2adOutput(device['host'], @@ -111,25 +111,20 @@ class FutureNowLight(Light): def turn_on(self, **kwargs): """Turn the light on.""" - level = kwargs.get(ATTR_BRIGHTNESS, 255) if self._dimmable else 255 + if self._dimmable: + level = kwargs.get(ATTR_BRIGHTNESS, self._last_brightness) + else: + level = 255 self._light.turn_on(to_futurenow_level(level)) - self._brightness = level - self._state = True - self._skip_update = True def turn_off(self, **kwargs): """Turn the light off.""" self._light.turn_off() - self._brightness = 0 - self._state = False - self._skip_update = True + if self._brightness: + self._last_brightness = self._brightness def update(self): """Fetch new state data for this light.""" - if self._skip_update: - self._skip_update = False - return - state = int(self._light.is_on()) self._state = bool(state) self._brightness = to_hass_level(state) diff --git a/requirements_all.txt b/requirements_all.txt index 000d4108a144..139d90971bfa 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -829,7 +829,7 @@ pyflexit==0.3 pyflic-homeassistant==0.4.dev0 # homeassistant.components.light.futurenow -pyfnip==0.1 +pyfnip==0.2 # homeassistant.components.fritzbox pyfritzhome==0.3.7 From 8dbe78a21a465836db6d45424ab5746de9cc72c0 Mon Sep 17 00:00:00 2001 From: Josh Shoemaker Date: Mon, 30 Jul 2018 01:19:34 -0400 Subject: [PATCH 110/113] Add Genie Aladdin Connect cover component (#15699) * Add Genie Aladdin Connect cover component * Fix lines being too long * Fix issues found in review * remove Unknown state, use None instead * Fixed requirements_all --- .coveragerc | 1 + .../components/cover/aladdin_connect.py | 115 ++++++++++++++++++ requirements_all.txt | 3 + 3 files changed, 119 insertions(+) create mode 100644 homeassistant/components/cover/aladdin_connect.py diff --git a/.coveragerc b/.coveragerc index a33b996cb861..3d369eed0739 100644 --- a/.coveragerc +++ b/.coveragerc @@ -404,6 +404,7 @@ omit = homeassistant/components/climate/touchline.py homeassistant/components/climate/venstar.py homeassistant/components/climate/zhong_hong.py + homeassistant/components/cover/aladdin_connect.py homeassistant/components/cover/brunt.py homeassistant/components/cover/garadget.py homeassistant/components/cover/gogogate2.py diff --git a/homeassistant/components/cover/aladdin_connect.py b/homeassistant/components/cover/aladdin_connect.py new file mode 100644 index 000000000000..efaea39bb864 --- /dev/null +++ b/homeassistant/components/cover/aladdin_connect.py @@ -0,0 +1,115 @@ +""" +Platform for the Aladdin Connect cover component. + +For more details about this platform, please refer to the documentation +https://home-assistant.io/components/cover.aladdin_connect/ +""" +import logging + +import voluptuous as vol + +from homeassistant.components.cover import (CoverDevice, PLATFORM_SCHEMA, + SUPPORT_OPEN, SUPPORT_CLOSE) +from homeassistant.const import (CONF_USERNAME, CONF_PASSWORD, STATE_CLOSED, + STATE_OPENING, STATE_CLOSING, STATE_OPEN) +import homeassistant.helpers.config_validation as cv + +REQUIREMENTS = ['aladdin_connect==0.1'] + +_LOGGER = logging.getLogger(__name__) + +NOTIFICATION_ID = 'aladdin_notification' +NOTIFICATION_TITLE = 'Aladdin Connect Cover Setup' + +STATES_MAP = { + 'open': STATE_OPEN, + 'opening': STATE_OPENING, + 'closed': STATE_CLOSED, + 'closing': STATE_CLOSING +} + +SUPPORTED_FEATURES = SUPPORT_OPEN | SUPPORT_CLOSE + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_USERNAME): cv.string, + vol.Required(CONF_PASSWORD): cv.string +}) + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Set up the Aladdin Connect platform.""" + from aladdin_connect import AladdinConnectClient + + username = config.get(CONF_USERNAME) + password = config.get(CONF_PASSWORD) + acc = AladdinConnectClient(username, password) + + try: + if not acc.login(): + raise ValueError("Username or Password is incorrect") + add_devices(AladdinDevice(acc, door) for door in acc.get_doors()) + except (TypeError, KeyError, NameError, ValueError) as ex: + _LOGGER.error("%s", ex) + hass.components.persistent_notification.create( + 'Error: {}
' + 'You will need to restart hass after fixing.' + ''.format(ex), + title=NOTIFICATION_TITLE, + notification_id=NOTIFICATION_ID) + + +class AladdinDevice(CoverDevice): + """Representation of Aladdin Connect cover.""" + + def __init__(self, acc, device): + """Initialize the cover.""" + self._acc = acc + self._device_id = device['device_id'] + self._number = device['door_number'] + self._name = device['name'] + self._status = STATES_MAP.get(device['status']) + + @property + def device_class(self): + """Define this cover as a garage door.""" + return 'garage' + + @property + def supported_features(self): + """Flag supported features.""" + return SUPPORTED_FEATURES + + @property + def name(self): + """Return the name of the garage door.""" + return self._name + + @property + def is_opening(self): + """Return if the cover is opening or not.""" + return self._status == STATE_OPENING + + @property + def is_closing(self): + """Return if the cover is closing or not.""" + return self._status == STATE_CLOSING + + @property + def is_closed(self): + """Return None if status is unknown, True if closed, else False.""" + if self._status is None: + return None + return self._status == STATE_CLOSED + + def close_cover(self, **kwargs): + """Issue close command to cover.""" + self._acc.close_door(self._device_id, self._number) + + def open_cover(self, **kwargs): + """Issue open command to cover.""" + self._acc.open_door(self._device_id, self._number) + + def update(self): + """Update status of cover.""" + acc_status = self._acc.get_door_status(self._device_id, self._number) + self._status = STATES_MAP.get(acc_status) diff --git a/requirements_all.txt b/requirements_all.txt index 139d90971bfa..9be2d66f327e 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -108,6 +108,9 @@ aiolifx_effects==0.1.2 # homeassistant.components.scene.hunterdouglas_powerview aiopvapi==1.5.4 +# homeassistant.components.cover.aladdin_connect +aladdin_connect==0.1 + # homeassistant.components.alarmdecoder alarmdecoder==1.13.2 From 460bb69adeb58676a47bbc052a3044f4c46492d6 Mon Sep 17 00:00:00 2001 From: David Straub Date: Mon, 30 Jul 2018 10:32:39 +0200 Subject: [PATCH 111/113] Add mvglive option to store multiple departures in attributes (#15454) * MVG Live sensor: add option to store multiple departures in attributes * Fix lint error * mvglive: take into account timeoffset in API call * Prevent exception if departure list is empty * Rename state_attributes -> device_state_attributes --- homeassistant/components/sensor/mvglive.py | 43 +++++++++++++++------- 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/homeassistant/components/sensor/mvglive.py b/homeassistant/components/sensor/mvglive.py index 95f2845eced6..e066bb5e0b9b 100644 --- a/homeassistant/components/sensor/mvglive.py +++ b/homeassistant/components/sensor/mvglive.py @@ -7,6 +7,7 @@ https://home-assistant.io/components/sensor.mvglive/ import logging from datetime import timedelta +from copy import deepcopy import voluptuous as vol import homeassistant.helpers.config_validation as cv @@ -28,6 +29,7 @@ CONF_DIRECTIONS = 'directions' CONF_LINES = 'lines' CONF_PRODUCTS = 'products' CONF_TIMEOFFSET = 'timeoffset' +CONF_NUMBER = 'number' DEFAULT_PRODUCT = ['U-Bahn', 'Tram', 'Bus', 'S-Bahn'] @@ -52,6 +54,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Optional(CONF_PRODUCTS, default=DEFAULT_PRODUCT): cv.ensure_list_csv, vol.Optional(CONF_TIMEOFFSET, default=0): cv.positive_int, + vol.Optional(CONF_NUMBER, default=1): cv.positive_int, vol.Optional(CONF_NAME): cv.string}] }) @@ -68,6 +71,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): nextdeparture.get(CONF_LINES), nextdeparture.get(CONF_PRODUCTS), nextdeparture.get(CONF_TIMEOFFSET), + nextdeparture.get(CONF_NUMBER), nextdeparture.get(CONF_NAME))) add_devices(sensors, True) @@ -76,12 +80,12 @@ class MVGLiveSensor(Entity): """Implementation of an MVG Live sensor.""" def __init__(self, station, destinations, directions, - lines, products, timeoffset, name): + lines, products, timeoffset, number, name): """Initialize the sensor.""" self._station = station self._name = name self.data = MVGLiveData(station, destinations, directions, - lines, products, timeoffset) + lines, products, timeoffset, number) self._state = STATE_UNKNOWN self._icon = ICONS['-'] @@ -98,9 +102,14 @@ class MVGLiveSensor(Entity): return self._state @property - def state_attributes(self): + def device_state_attributes(self): """Return the state attributes.""" - return self.data.departures + dep = self.data.departures + if not dep: + return None + attr = dep[0] # next depature attributes + attr['departures'] = deepcopy(dep) # all departures dictionary + return attr @property def icon(self): @@ -119,15 +128,15 @@ class MVGLiveSensor(Entity): self._state = '-' self._icon = ICONS['-'] else: - self._state = self.data.departures.get('time', '-') - self._icon = ICONS[self.data.departures.get('product', '-')] + self._state = self.data.departures[0].get('time', '-') + self._icon = ICONS[self.data.departures[0].get('product', '-')] class MVGLiveData: """Pull data from the mvg-live.de web page.""" def __init__(self, station, destinations, directions, - lines, products, timeoffset): + lines, products, timeoffset, number): """Initialize the sensor.""" import MVGLive self._station = station @@ -136,25 +145,30 @@ class MVGLiveData: self._lines = lines self._products = products self._timeoffset = timeoffset + self._number = number self._include_ubahn = True if 'U-Bahn' in self._products else False self._include_tram = True if 'Tram' in self._products else False self._include_bus = True if 'Bus' in self._products else False self._include_sbahn = True if 'S-Bahn' in self._products else False self.mvg = MVGLive.MVGLive() - self.departures = {} + self.departures = [] def update(self): """Update the connection data.""" try: _departures = self.mvg.getlivedata( - station=self._station, ubahn=self._include_ubahn, - tram=self._include_tram, bus=self._include_bus, + station=self._station, + timeoffset=self._timeoffset, + ubahn=self._include_ubahn, + tram=self._include_tram, + bus=self._include_bus, sbahn=self._include_sbahn) except ValueError: - self.departures = {} + self.departures = [] _LOGGER.warning("Returned data not understood") return - for _departure in _departures: + self.departures = [] + for i, _departure in enumerate(_departures): # find the first departure meeting the criteria if ('' not in self._destinations[:1] and _departure['destination'] not in self._destinations): @@ -173,5 +187,6 @@ class MVGLiveData: 'product']: _nextdep[k] = _departure.get(k, '') _nextdep['time'] = int(_nextdep['time']) - self.departures = _nextdep - break + self.departures.append(_nextdep) + if i == self._number - 1: + break From 744c27712308468270fcf8046bd62cdcaf3100dc Mon Sep 17 00:00:00 2001 From: Dan Faulknor Date: Mon, 30 Jul 2018 20:38:49 +1200 Subject: [PATCH 112/113] Add other wemo motion sensor identifier (#15627) * Add other motion sensor identifier * Fix order --- homeassistant/components/wemo.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/wemo.py b/homeassistant/components/wemo.py index e8c7db5efe10..27027cc9eb4e 100644 --- a/homeassistant/components/wemo.py +++ b/homeassistant/components/wemo.py @@ -26,6 +26,7 @@ WEMO_MODEL_DISPATCH = { 'Insight': 'switch', 'LightSwitch': 'switch', 'Maker': 'switch', + 'Motion': 'binary_sensor', 'Sensor': 'binary_sensor', 'Socket': 'switch' } From 1e5596b5945b322cb874c0e121ee0fa203c6d9cb Mon Sep 17 00:00:00 2001 From: Jason Hu Date: Mon, 30 Jul 2018 04:44:31 -0700 Subject: [PATCH 113/113] Remove self type hints (#15732) * Remove self type hints * Lint --- homeassistant/components/climate/honeywell.py | 8 +++---- homeassistant/components/fan/__init__.py | 22 +++++++++---------- homeassistant/components/fan/dyson.py | 19 ++++++++-------- homeassistant/components/fan/insteon_local.py | 11 +++++----- homeassistant/components/fan/template.py | 20 +++++++---------- homeassistant/components/fan/wink.py | 13 +++++------ homeassistant/components/fan/zha.py | 2 +- homeassistant/util/__init__.py | 8 +++---- homeassistant/util/unit_system.py | 2 +- tests/components/climate/test_honeywell.py | 4 ++-- 10 files changed, 51 insertions(+), 58 deletions(-) diff --git a/homeassistant/components/climate/honeywell.py b/homeassistant/components/climate/honeywell.py index 11a507aded2d..04d705d6b49b 100644 --- a/homeassistant/components/climate/honeywell.py +++ b/homeassistant/components/climate/honeywell.py @@ -165,7 +165,7 @@ class RoundThermostat(ClimateDevice): self.client.set_temperature(self._name, temperature) @property - def current_operation(self: ClimateDevice) -> str: + def current_operation(self) -> str: """Get the current operation of the system.""" return getattr(self.client, ATTR_SYSTEM_MODE, None) @@ -174,7 +174,7 @@ class RoundThermostat(ClimateDevice): """Return true if away mode is on.""" return self._away - def set_operation_mode(self: ClimateDevice, operation_mode: str) -> None: + def set_operation_mode(self, operation_mode: str) -> None: """Set the HVAC mode for the thermostat.""" if hasattr(self.client, ATTR_SYSTEM_MODE): self.client.system_mode = operation_mode @@ -280,7 +280,7 @@ class HoneywellUSThermostat(ClimateDevice): return self._device.setpoint_heat @property - def current_operation(self: ClimateDevice) -> str: + def current_operation(self) -> str: """Return current operation ie. heat, cool, idle.""" oper = getattr(self._device, ATTR_CURRENT_OPERATION, None) if oper == "off": @@ -373,7 +373,7 @@ class HoneywellUSThermostat(ClimateDevice): except somecomfort.SomeComfortError: _LOGGER.error('Can not stop hold mode') - def set_operation_mode(self: ClimateDevice, operation_mode: str) -> None: + def set_operation_mode(self, operation_mode: str) -> None: """Set the system mode (Cool, Heat, etc).""" if hasattr(self._device, ATTR_SYSTEM_MODE): self._device.system_mode = operation_mode diff --git a/homeassistant/components/fan/__init__.py b/homeassistant/components/fan/__init__.py index 66790d026872..db0e8c590fda 100644 --- a/homeassistant/components/fan/__init__.py +++ b/homeassistant/components/fan/__init__.py @@ -235,11 +235,11 @@ def async_setup(hass, config: dict): class FanEntity(ToggleEntity): """Representation of a fan.""" - def set_speed(self: ToggleEntity, speed: str) -> None: + def set_speed(self, speed: str) -> None: """Set the speed of the fan.""" raise NotImplementedError() - def async_set_speed(self: ToggleEntity, speed: str): + def async_set_speed(self, speed: str): """Set the speed of the fan. This method must be run in the event loop and returns a coroutine. @@ -248,11 +248,11 @@ class FanEntity(ToggleEntity): return self.async_turn_off() return self.hass.async_add_job(self.set_speed, speed) - def set_direction(self: ToggleEntity, direction: str) -> None: + def set_direction(self, direction: str) -> None: """Set the direction of the fan.""" raise NotImplementedError() - def async_set_direction(self: ToggleEntity, direction: str): + def async_set_direction(self, direction: str): """Set the direction of the fan. This method must be run in the event loop and returns a coroutine. @@ -260,12 +260,12 @@ class FanEntity(ToggleEntity): return self.hass.async_add_job(self.set_direction, direction) # pylint: disable=arguments-differ - def turn_on(self: ToggleEntity, speed: str = None, **kwargs) -> None: + def turn_on(self, speed: str = None, **kwargs) -> None: """Turn on the fan.""" raise NotImplementedError() # pylint: disable=arguments-differ - def async_turn_on(self: ToggleEntity, speed: str = None, **kwargs): + def async_turn_on(self, speed: str = None, **kwargs): """Turn on the fan. This method must be run in the event loop and returns a coroutine. @@ -275,11 +275,11 @@ class FanEntity(ToggleEntity): return self.hass.async_add_job( ft.partial(self.turn_on, speed, **kwargs)) - def oscillate(self: ToggleEntity, oscillating: bool) -> None: + def oscillate(self, oscillating: bool) -> None: """Oscillate the fan.""" pass - def async_oscillate(self: ToggleEntity, oscillating: bool): + def async_oscillate(self, oscillating: bool): """Oscillate the fan. This method must be run in the event loop and returns a coroutine. @@ -297,7 +297,7 @@ class FanEntity(ToggleEntity): return None @property - def speed_list(self: ToggleEntity) -> list: + def speed_list(self) -> list: """Get the list of available speeds.""" return [] @@ -307,7 +307,7 @@ class FanEntity(ToggleEntity): return None @property - def state_attributes(self: ToggleEntity) -> dict: + def state_attributes(self) -> dict: """Return optional state attributes.""" data = {} # type: dict @@ -322,6 +322,6 @@ class FanEntity(ToggleEntity): return data @property - def supported_features(self: ToggleEntity) -> int: + def supported_features(self) -> int: """Flag supported features.""" return 0 diff --git a/homeassistant/components/fan/dyson.py b/homeassistant/components/fan/dyson.py index 5b689ece6ed2..fbe9ffc948cd 100644 --- a/homeassistant/components/fan/dyson.py +++ b/homeassistant/components/fan/dyson.py @@ -8,12 +8,11 @@ import logging import voluptuous as vol +import homeassistant.helpers.config_validation as cv from homeassistant.components.dyson import DYSON_DEVICES from homeassistant.components.fan import ( DOMAIN, SUPPORT_OSCILLATE, SUPPORT_SET_SPEED, FanEntity) from homeassistant.const import CONF_ENTITY_ID -import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import ToggleEntity _LOGGER = logging.getLogger(__name__) @@ -100,7 +99,7 @@ class DysonPureCoolLinkDevice(FanEntity): """Return the display name of this fan.""" return self._device.name - def set_speed(self: ToggleEntity, speed: str) -> None: + def set_speed(self, speed: str) -> None: """Set the speed of the fan. Never called ??.""" from libpurecoollink.const import FanSpeed, FanMode @@ -113,7 +112,7 @@ class DysonPureCoolLinkDevice(FanEntity): self._device.set_configuration( fan_mode=FanMode.FAN, fan_speed=fan_speed) - def turn_on(self: ToggleEntity, speed: str = None, **kwargs) -> None: + def turn_on(self, speed: str = None, **kwargs) -> None: """Turn on the fan.""" from libpurecoollink.const import FanSpeed, FanMode @@ -129,14 +128,14 @@ class DysonPureCoolLinkDevice(FanEntity): # Speed not set, just turn on self._device.set_configuration(fan_mode=FanMode.FAN) - def turn_off(self: ToggleEntity, **kwargs) -> None: + def turn_off(self, **kwargs) -> None: """Turn off the fan.""" from libpurecoollink.const import FanMode _LOGGER.debug("Turn off fan %s", self.name) self._device.set_configuration(fan_mode=FanMode.OFF) - def oscillate(self: ToggleEntity, oscillating: bool) -> None: + def oscillate(self, oscillating: bool) -> None: """Turn on/off oscillating.""" from libpurecoollink.const import Oscillation @@ -183,7 +182,7 @@ class DysonPureCoolLinkDevice(FanEntity): """Return Night mode.""" return self._device.state.night_mode == "ON" - def night_mode(self: ToggleEntity, night_mode: bool) -> None: + def night_mode(self, night_mode: bool) -> None: """Turn fan in night mode.""" from libpurecoollink.const import NightMode @@ -198,7 +197,7 @@ class DysonPureCoolLinkDevice(FanEntity): """Return auto mode.""" return self._device.state.fan_mode == "AUTO" - def auto_mode(self: ToggleEntity, auto_mode: bool) -> None: + def auto_mode(self, auto_mode: bool) -> None: """Turn fan in auto mode.""" from libpurecoollink.const import FanMode @@ -209,7 +208,7 @@ class DysonPureCoolLinkDevice(FanEntity): self._device.set_configuration(fan_mode=FanMode.FAN) @property - def speed_list(self: ToggleEntity) -> list: + def speed_list(self) -> list: """Get the list of available speeds.""" from libpurecoollink.const import FanSpeed @@ -230,6 +229,6 @@ class DysonPureCoolLinkDevice(FanEntity): return supported_speeds @property - def supported_features(self: ToggleEntity) -> int: + def supported_features(self) -> int: """Flag supported features.""" return SUPPORT_OSCILLATE | SUPPORT_SET_SPEED diff --git a/homeassistant/components/fan/insteon_local.py b/homeassistant/components/fan/insteon_local.py index 1a5e8124dfc5..28b93c86ed7f 100644 --- a/homeassistant/components/fan/insteon_local.py +++ b/homeassistant/components/fan/insteon_local.py @@ -7,11 +7,10 @@ https://home-assistant.io/components/fan.insteon_local/ import logging from datetime import timedelta +from homeassistant import util from homeassistant.components.fan import ( ATTR_SPEED, SPEED_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH, SUPPORT_SET_SPEED, FanEntity) -from homeassistant.helpers.entity import ToggleEntity -from homeassistant import util _CONFIGURING = {} _LOGGER = logging.getLogger(__name__) @@ -68,7 +67,7 @@ class InsteonLocalFanDevice(FanEntity): return self._speed @property - def speed_list(self: ToggleEntity) -> list: + def speed_list(self) -> list: """Get the list of available speeds.""" return [SPEED_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH] @@ -91,18 +90,18 @@ class InsteonLocalFanDevice(FanEntity): """Flag supported features.""" return SUPPORT_INSTEON_LOCAL - def turn_on(self: ToggleEntity, speed: str = None, **kwargs) -> None: + def turn_on(self, speed: str = None, **kwargs) -> None: """Turn device on.""" if speed is None: speed = kwargs.get(ATTR_SPEED, SPEED_MEDIUM) self.set_speed(speed) - def turn_off(self: ToggleEntity, **kwargs) -> None: + def turn_off(self, **kwargs) -> None: """Turn device off.""" self.node.off() - def set_speed(self: ToggleEntity, speed: str) -> None: + def set_speed(self, speed: str) -> None: """Set the speed of the fan.""" if self.node.on(speed): self._speed = speed diff --git a/homeassistant/components/fan/template.py b/homeassistant/components/fan/template.py index e72d82eff087..74fb73dae1d2 100644 --- a/homeassistant/components/fan/template.py +++ b/homeassistant/components/fan/template.py @@ -8,21 +8,17 @@ import logging import voluptuous as vol -from homeassistant.core import callback -from homeassistant.const import ( - CONF_FRIENDLY_NAME, CONF_VALUE_TEMPLATE, CONF_ENTITY_ID, - STATE_ON, STATE_OFF, MATCH_ALL, EVENT_HOMEASSISTANT_START, - STATE_UNKNOWN) - -from homeassistant.exceptions import TemplateError import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.config_validation import PLATFORM_SCHEMA -from homeassistant.helpers.entity import ToggleEntity from homeassistant.components.fan import ( SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH, SUPPORT_SET_SPEED, SUPPORT_OSCILLATE, FanEntity, ATTR_SPEED, ATTR_OSCILLATING, ENTITY_ID_FORMAT, SUPPORT_DIRECTION, DIRECTION_FORWARD, DIRECTION_REVERSE, ATTR_DIRECTION) - +from homeassistant.const import ( + CONF_FRIENDLY_NAME, CONF_VALUE_TEMPLATE, CONF_ENTITY_ID, + STATE_ON, STATE_OFF, MATCH_ALL, EVENT_HOMEASSISTANT_START, + STATE_UNKNOWN) +from homeassistant.core import callback +from homeassistant.exceptions import TemplateError from homeassistant.helpers.entity import async_generate_entity_id from homeassistant.helpers.script import Script @@ -65,7 +61,7 @@ FAN_SCHEMA = vol.Schema({ vol.Optional(CONF_ENTITY_ID): cv.entity_ids }) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ +PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA.extend({ vol.Required(CONF_FANS): vol.Schema({cv.slug: FAN_SCHEMA}), }) @@ -196,7 +192,7 @@ class TemplateFan(FanEntity): return self._supported_features @property - def speed_list(self: ToggleEntity) -> list: + def speed_list(self) -> list: """Get the list of available speeds.""" return self._speed_list diff --git a/homeassistant/components/fan/wink.py b/homeassistant/components/fan/wink.py index 0cebd9cb9f83..4eebacbbbf2e 100644 --- a/homeassistant/components/fan/wink.py +++ b/homeassistant/components/fan/wink.py @@ -11,7 +11,6 @@ from homeassistant.components.fan import ( SPEED_HIGH, SPEED_LOW, SPEED_MEDIUM, STATE_UNKNOWN, SUPPORT_DIRECTION, SUPPORT_SET_SPEED, FanEntity) from homeassistant.components.wink import DOMAIN, WinkDevice -from homeassistant.helpers.entity import ToggleEntity _LOGGER = logging.getLogger(__name__) @@ -39,19 +38,19 @@ class WinkFanDevice(WinkDevice, FanEntity): """Call when entity is added to hass.""" self.hass.data[DOMAIN]['entities']['fan'].append(self) - def set_direction(self: ToggleEntity, direction: str) -> None: + def set_direction(self, direction: str) -> None: """Set the direction of the fan.""" self.wink.set_fan_direction(direction) - def set_speed(self: ToggleEntity, speed: str) -> None: + def set_speed(self, speed: str) -> None: """Set the speed of the fan.""" self.wink.set_state(True, speed) - def turn_on(self: ToggleEntity, speed: str = None, **kwargs) -> None: + def turn_on(self, speed: str = None, **kwargs) -> None: """Turn on the fan.""" self.wink.set_state(True, speed) - def turn_off(self: ToggleEntity, **kwargs) -> None: + def turn_off(self, **kwargs) -> None: """Turn off the fan.""" self.wink.set_state(False) @@ -82,7 +81,7 @@ class WinkFanDevice(WinkDevice, FanEntity): return self.wink.current_fan_direction() @property - def speed_list(self: ToggleEntity) -> list: + def speed_list(self) -> list: """Get the list of available speeds.""" wink_supported_speeds = self.wink.fan_speeds() supported_speeds = [] @@ -99,6 +98,6 @@ class WinkFanDevice(WinkDevice, FanEntity): return supported_speeds @property - def supported_features(self: ToggleEntity) -> int: + def supported_features(self) -> int: """Flag supported features.""" return SUPPORTED_FEATURES diff --git a/homeassistant/components/fan/zha.py b/homeassistant/components/fan/zha.py index 01b1d0a92cf8..983bc3a79d75 100644 --- a/homeassistant/components/fan/zha.py +++ b/homeassistant/components/fan/zha.py @@ -89,7 +89,7 @@ class ZhaFan(zha.Entity, FanEntity): yield from self.async_set_speed(SPEED_OFF) @asyncio.coroutine - def async_set_speed(self: FanEntity, speed: str) -> None: + def async_set_speed(self, speed: str) -> None: """Set the speed of the fan.""" yield from self._endpoint.fan.write_attributes({ 'fan_mode': SPEED_TO_VALUE[speed]}) diff --git a/homeassistant/util/__init__.py b/homeassistant/util/__init__.py index ff098b24fb88..64c9f4f02c99 100644 --- a/homeassistant/util/__init__.py +++ b/homeassistant/util/__init__.py @@ -126,25 +126,25 @@ class OrderedEnum(enum.Enum): # https://github.com/PyCQA/pylint/issues/2306 # pylint: disable=comparison-with-callable - def __ge__(self: ENUM_T, other: ENUM_T) -> bool: + def __ge__(self, other: ENUM_T) -> bool: """Return the greater than element.""" if self.__class__ is other.__class__: return bool(self.value >= other.value) return NotImplemented - def __gt__(self: ENUM_T, other: ENUM_T) -> bool: + def __gt__(self, other: ENUM_T) -> bool: """Return the greater element.""" if self.__class__ is other.__class__: return bool(self.value > other.value) return NotImplemented - def __le__(self: ENUM_T, other: ENUM_T) -> bool: + def __le__(self, other: ENUM_T) -> bool: """Return the lower than element.""" if self.__class__ is other.__class__: return bool(self.value <= other.value) return NotImplemented - def __lt__(self: ENUM_T, other: ENUM_T) -> bool: + def __lt__(self, other: ENUM_T) -> bool: """Return the lower element.""" if self.__class__ is other.__class__: return bool(self.value < other.value) diff --git a/homeassistant/util/unit_system.py b/homeassistant/util/unit_system.py index b8fb393a2f32..5a8f515c3adf 100644 --- a/homeassistant/util/unit_system.py +++ b/homeassistant/util/unit_system.py @@ -65,7 +65,7 @@ def is_valid_unit(unit: str, unit_type: str) -> bool: class UnitSystem: """A container for units of measure.""" - def __init__(self: object, name: str, temperature: str, length: str, + def __init__(self, name: str, temperature: str, length: str, volume: str, mass: str) -> None: """Initialize the unit system object.""" errors = \ diff --git a/tests/components/climate/test_honeywell.py b/tests/components/climate/test_honeywell.py index b12c0c38f3ae..69df11715e91 100644 --- a/tests/components/climate/test_honeywell.py +++ b/tests/components/climate/test_honeywell.py @@ -320,7 +320,7 @@ class TestHoneywellRound(unittest.TestCase): self.device.set_temperature.call_args, mock.call('House', 25) ) - def test_set_operation_mode(self: unittest.TestCase) -> None: + def test_set_operation_mode(self) -> None: """Test setting the system operation.""" self.round1.set_operation_mode('cool') self.assertEqual('cool', self.round1.current_operation) @@ -384,7 +384,7 @@ class TestHoneywellUS(unittest.TestCase): self.assertEqual(74, self.device.setpoint_cool) self.assertEqual(74, self.honeywell.target_temperature) - def test_set_operation_mode(self: unittest.TestCase) -> None: + def test_set_operation_mode(self) -> None: """Test setting the operation mode.""" self.honeywell.set_operation_mode('cool') self.assertEqual('cool', self.device.system_mode)