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 182367f3890..a84c278350f 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 37a6805dfb5..c8eba41e66b 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 5ee2cd56081..71f3374f0c0 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 00000000000..fc02009b7af --- /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 0b059379c11..79b14c94b88 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 475e43e55a4..c6a414b9d91 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 cde7060df74..c6a629c1278 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 9f342a50407..cc2f244efb4 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 6518c2bcc1c..84287c2e425 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 61def9075f8..89233b6c518 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 5f3a2d6478c..46b88e46b4d 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 237a6d219f0..5e24fe82340 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 c6a629c1278..cea2ddf9be2 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 17a56bc5f42..b359f67d77f 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 89233b6c518..958247cadc5 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 00000000000..6dea5919f09 --- /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 00000000000..3aa106ac18c --- /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 00000000000..1a536a1bc43 --- /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 00000000000..62c6dc929a1 --- /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 00000000000..57a81a78da3 --- /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 00000000000..d6a4030190d --- /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 958247cadc5..141da89f359 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 1a536a1bc43..17d83003c48 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 cea2ddf9be2..f524dc28a8b 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 08909b3bf6f..ad3877392c7 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 deaa118f51c..b6d582b7793 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 447f4e1e56a..6fe891b6b24 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 b637858303e..554f877d0bd 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 2c49b25a39d..50501025f0c 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 032d85637ef..b3043689f8c 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 01ae2977f6d..12e1cb0099a 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 f265014657b..94e3b407d13 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 f350ea56bb4..b7da671bb15 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 a40437e719b..e72d82eff08 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 73ab9e8123c..56f153517cf 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 cb9387fb2c0..81f66880943 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 373c1188f2d..d4c2cb58209 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 38cac649a1a..ad77b734fbb 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 37a50b39e95..97b9b64c7cb 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 283c4af032e..ec878e5c043 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 90638cd9dfc..30e307fd117 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 c3de341d607..55b3fb0ea4f 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 59a2dc861a6..eda09e3af64 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 609887e9690..eeb0c966ab4 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 e045043e09c..2da5cb5cdf0 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 b443bd56f03..304489f99b7 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 398c0b350ee..1fd556b17c0 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 1048c04d43d..3eabce9458c 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 f1f8adab062..6f50a57b3ab 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 3628765293b..249a69578db 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 20d00267dee..df181c67c03 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 3d9704875c9..97cf948ff9f 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 e3c3a0cf5ca..e70dd482f1e 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 9742bc25c63..52d4165f3fa 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 7c171d74967..a8acc437546 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 921b3bcf06b..504c9d4b067 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 555802f9a2c..8315de34e06 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 59e99e5c1b5..5d48fd88127 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 2afa943ee50..d9206d62250 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 717a3f62ec9..435d3a00ec2 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 00000000000..9a114c243b6 --- /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 c557774b5f1..ccb5227aead 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 5266b9c6f57..346f381db34 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 f524dc28a8b..a39a2cada31 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 480786b2c2c..c2045c2df5e 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 4a26a1dc9fc..2003b1b41c9 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 d01b62e4c12..07389c7c8a9 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 bc073a04c47..3104ce897a1 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 496308598dc..32e26ad7f7b 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 a190aea9fa8..d832dda4754 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 d9206d62250..230a10498f3 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 2e5613057f1..0fc66174c66 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 8b534bf1731..bfc3aed194f 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 e51ba4d9718..24dcb46bb68 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 d5c629c9d34..17329fbddff 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 478320dca27..45482d5d14e 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 bbf0f7e11e2..c52bfa584da 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 b3aa370da2e..334b4f45483 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 e390b537d34..9fc87b24a9b 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 10b43445184..65999a3a5c7 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 4cc0fff96b9..ec17c1b796b 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 298d52722a5..ddf7eb44601 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 3970ea72d47..5a597994d6b 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 32e26ad7f7b..7a5345a1a73 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 54cd569aceb..ed489ed858b 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 52e6b1e7703..2b0f9ed18e4 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 6988e20fb5f..ce94a560dae 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 0c57c98c0af..d909eb3c0f2 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 00000000000..97d96623d88 --- /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 334dd9a0bec..9086df6e79b 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 c080a136c08..d2366e5836c 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 895f5edd5da..e313f96fb7c 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 9f2a4176ed8..d6198301d76 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 172a368225d..60c33ca9b0e 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 f932f239969..e3f327f1d5c 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 785b178969f..690d1651db9 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 00dc588a468..1a45235f15a 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 d2533eb8f5b..cb943ac3f18 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 4072f4ae234..31a518dc1dc 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 1abfa25c82b..a1126bdd2f9 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 e6eff0d9bb5..a589ab4e8c8 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 35566b0cbed..9d105fb02d0 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 71a6a17de10..42ab7b01c63 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 b2a27230a02..dc991644b8e 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 ab26df5caf0..fcd9b7e8a07 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 90ef08c24fe..be59a1c1f50 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 565e913319f..6c340e4a5f0 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 7d392e5a40f..e567340efc9 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 fbe5460979b..1426ff31af9 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 62e1069e18b..e1775e2f968 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 aefb7ab89d7..5079a3b60c2 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 384f96f3f52..2f6951cfc0d 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 ac4fddf98bb..04bf0ef1d32 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 64ce3cda073..c2c7866148f 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 580c0272e46..b95a0a9d73b 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 2e2d9b10d98..b5031e8ccfb 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 9a5532fc9f4..7e5ae7c9227 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 e99524c36db..2d7f1e80406 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 2b74984e4ca..f7fbe2e15e3 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 b8a5c99add4..1a5e8124dfc 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 6fa506edec6..5faa735801d 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 81f66880943..32d0956e878 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 6a43a0c6228..c2f8951c8d8 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 0a4ad66ce56..9497282ab21 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 bda0e1bc550..0b57dba8bca 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 8984f25cdf2..9479a804a44 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 837a6f82510..0da59b6f100 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 8ba2329af7e..cbac8cf4e20 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 bd7814df8f3..e2bc54de517 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 2ebe766c8c5..b8491b6f0f5 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 09f0a337cc3..29186b8fcd2 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 c0e363f85d6..cfc1ce27b94 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 705e106fdff..d17c7dd73bf 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 f6b3fbe8b70..ffa73aca915 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 cdfe2fe5671..293783ee3ab 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 214a2d99449..3d567afe09e 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 4cd34b698da..4c912d60fb7 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 d8af22cd5c3..45029e679a5 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 ccb371de2f8..8096a4fabb7 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 ec878e5c043..c96889f4fe4 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 4b0f9d0cf21..9be4143ef2b 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 7fa8d5b3fe8..0585692e77d 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 df1ee662124..955ba7ccb32 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 81e4c3541d3..203a2c2b408 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 42d0ae85ab3..362095daee6 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 3916714b8d1..1cbe099ddde 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 a7719a80d99..73222cb6be2 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 842dce087e8..a63b7325035 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 78d277ca65f..829a038953c 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 42d4ce77054..dc37eb760f7 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 5cb7bb337ce..824ec46d636 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 37fb58d8dc7..87539e2dded 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 f49d8e76f6c..d4d8ea09d29 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 7308cd4f791..6b0d3e569d7 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 b22e4df9a50..c5c26bc5ffa 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 261f6e2b510..df913ab6d80 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 e7d25872701..d14a70ecc84 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 c070a3e990f..388af9b3ce7 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 93e15b9cd5e..120fe8fdb22 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 7af858b9d94..f1f12b6ecab 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 c4014fbd1dd..5f404ccd5f7 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 2822ce01dca..62d8af2ee8f 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 997fd312a6a..0e29c55d39d 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 2c0f8eb4d5a..2a61c1143ee 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 9784cc3dc4c..c30f1575049 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 a5a6eb5f07b..b410e7e860a 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 b04b7727e40..f06f6a896e7 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 de929aa0942..2fc67e57162 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 83cf799e3cd..0b936d8c8c7 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 cecce0d270f..a8875f6904c 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 79b14c94b88..70c169a1b7c 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 baeed391557..21a3030b79b 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 1189a53bb09..60b6a018fc2 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 4f50c6beaaa..34290819106 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 1547f4f1dee..2bcb04c566e 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 5412f559b73..94952ac736b 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 4456436ea61..c4c8a854670 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 1e7c46733ad..79ef4a5fd7f 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 ca70c212774..94e1d7ea6d6 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 1075888e199..4442b186e30 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 57fa4b00c98..4e5f1c47e9f 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 e25368f3c5c..06f2ee5f550 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 68e91612008..17b5c8e40d5 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 26de2a78e18..300af4be61d 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 3031b1e0290..dad0c7c59ba 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 8b2734612de..adc08a65e71 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 37c2f52e228..0753435cfba 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 c18ad492d40..c189c312b25 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 adf3bf2d9bd..fa32843eb4b 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 a21d44527a1..9a876a87683 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 8c2f110257f..fd80f4cdbfb 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 bfc3aed194f..dac16111de7 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 7ab90b7a048..e10d608fc62 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 69b1bf21c08..1924de88aaf 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 9a5b4dd1a43..d92502de078 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 b8a97607215..bd428a84bed 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 cfc1ce27b94..09fa094c1b2 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 26b949720d9..901535c5465 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 7d6dd65e90a..404d60c0a2e 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 e1c3da50e7e..1cf09f2ccb5 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 893fa76c44b..43cb4494978 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 94fe5f40be8..bb21e1df3d5 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 87021e9c7c5..7292e3b2f40 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 fc407de0a6b..63b0f61bb7c 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 6289fed265d..224e763a097 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 a39a2cada31..8ef15e4bd18 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 fc2e7fc912d..f88b911a6a5 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 00000000000..efcfb629f39 --- /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 ba91dd7c1fc..1cbc81709c4 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 9cb87b31749..25fc3fb5d73 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 82fc6b02266..ef631223894 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 8ef15e4bd18..1ef32386148 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 a635d1820db..b1c7a650843 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 1ef32386148..970a8c134ee 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 141da89f359..5a3ac0d16b5 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 970a8c134ee..051fc0d0dd0 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 ad3877392c7..01f90202e8f 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 fc02009b7af..4f528cfcb51 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 8f3949046ca..1685d34c0ec 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 84287c2e425..435555c2e31 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 ce94a560dae..36ce1c392f9 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 c8eba41e66b..0cbee628a8a 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 0a37f22867e..b01e68f701d 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 3de276564eb..23698af8101 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 46b88e46b4d..1d3719b8c66 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 1617f327d27..c99d273a458 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 27367b4173e..523d4943ba0 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 2b966daff6c..b5eed19eb61 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 04d9cc450ba..b222d78b577 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 6808206243f..5a9efd5c041 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 fca03ad8fa9..960e8f5e7b4 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 fa4ab612bb1..cd2cbc44539 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 5a3ac0d16b5..dc5d1d7bf7e 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 051fc0d0dd0..36ad3cdc81d 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 01f90202e8f..e1e1593b814 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 b4906acba3c..f818df4bd71 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 36ad3cdc81d..309a8c7c1b6 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 cc2f244efb4..62c416a9883 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 3769248fc05..68cc1c7edd2 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 b359f67d77f..d24110a4736 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 e06b16177a1..c86c8eb71f1 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 57c05e3bdc8..1f92fb60f13 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 fea523c4117..d141faa4c27 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 08fb63a3c72..9db6293d98a 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 8e8c9738756..b472e4c95df 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 007e37b90c4..71642bd7a32 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 1d3719b8c66..807bf15854b 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 1320be299b8..f6c027150dd 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 3aa8e82911e..3066819638f 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 309a8c7c1b6..ecd28ed7170 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 bdc2c5990d9..54b095bb84b 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 6d5feb87dc2..bafbc0781ca 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 d603843f51f..100444c0211 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 b6d406bd550..8d4520d74e8 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 9b7da71a293..80fa4ccb4bf 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 820ca41ad2e..bcd0c38c3bd 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 7e2b4cda28f..8808cee79a3 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 8625685c057..785f8c57f94 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 0b5e7c1e1d7..e273d7d6f6a 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 73751ef14bb..0366f753ba6 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 772792f5785..0c33877854f 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 f9ff4ac0a7a..de6ad8223d7 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 d35c36a012e..d0654317248 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 0830d86dc2a..bb597f208e6 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 c0f6ca3f112..5b8e133b5f4 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 fc18648f907..784a96d8615 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 a44f0163787..e84643711eb 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 bc9d3acf54f..00377b3f12b 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 a7ed262ac2c..061b09c1b3b 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 9c30d1481f8..3db24790aaf 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 53129d3316c..0bf09f6f2c7 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 279fb1e2694..925bbcacddf 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 42ab7b01c63..ba1f60027ba 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 4d0fbe617b2..04c33d83f3d 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 a4b921037db..431834151fd 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 52c544256b6..b1dcacb5654 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 c40bd99b542..154320b4abd 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 425ed6f9c9a..69d88274f29 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 2c159633a9b..56fb7b4247b 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 c29c11c5b6b..d5de8863543 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 5808528ca5a..8983ecf82d8 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 88174b9d612..414d4b0766c 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 b95a0a9d73b..391f36ad623 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 607f236f920..4fcc550d7db 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 f36afc622ee..02a12653180 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 b278c421925..2cac81fd405 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 5f06946fc44..d0669ab4967 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 6a0cb18d55e..d09e1930d4f 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 ba9bc8c2631..c08ddb4047b 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 6df9f3c9974..526c1a4b47b 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 84fb449c070..08d3a4c9445 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 28b3a05e403..0f275a7fe66 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 a0f50842649..c0c9d95586c 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 96f094b527d..3829c2caebd 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 36ce1c392f9..8a67b933b9f 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 364306ff8dd..645cb033e13 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 56f153517cf..782fd8ac8dd 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 e083affe92b..9aaae16ee21 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 25bcb5b0f79..0d4b19da030 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 fdbc3382072..e37b3ba7ff7 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 c3caf40ba62..5410cb9ec1a 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 7ee1c70487f..21d4cdc6e56 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 a4e3e78e860..57bb9488ee8 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 0cbee628a8a..42629f752ad 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 fe8b7db84d1..7bb5194b1ec 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 23698af8101..34b72e9ed69 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 8710b2561b0..f528de462ef 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 a60e3d5b8fc..0c4db63034e 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 ef631223894..7845b47e218 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 bbd7bc44082..9a7cc7caecb 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 61f8ca90137..f8db21118ec 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 26fe356d772..a3e9ff86ed0 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 8e9d93657ce..52a70532005 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 9b2c183c1d1..3738fd8f004 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 182d7536dc4..3ad75a1cea4 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 f468e8c25ef..55feef496f8 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 962e30774b8..c98ef16c7ed 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 eb2e8391221..b9970e3466e 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 8ff3746889e..6a648e4dc47 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 b2805c994e8..5f6c30aaeba 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 bca7a1b4ab7..b574f0bcb15 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 21accdf84b3..2ecee06d8a3 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 4e24d5f2f71..32ceadf248f 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 280a84f0828..979aec57c74 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 ca0979f1752..e5f7a2f9432 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 371ad890364..8eb4c85f6b2 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 847f4131f43..e0e0e716d2e 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 9f53f84e020..7e6738b95f8 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 fc6db96e029..f484cb31a6c 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 55d99a0817e..3928eb945aa 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 3531c6b4919..292e56418fc 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 b0770f90c1d..3ae99f61d17 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 63b0f61bb7c..6eabfc713bc 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 58fa1953ef0..23a331b21b2 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 b1c7a650843..c25b57fbd62 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 41198d1f296..13cd6203ed4 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 9cca81e1485..8fabfc3aefb 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 e7ab86a5f35..0f8fbb39073 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 c1059227f7a..bc936265f6f 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 344c750c0ec..d307a428e0e 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 048851e97f5..84dc8402742 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 0a6c959f243..da986f024a4 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 3a804c50c74..90854efe8b1 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 a04f4926b76..53cd8e79d7e 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 22fc427ccce..b6546f2e67b 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 41480c09a32..f43263bf4bf 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 98cd937de3c..a94e8e95c6f 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 a7193b40949..dcea69cbb48 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 0002274833f..403722c7b6a 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 19860ba84fd..751f0f11171 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 3689e94b05d..d24621becc9 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 bd23b9850f7..f51b7dcd5bd 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 5cec528d26a..eb63e116254 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 8806fae5974..06d7f512c9f 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 10a96ded437..e366a83a65d 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 f4b666f1e5c..c4f38b1be02 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 2003b1b41c9..1db7c58d328 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 6d55853d724..846b109afca 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 f5d6f278da0..0834bbc99d8 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 e75f36d59f7..2e57e1bd447 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 ec9b14883a9..0e6ab164d4f 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 c5c26bc5ffa..6770594b919 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 f6d791f9fd6..a443c78b2b1 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 e023dfcc49f..4f9664617a3 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 cca06bd9782..065c88d8332 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 d7b867081a3..218968ecee8 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 978b8db669a..4c209d17d07 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 cd02137f4d5..a62eaba7df8 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 265350f3e95..bf4ee55c446 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 87c301d34f5..bf868d49201 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 9143ccaf23f..65474cd4bf6 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 a2ee18b3659..4f724b5b851 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 df913ab6d80..15059b08a17 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 13129919139..ef064e84228 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 438366ae555..5a6f8da79b2 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 304489f99b7..e42be861d37 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 94f3f1884d1..9e32bf27c29 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 c8c4db17c8d..b79e6e69adf 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 bd6e91c7b53..a6dfd89e45a 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 388af9b3ce7..cc5461ed548 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 c1fe7ab4880..bc79c4d0c1d 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 006542a777f..f8afe9c7637 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 acd10fe08af..98ee83f8958 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 2195153ab1e..db75d51fbad 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 c0c9bf62efd..a1a604df3e4 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 c0d492984e0..8bfbaf49837 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 603d82359de..5febebeec87 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 2ffbb914275..bbb5993b064 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 fe13c0db8a7..4619eda0611 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 9952e2a1c24..c9bc7205ce6 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 c2f6412049c..57e5f1c6b02 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 b6366de6432..ec3d3f47ba7 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 cd559d3bbd2..60f6598ab21 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 3e1887cfd59..81c48555cfc 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 81c7173e4d0..95f2845eced 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 54b095bb84b..bc0cbc5eea6 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 2d08159967c..488b1611399 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 5e3bf55dc9d..fd8b8d2aeec 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 2440dac3204..5f677d39888 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 7c7ff3480b0..ff0cd8ef891 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 0fa6362ad05..a8dda416a54 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 741ffa2842d..5e8231bb124 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 1b5867836bc..dc221f6b07d 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 96db4430d32..6241f3e5378 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 2adf5691e2e..363ada725ba 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 c11c83ab40e..ea68a902c48 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 cc4ce1e6448..a5593c259a5 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 97cf948ff9f..a44823d86ba 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 75235bedaab..8db48719a37 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 5eee9053db5..16f4ccb9b6c 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 a50f4cdfd2c..f0e566f718f 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 bc3e127508b..541abea3091 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 e1a7f3c9e5f..2aeff8e73d8 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 da8f3fcc639..408435a9667 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 95bf207acf8..0ba74f0e7ed 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 bf2868d3b01..8c1ffc03786 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 aefbc2d4626..374e14c5ac2 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 63d500e2373..b4536b48c9e 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 e70dd482f1e..124cec97617 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 9a85eb25575..6b2284f4376 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 c2ef1d4c6b9..7298181796a 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 0bf1ef4caff..68a1cfc4fe1 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 28a3b48892b..0f27b656404 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 4dac411d224..3e74b454913 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 e80fe7d2d82..cd476a1a226 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 24c45ec1ff3..76c5d2f648e 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 7f2df4bcda9..24ae2d0068f 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 8c2cfd9923f..82cb7f845dc 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 c7ff967723b..fcddf41af97 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 db66419e54a..b2279e107da 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 df5ff5e8d37..e8e5fdec4d8 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 b2a913c2af8..d2166dde64e 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 df36eef2f9e..4d4ecf0160b 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 b35cd8cf5a8..2ee0080a06d 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 4e62b711979..01d27b8abcd 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 46002112177..6b754effaf1 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 f3af70c6222..29e6771d1d5 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 1c7253c4ec3..9ce324ef6bb 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 9968f631260..9c0f852846a 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 4e5f1c47e9f..7ffce13ff6a 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 adc08a65e71..b549f351afc 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 8a0a1683aa4..31f942bd3af 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 dfb4b1e5fa9..bb235647d1b 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 ffb820e8148..cfd0d297d54 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 999b584360c..f8a521c3e2f 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 9de7f6c4444..0f503dcdc39 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 364562f1119..41aa240492b 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 b367752c247..1f26ab639d6 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 59fc707bb28..b28189444ee 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 7afa97fd4f6..6dac22bc941 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 65fa7c8cb0f..0c876d9e2d7 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 f9befece5a4..3f12195d6bf 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 3a84e963841..67bdf744251 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 dac16111de7..947ab78b673 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 7dc5d2524ec..c7e88b210b3 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 4ac3a147296..72b6ceecbfd 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 472a88888d8..0847c116954 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 19980394d26..5caa6b93131 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 72deabaae28..b2360f3aca5 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 f523726c388..4d09416398b 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 ae932b7d955..b8e6a862b46 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 c52bfa584da..a1c0fb0024c 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 65999a3a5c7..7ce98fc2f2a 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 ec17c1b796b..b8fb393a2f3 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 b03d473e6f3..3e8e813164e 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 afa4d19b5d9..ce4ec5aa146 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 aadafadd4a6..da9350008d8 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 af7e856e417..893745ce3de 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 d0f1425a595..cf902ca1779 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 e45d70bc172..6294ba3467a 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 628c5405eaa..ab2e3be11d6 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 b743dee9704..acc2519c9b7 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 c2ab5684ed0..8797f661767 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 50060e08a4b..65e735a6f7e 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 fc40f8e17fb..38e94166c5a 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 7c85775949c..550bfe88a61 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 399cdc67ca6..14e1769047a 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 e17241485db..980284737a2 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 318f3c7512c..486300679b7 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 5a5fdffd5a3..44c3a1dd695 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 370059a0a09..92f840b8033 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 48bc04d46ed..38924817980 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 2ed2f4487ea..4cf79e679cd 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 ee1372509d9..95167dd181b 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 3ddcfae8c01..097fb799d40 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 6a2d2c65035..cf9a7b2db29 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 b6bfa430fd2..e8746ee762f 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 b55236c5e8e..7ec2ae39cd6 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 2abdc0e69ff..365cf1ff73b 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 5b4ef4dcf53..82d20318928 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 066e7386c6e..55e67def2bc 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 4211e3da31b..4981ad23cc0 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 60b0e68ca59..1f43c5a4b49 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 00000000000..f19a9e5a5f7 --- /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 ccb5227aead..337071baf69 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 7bb5194b1ec..e05f951322e 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 34b72e9ed69..f3d3cd06e22 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 6cd16909041..98e3057338a 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 c5691cf3e2a..a6a07928113 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 dc5d1d7bf7e..68e88406ad6 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 ecd28ed7170..75a563c244f 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 e1e1593b814..3de2285eae9 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 09a4fa3610d..669901f5b57 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 d314dec65ea..c475291227a 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 4442b186e30..eb91f8d846a 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 31f9a729c53..7cd5a42b4a3 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 23a01d37c2b..d6bd83dd41f 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 b4a3e2a1155..dac1f81ad23 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 75a563c244f..af60587e096 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 bc7f1910803..1377b2a6c3a 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 f0c8ec2d97c..fcc77d474e1 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 6754db05f77..f737e2ad7d2 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 af60587e096..0bb763a3fae 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 669901f5b57..9374c1418f0 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 eb54e7982a7..0cacdfe1539 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 68e88406ad6..fb59d6254b0 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 2125668facb..4a950910809 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 32d0956e878..ad2f8b4ac6d 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 d4e6d48c29f..a7e895f49e2 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 59da90cc75b..23706f02e75 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 cc0370f01b1..f8afb4a49ab 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 337071baf69..f55fe7a03b3 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 947ab78b673..f868c52cfb0 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 db53699379c..4d1f86059fc 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 3de2285eae9..aad851a93cb 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 87e85f09da0..736334c956a 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 20887157cb4..0f8913f85a0 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 2e0e9994e10..3cd44dcc84c 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 43cb4494978..79f872951db 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 209c5367c92..9941f70a2e4 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 2f2f89b9dfc..b2b7c45d410 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 e313f96fb7c..942d0dc159a 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 d6198301d76..c5408304018 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 f818df4bd71..b400a927b5e 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 80fa4ccb4bf..042d878fceb 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 2a7da86c6cf..eb25ee8fb08 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 435555c2e31..3eac6a370d2 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 2a7a3887b34..8b1cd3cad84 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 6b8ee577a09..74cf195bc61 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 7c3a3e1dd30..73a373a15ff 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 cc3079c6e53..59bf8a21064 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 288b46c2370..3cbd179154f 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 79c36fb2ef2..360671d1cea 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 39f520ddc6d..402feefa99f 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 b37be3f6cb6..00d2a95e356 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 be5d9a689d1..2a9746b4a01 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 22354b51956..e84377ee419 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 3c63e56b319..4cb218bc019 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 51c3bc89b05..5b39718939a 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 34a78e19f9f..1c7dc4c7ce0 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 e992020e2b2..b5306c31c84 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 e64c2d5000e..71878827153 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 b8fb7a984fa..a2725f6f3aa 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 9c005b62dcc..a0adc12bfbf 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 dc1f74613bc..fa3943c3e27 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 431834151fd..b4bed367878 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 34fcfd667b6..9338c219fe5 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 9a114c243b6..19267d693a0 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 c2b82e1cc84..4bacf64cf9e 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 4deb4d9ea2e..0f89b15e5a1 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 12a6960f833..15e555db8b9 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 b1dcacb5654..f87f2e83f5d 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 b1533bd68c8..b81ac4e45e1 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 d5de8863543..8c8c88ecb87 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 b67d32508be..a6f67506227 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 391f36ad623..74cb0a77fef 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 61eee99e721..142842b12d2 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 707850d2215..93bc9270650 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 3e17fdd3329..539d4fde5ef 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 adb5c6f6d28..7231c5127be 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 aee584aa953..354d3b0980c 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 9bbc6bf9ffe..c996b7e643b 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 0c289ce9a82..deab486ec6e 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 12e1cb0099a..718adad4212 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 2f60f226042..74ee55c5e93 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 e4626d0f016..2b768bc3786 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 6ab86435371..13bc6e9c558 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 73a29990fba..8517122f6a8 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 c2f8951c8d8..23a907d43f7 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 9e5356d914a..3be89172e27 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 2cc62dce38e..4c71504104e 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 cd07ab6df69..8b28a7cf288 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 af67a594495..a7fe3ff04e0 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 f8db21118ec..c6101230031 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 8b7e09d86bc..431f5d12ff0 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 c5cd9a8c4fd..2b53fb65054 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 b9970e3466e..c4fcf53a9c1 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 474751c2574..a74629917b3 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 97b9b64c7cb..d4a7ad19807 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 c96889f4fe4..a6b345b1d3b 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 464baed1686..07a379db45c 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 32ceadf248f..099b365c50b 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 41713e0c5bc..6ccc6061703 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 0758b5f3058..978a1088aa6 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 2b2b9eb5c28..654374de08a 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 0adb02b6a65..89547892550 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 4f9a4019268..1dfb19a33be 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 0585692e77d..8758e969db1 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 6b161f86ab0..1b5948c964a 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 ad8dd0bf056..773825e0d57 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 73417e5f25d..4b3dfc2ccbb 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 ca6b9722a49..e3c6f453c35 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 a46e781de59..5f28660f4bd 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 31b04ceb3cd..e9f8ab5f199 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 9c4a0e9fa17..8f14031481a 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 203a2c2b408..046aecbb92e 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 11ab1615617..c4ddd38fc4f 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 3066819638f..980efcf5805 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 73222cb6be2..a7de62cd329 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 b5c095f34b8..f97e0dc8ff5 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 d6bd83dd41f..7f54e6fd6f9 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 7529608387d..e280aa67e40 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 40b09dc3c72..dd35f986f78 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 899ccf9b09a..b012506acd9 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 e38e7fcaa0f..6076cd5393a 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 bc936265f6f..ff52ad94d8b 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 90854efe8b1..7162913087d 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 43c2aa5c7b1..60df6327009 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 32d6291b90c..e7948446231 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 272a5b868ec..b8af971b3ff 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 6fe91d0acd2..a9ec1ef679c 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 b51ab288c1a..26247c77454 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 a8ef179280b..0d193dee79b 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 eeb0c966ab4..6d764b1c916 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 e366a83a65d..33c7e432fca 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 0834bbc99d8..4a7face0156 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 2e57e1bd447..b2bb7bb4da2 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 ee2292d4122..2a78d4ad864 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 d7982f1c9db..3a1bf1da39e 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 fd7c1aee3ae..5899ef267cb 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 857e6cc4a07..b980323abe1 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 1d270419933..f463d0fb8d1 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 f1f12b6ecab..c3d0fe8f1b6 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 8c9409ef5ff..2d609070415 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 3eabce9458c..19dcfc87014 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 ab6bd8270ce..f575768b505 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 62d8af2ee8f..319185923cd 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 d909eb3c0f2..50f4f72078c 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 ff0cd8ef891..7126bd89ef9 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 df181c67c03..9e62846e4d3 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 dc221f6b07d..1b345c752ff 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 a44823d86ba..8b25eb3de31 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 0ba74f0e7ed..5600f906f34 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 124cec97617..d431805ab19 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 737b3d08368..aa6314b8c5b 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 aedecfe61e5..6c6c296652a 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 048ca988e3d..123c11021b4 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 bfdf0c3c3aa..0668b5bdbce 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 4fc92db1d90..eaef3dcf7f7 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 343bcdf2033..78e8a7e76c6 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 05d61173da0..597a971e208 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 ddf506bf4eb..c49ce36bd49 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 3192d0d2f60..32139b21976 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 d2166dde64e..c6356efe157 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 c189c312b25..35ea435bf48 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 b9329a46b72..53695102601 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 ba8dc54b264..6ee42b32504 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 bb235647d1b..c2b7ba9ba0f 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 6557be2fb1b..0ce8870bedf 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 a8acc437546..c996572bf51 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 b62eeb67d32..312d72575a9 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 230a10498f3..d458ce44d37 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 504c9d4b067..930f68c3da4 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 0bd490940a9..056d45ad656 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 5a0b2ca56ea..4f412eb58e7 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 73a09464439..8b621b2f01c 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 712b48da0d7..c8488fa3334 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 f2ae36e7fd0..a139be4b260 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 e10d608fc62..8aa3b553f3a 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 b2360f3aca5..4a3f915e810 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 4d09416398b..ea620c9bccd 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 b8e6a862b46..7147fab1080 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 275a33627a9..6c6557897ee 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 45482d5d14e..0641a461130 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 a1c0fb0024c..6b539e99186 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 0f07a90e9bb..bae38f27ee2 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 913d6456906..6e2b378b218 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 ddf7eb44601..7ce16600e1b 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 d47437cb121..1e9e490adfe 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 3e8e813164e..314799b185b 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 ce4ec5aa146..cf8535653a9 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 2de0782fd91..18292d32a02 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 cce39ce43a7..0c20350a845 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 bdd921f395f..0705fb2c399 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 a1e3867f9c3..db8d7e5f1e1 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 a847de51142..8e7ef4348f7 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 31ec5ee7ed7..990414d7713 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 94eeafad7b1..0d6aca9d0b7 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 9e2050e850c..275bb4a1e8b 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 61e665f265c..155ed85dac2 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 280704fdc31..892fe5b5f4d 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 7a5345a1a73..65b1cd2ae1a 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 d832dda4754..43c7168dd2e 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 7162913087d..0e67e15d5c0 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 d458ce44d37..6120a20fd63 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 0fc66174c66..8e2bb3fa5df 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 f868c52cfb0..a7684d130ae 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 24dcb46bb68..f010ada02f3 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 2b0f9ed18e4..c5cf99de234 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 17329fbddff..aa330ffec16 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 7147fab1080..313f98a890c 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 753947a2c12..b73ec4e184e 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 0641a461130..31404b978eb 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 6b539e99186..37f669944d9 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 334b4f45483..1e2eb25245a 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 a26f7014444..0538bfbf369 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 c26606d52cf..9d2a4600a64 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 bae38f27ee2..06159a944a2 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 1029e58c118..8ecfebd5b33 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 9fc87b24a9b..16aec2ec617 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 7ce98fc2f2a..f2bf15d8a03 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 d1d398020de..9433046e688 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 4f528cfcb51..392c5986c89 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 7ce16600e1b..40ddfdf7b96 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 5a597994d6b..c92786e643f 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 33f1a7aa704..b1990fb80aa 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 31ad70e8aba..a5e9bbc0b82 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 fcb0047c135..c4282cdfbaf 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 da0ad24b135..8a92d89ce67 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 8a92d89ce67..5375001f75c 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 9ad922d7045..86e212bb11d 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 33c7e432fca..992c27bbe2e 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 f0c4f7bb3e2..06e28c42b13 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 84a72945a7e..0a4dd6bde78 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 9d47e4bd322..80a02b3275d 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 13fa64438d3..5da117e74c3 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 68445092db7..958e4a19777 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 a4ee25f0915..966107bcb36 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 414d4b0766c..e0982c65f33 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 704eab1846b..209fa7ba879 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 5ffd97ef0e3..7dd4e7dc32a 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 57bb9488ee8..9715a5fc024 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 f528de462ef..b7cf0e1de07 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 7845b47e218..055015b74f5 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 c6101230031..5b3af3029b4 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 7b1b7417cfd..2535fb76120 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 a7de62cd329..8c80604d188 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 23a331b21b2..4406062c821 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 b6546f2e67b..9f15c8b373f 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 a7b33b4c697..b9c75c87c1d 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 4247855da39..128377d19f7 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 7c3de210768..c67a3dffaaf 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 52d4165f3fa..bf7db87f06b 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 9ed613abde0..b2e41902552 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 8aeb93fed25..b4fe9d3fce9 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 47daf17f2a9..c3c6c1e2114 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 cb587c432c1..7d0730a969c 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 1cee08feb0a..8f5342de1e3 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 3eac6a370d2..ee401330209 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 807bf15854b..467d6dc3c6c 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 13bc6e9c558..3a3e19fb484 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 5410cb9ec1a..d75529a99b0 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 f67a6cbccec..b1975669731 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 e84377ee419..3c252adb7ea 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 1bbd263e585..3da0f19fbf0 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 a5ed0cdc02c..757a1b5fc09 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 6fe891b6b24..d88d52c4c8a 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 71f3374f0c0..53b246c700d 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 966107bcb36..aadf0103c5a 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 c67a3dffaaf..4c5592c02c2 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 2a4ec2966df..6f51d9aca2c 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 260856c6742..3ed9ea7b88e 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 2cbc2360fd4..9fe22fc7e79 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 d3f13ac4302..19185e165bc 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 c839ab7bc6e..84927712741 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 672bafeaf28..8aae5c0a28b 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 6eabfc713bc..25da38e7f75 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 0bb763a3fae..a6ad298dc9b 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 526c1a4b47b..07f15e7e88a 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 ee401330209..95a2a2f1fac 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 e05f951322e..ab582066a22 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 00000000000..7b5fefee58a --- /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 00000000000..3990a7da206 --- /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 f55fe7a03b3..490c11baad7 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 2ee0080a06d..7904f0a6cce 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 a6ad298dc9b..5a614e96471 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 c594bf1f99e..7c0867e3852 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 c7e88b210b3..f466664fc61 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 0847c116954..dc1e376f471 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 b222d78b577..2fa64ff8680 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 1591b8da1d2..559f29372de 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 4981ad23cc0..e24bec489f4 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 2d2f148189f..b52405aa8be 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 fb59d6254b0..18704183ec5 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 5a614e96471..8fb02383c27 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 aad851a93cb..be979aa5374 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 bc0cbc5eea6..3e3f7ce9486 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 3c252adb7ea..736bcec1e9c 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 3c1477d1828..0e77e6e95ad 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 544fd0e6b8a..b977fcd5c52 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 51e04fca351..b901b723c0b 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 bd428a84bed..472be92583a 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 634e3774b8a..74f8c85b532 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 95a2a2f1fac..f5b5ce62f8f 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 467d6dc3c6c..eea768c96a0 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 927139a483e..63a3e641170 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 a98bb6c2317..94856c730b1 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 d24110a4736..e9693b09634 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 b01e68f701d..555f302f8e1 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 f3d3cd06e22..2b6c2a113c4 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 523d4943ba0..a510d2b3829 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 73a79c2d87b..09d2f765d2a 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 00000000000..9a8f7dd7fbd --- /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 00000000000..10dbd630b75 --- /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 8fb02383c27..2cd4d8d4a49 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 09d2f765d2a..7e10830d876 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 00000000000..713f06db735 --- /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 2cd4d8d4a49..3d4b48398f9 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 92e363228a8..12057e88647 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 12b81c9003b..f4ce7bb3d1a 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 12dc0b1104f..fd3265b8230 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 ec480ac12d6..7cb7ef7151d 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 12ddf49fca8..c5678dff351 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 e42be861d37..3da9c512ebd 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 f80ea5853c8..58e8becd6bb 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 aa330ffec16..edd25817f5a 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 1e2eb25245a..aa030bf13c7 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 fcd9b7e8a07..bf6700371fd 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 958e4a19777..97fb2363024 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 ba1f60027ba..30c5a6177b4 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 472be92583a..58991a8e505 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 a6b345b1d3b..5631ec06cf1 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 30e307fd117..c4d8b778095 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 24f8ea7e6a9..c9a69923135 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 a8dda416a54..a6fee5a69e8 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 a5593c259a5..4aa121e0895 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 a7684d130ae..828dfc24d6c 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 4357c4109eb..8f26d4fe0ee 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 c5cf99de234..3ac49e354b5 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 1924de88aaf..d7be5b1a91c 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 421e84d503a..031df1d3a72 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 37f669944d9..ff098b24fb8 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 9d2a4600a64..22ed1a4dae6 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 06159a944a2..ce6775b9ea7 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 40ddfdf7b96..69f83aefad7 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 1e9e490adfe..00bc6582f3a 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 4d1f86059fc..225958a722c 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 be979aa5374..a38ac24dd7b 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 18704183ec5..5bf2b193957 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 3d4b48398f9..44f9602f818 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 a38ac24dd7b..913117adc2d 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 4a950910809..dfa67f48614 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 ea68a902c48..27750c9ac61 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 9a8f7dd7fbd..a6916b22a25 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 10dbd630b75..359aa029794 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 00000000000..94b7db8f1e5 --- /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 44f9602f818..3b89719651f 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 7e10830d876..84f4da0e371 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 00000000000..56ecaa7b2ca --- /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 3b89719651f..51ab8100372 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 4c71504104e..77621e3bc7c 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 c5408304018..54b85ffbe23 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 1cbe099ddde..128c45f1311 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 ed6c77f676c..9e0ef14a3fa 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 84f4da0e371..1d7fbd13f3e 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 00000000000..02c61024e30 --- /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 51ab8100372..b59d874f1cb 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 0ab5e7ce39a..5bc6260c0a7 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 b59d874f1cb..fb209f5e95a 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 96efa6e6c19..25435d373fd 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 0c0100bc9f5..672964f765e 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 de6db875def..2ccca366d90 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 c9ceda8651a..5a7e85d50dc 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 b30a242c17c..2dcf2c3f7be 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 499a4ca53a7..3f461784693 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 b80a5716061..de28eeff5ca 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 2b6c2a113c4..22ef34de54a 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 58991a8e505..8b4b2137711 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 b9ee8126ed3..cb69240ee73 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 98e3057338a..ed478550c7a 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 a84c278350f..33a00b65533 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 828dfc24d6c..b17df2c11fe 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 f466664fc61..c356c266db6 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 314799b185b..5567a431e58 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 74f8c85b532..4d779eef461 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 d679aa2c827..55e44299294 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 f53010ef27f..09dc27e97c1 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 38924817980..8da1311c87d 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 dc1688bae16..1fac1af9f64 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 e608dcccaba..39abf6f588f 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 7633c820d2d..9de801e0bb4 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 f5b5ce62f8f..0b2b4fb1a2e 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 00000000000..6d1b6cf4ecf --- /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 50bd03d6ced..8b6108067c5 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 1d7fbd13f3e..a33b996cb86 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 00000000000..ded78716317 --- /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 00000000000..9a94da158a1 --- /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 00000000000..dc9f9cc4c25 --- /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 fb209f5e95a..c951601ca3e 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 1db7c58d328..846604a9ff5 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 3104ce897a1..808f8cff6a1 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 2ecee06d8a3..e17823a1e44 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 c951601ca3e..6862c7cad1e 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 6a53569798b..78f471d125b 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 ca0f3527f73..00ae01f1123 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 6862c7cad1e..79d1e231d24 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 913117adc2d..469e48377d4 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 359aa029794..48632be6bad 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 79d1e231d24..af1d93e04c5 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 bf3916f3abe..a2a371163fd 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 9e32bf27c29..06062b26b00 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 0065f3e0927..e7aace8ec6d 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 6b2284f4376..ff8e7d7ddfe 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 af1d93e04c5..509d5faa7c7 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 f8a521c3e2f..f060c9f353a 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 509d5faa7c7..deb2a9d22c2 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 60df6327009..f3d8e269a42 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 8574a7231da..83f5478867f 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 deb2a9d22c2..0047ce38b73 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 469e48377d4..ff95cd3be25 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 66b17cf9bd9..e832314cf17 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 0047ce38b73..6ba57da573d 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 bbf10dd309d..7519fc6a873 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 e17823a1e44..029d10ea00a 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 6ba57da573d..6ad190f40ef 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 6241f3e5378..ba7fc4f9095 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 0c876d9e2d7..334948b67fb 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 6ad190f40ef..a8e57097bf2 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 d88d52c4c8a..a695848d1fa 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 a8e57097bf2..887b158899e 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 654374de08a..604fb91451e 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 887b158899e..000d4108a14 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 56ecaa7b2ca..1777376881e 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 000d4108a14..139d90971bf 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 a33b996cb86..3d369eed073 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 00000000000..efaea39bb86 --- /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 139d90971bf..9be2d66f327 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 95f2845eced..e066bb5e0b9 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 e8c7db5efe1..27027cc9eb4 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 11a507aded2..04d705d6b49 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 66790d02687..db0e8c590fd 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 5b689ece6ed..fbe9ffc948c 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 1a5e8124dfc..28b93c86ed7 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 e72d82eff08..74fb73dae1d 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 0cebd9cb9f8..4eebacbbbf2 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 01b1d0a92cf..983bc3a79d7 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 ff098b24fb8..64c9f4f02c9 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 b8fb393a2f3..5a8f515c3ad 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 b12c0c38f3a..69df11715e9 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)